From 5a390aa73357cefae122d3ec3853a9a85982082e Mon Sep 17 00:00:00 2001 From: ml Date: Sat, 13 Jun 2026 13:40:24 +0200 Subject: [PATCH] Add C64 SID audio output via OpenSL ES with sound toggle --- CLAUDE.md | 18 +- .../.idea/deploymentTargetSelector.xml | 33 ++ .../app/.gitignore | 8 +- .../app/build.gradle.kts | 42 +++ .../C64Engine.kt | 3 + .../MainActivity.kt | 71 ++-- .../app/src/main/jni/CMakeLists.txt | 2 +- .../app/src/main/jni/vice_jni.c | 319 +++++++++++++++++- .../app/src/main/res/layout/activity_main.xml | 27 +- SchwertUndMagieOnPebbleCompanionApp/res/basic | Bin 8192 -> 0 bytes .../res/chargen | Bin 4096 -> 0 bytes .../res/kernal | Bin 8192 -> 0 bytes 12 files changed, 435 insertions(+), 88 deletions(-) delete mode 100644 SchwertUndMagieOnPebbleCompanionApp/res/basic delete mode 100644 SchwertUndMagieOnPebbleCompanionApp/res/chargen delete mode 100644 SchwertUndMagieOnPebbleCompanionApp/res/kernal diff --git a/CLAUDE.md b/CLAUDE.md index cdb5ed8..0673df2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,21 +101,9 @@ The NDK must be installed: **Android Studio → SDK Manager → SDK Tools → ND After the first successful build, subsequent builds skip the VICE compilation step entirely (outputs are up-to-date). -**C64 ROM files** must be copied to the device separately (not redistributable). -VICE expects files named exactly `kernal`, `basic`, `chargen` (no extensions). - -**Via USB file transfer (recommended):** connect the phone, open it in Windows Explorer or -Android File Transfer (Mac), and copy the files to: -``` -Android/data/de.ladkau.schwertundmagieonpebblecompanionapp/files/ -``` - -**Via adb (alternative):** -```bash -adb push kernal /sdcard/Android/data/de.ladkau.schwertundmagieonpebblecompanionapp/files/kernal -adb push basic /sdcard/Android/data/de.ladkau.schwertundmagieonpebblecompanionapp/files/basic -adb push chargen /sdcard/Android/data/de.ladkau.schwertundmagieonpebblecompanionapp/files/chargen -``` +**C64 ROM files** (`kernal`, `basic`, `chargen`, `1541`) all ship inside the VICE 3.8 +tarball at `res/vice-3.8.tar.gz` and are bundled in the APK under `app/src/main/assets/`. +They are copied automatically to the external files dir on first launch — no user action needed. ### Key Kotlin/C files diff --git a/SchwertUndMagieOnPebbleCompanionApp/.idea/deploymentTargetSelector.xml b/SchwertUndMagieOnPebbleCompanionApp/.idea/deploymentTargetSelector.xml index f5f9d3d..f30b5fc 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/.idea/deploymentTargetSelector.xml +++ b/SchwertUndMagieOnPebbleCompanionApp/.idea/deploymentTargetSelector.xml @@ -13,6 +13,39 @@ + + + + + + \ No newline at end of file diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/.gitignore b/SchwertUndMagieOnPebbleCompanionApp/app/.gitignore index 42afabf..4218c1e 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/app/.gitignore +++ b/SchwertUndMagieOnPebbleCompanionApp/app/.gitignore @@ -1 +1,7 @@ -/build \ No newline at end of file +/build + +# ROM files extracted from the VICE tarball at build time (see extractViceRoms task) +/src/main/assets/kernal +/src/main/assets/basic +/src/main/assets/chargen +/src/main/assets/1541 \ No newline at end of file diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts b/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts index 15daf39..8ec2505 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts +++ b/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts @@ -130,6 +130,48 @@ tasks.whenTaskAdded { } } +// --------------------------------------------------------------------------- +// ROM extraction task +// +// Extracts C64 and drive ROMs from the bundled VICE tarball into the Android +// assets directory so they are packaged into the APK automatically. +// Output files are gitignored — the tarball is the single source of truth. +// --------------------------------------------------------------------------- + +val assetsDir = layout.projectDirectory.dir("src/main/assets") + +tasks.register("extractViceRoms") { + group = "build" + description = "Extract C64 and 1541 ROMs from the VICE tarball into assets/" + + from(tarTree(viceTarball.asFile)) { + include("vice-3.8/data/C64/kernal-901227-03.bin") + include("vice-3.8/data/C64/basic-901226-01.bin") + include("vice-3.8/data/C64/chargen-901225-01.bin") + include("vice-3.8/data/DRIVES/dos1541-325302-01+901229-05.bin") + eachFile { + // Flatten the tarball directory structure: place each ROM directly + // in assets/ with its short name rather than the versioned filename. + relativePath = RelativePath(true, when (name) { + "kernal-901227-03.bin" -> "kernal" + "basic-901226-01.bin" -> "basic" + "chargen-901225-01.bin" -> "chargen" + "dos1541-325302-01+901229-05.bin" -> "1541" + else -> name + }) + } + includeEmptyDirs = false + } + into(assetsDir) +} + +// Run extractViceRoms before assets are merged into the APK. +tasks.whenTaskAdded { + if (name.startsWith("merge") && name.endsWith("Assets")) { + dependsOn("extractViceRoms") + } +} + dependencies { implementation(libs.androidx.activity.ktx) implementation(libs.androidx.appcompat) diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/C64Engine.kt b/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/C64Engine.kt index d71aead..611a7dd 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/C64Engine.kt +++ b/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/C64Engine.kt @@ -37,6 +37,9 @@ class C64Engine { */ external fun injectKey(keyCode: Int, pressed: Boolean) + /** Enable or disable audio output. Disabled by default. */ + external fun setSoundEnabled(enabled: Boolean) + companion object { init { System.loadLibrary("vice_jni") } diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/MainActivity.kt b/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/MainActivity.kt index 1d208c4..3d96f19 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/MainActivity.kt +++ b/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/MainActivity.kt @@ -11,11 +11,9 @@ import android.util.Log import android.widget.Button import android.widget.ScrollView import android.widget.TextView -import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import androidx.documentfile.provider.DocumentFile import fi.iki.elonen.NanoHTTPD import java.io.File import java.text.SimpleDateFormat @@ -45,6 +43,7 @@ class MainActivity : AppCompatActivity() { private val engine = C64Engine() private var emuThread: Thread? = null @Volatile private var emuRunning = false + private var soundEnabled = false // ---- disk slots --------------------------------------------------------- @@ -56,41 +55,6 @@ class MainActivity : AppCompatActivity() { ) private val episodeButtons = mutableMapOf() - // ---- ROM import (pick folder — kernal/basic/chargen have no extension) -- - - private val romNames = listOf("kernal", "basic", "chargen") - - private val romDirPickerLauncher: ActivityResultLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.data?.let { treeUri -> importRomDir(treeUri) } - } - } - - private fun importRomDir(treeUri: Uri) { - val dir = DocumentFile.fromTreeUri(this, treeUri) ?: run { - appendLog("Cannot access selected folder") - return - } - val files = dir.listFiles() - var imported = 0 - for (name in romNames) { - val file = files.firstOrNull { it.name?.lowercase() == name } ?: run { - appendLog("Not found: $name") - continue - } - importFileToAssets(file.uri, name) - appendLog("Imported: $name") - imported++ - } - if (imported == romNames.size) { - appendLog("All ROMs imported — restarting emulator") - mainHandler.postDelayed({ recreate() }, 400) - } else { - appendLog("Only $imported/${romNames.size} ROMs found — check folder contents") - } - } - // ---- Disk import (multi-select) ----------------------------------------- private val diskPickerLauncher: ActivityResultLauncher = @@ -126,13 +90,17 @@ class MainActivity : AppCompatActivity() { display = findViewById(R.id.c64_display) keyboard = findViewById(R.id.c64_keyboard) - findViewById