Chipbox is a music player that doesn't play MP3s. Instead it plays the raw program/data dumps of old video game console sound chips and emulates them in real time, reproducing the original soundtracks near-exactly from files as small as a few dozen KiB.
It targets modern Android (minSdk 26 / Android 8.0+, targetSdk 36) and also runs as a desktop (JVM) application. The codebase is Kotlin Multiplatform, with a shared Compose Multiplatform UI, Metro dependency injection, and Kotlin coroutines.
Playback is provided by several emulator cores wired in via the
cbox:*:player:emulators:* modules:
- GME (game-music-emu) — SPC (SNES), NSF/NSFE (NES), GBS (Game Boy), AY, HES, KSS and more
- VGM/VGZ — Genesis / Mega Drive, 32X, Arcade and numerous other systems
- PSF / miniPSF (slopsf) — Sony PlayStation
- GSF (mGBA) — Game Boy Advance
- USF / miniUSF — Nintendo 64
- SSF — Sega Saturn
- 2SF — Nintendo DS (DS Sound Format)
- NCSF / miniNCSF (SSEQ-Player) — Nintendo DS (Nitro Composer)
- vgmstream — streamed game audio (ADX, HCA, DSP, STRM, and many more single-file formats)
Chipbox is a Kotlin Multiplatform project built on top of the SAGE scaffold
(included as the sage git submodule). Most modules follow an
api / di / real / fake split, with an all aggregator:
apps/android— the Android application;apps/jvm— the desktop (JVM) application. Both wire the:dimodules together and host the shared Compose UI.cbox/common/**— platform-agnostic, multiplatform code shared by both apps (player director, buffer, repository, scanner, settings, entities, and the Compose UI shell)cbox/android/**— Android-specific implementations (database, file content source, emulator JNI bridges, speaker output)features/**— Compose feature screens (library, browse, search, now-playing, game/artist detail, settings), most withapi/real/screenshotmodulessage/— build logic and shared infrastructure (submodule)
Dependency injection is Metro (with Dagger-annotation interop). Navigation uses Voyager. Native emulator cores are integrated through JNI; everything else is Kotlin.
git clone --recurse-submodules git@github.com:sigmabeta/chipbox.git
cd chipbox
./gradlew :apps:android:assembleDebugIf you already cloned without submodules, initialize the sage submodule first:
git submodule update --init --recursiveInstall a debug build to a connected device:
./gradlew :apps:android:installDebugRun the desktop app on the host (native emulator libs are host-built via CMake; runs natively on Linux):
./gradlew :apps:jvm:runA Windows distribution is cross-compiled from Linux with the MinGW-w64
toolchain (all nine emulator cores, as fully static .dlls). It needs the cross
toolchain (mingw-w64 + a static MinGW zlib) and a Windows JDK for the win32 JNI
headers:
./gradlew :apps:jvm:distZip -Pchipbox.jvm.nativeTarget=windows-x64 \
-Pchipbox.jvm.nativeJdk=/path/to/windows-jdkOn a macOS host the cores build natively to .dylib, so ./gradlew :apps:jvm:run works as on Linux, and ./gradlew :apps:jvm:packageDmg produces a
.dmg. It's Apple Silicon (arm64) only and currently ships unsigned (signing +
notarization are a TODO).
Release builds are signed with chipbox.jks when the CHIPBOX_KEY_ALIAS,
CHIPBOX_KEYSTORE_PASSWORD, and CHIPBOX_KEY_PASSWORD environment variables are
present (set as GitHub Actions secrets); local builds fall back to debug signing.
Beyond unit tests and Paparazzi screenshots, Chipbox has a cross-platform UI
test framework (:cbox:common:uitest). A test scripts the real Compose UI —
the actual ChipboxAppUi shell, real ViewModels, real navigation — over fake
data via a small DSL:
runChipboxUiTest {
startAtScreen(GameDetail(firstGame().id))
assertTitle("Metal Slug")
clickWideItem(name = "JIM")
assertNavigationEvent(ArtistDetail(3023))
assertDirectorReceived(SessionRequest.Play)
}The same specs run on two targets:
./gradlew :cbox:common:uitest:jvmTest # desktop JVM (headless, fast)
./gradlew :cbox:common:uitest:connectedAndroidDeviceTest # on a connected device/emulatorTo watch a run on a device, add -Pchipbox.uitest.actionDelayMs=<ms> — it inserts a real pause
before each click and before the test ends, so you can see each action's effect (omit or 0 for no
delay):
./gradlew :cbox:common:uitest:connectedAndroidDeviceTest -Pchipbox.uitest.actionDelayMs=1500When a spec fails, the harness dumps a screenshot and a semantics-tree
dump of the live scene (named <TestClass>.<method>.png /
<TestClass>.<method>-semantics.txt) before rethrowing:
- desktop —
cbox/common/uitest/build/uitest-failures/ - on-device — pulled back to
cbox/common/uitest/build/outputs/connected_android_test_additional_output/androidDeviceTest/connected/<device>/
See arch-docs/architecture/ui-test-dsl.md for the design (verbs, the Metro test
graph, and the source-set topology).
- Gradle with the Kotlin DSL, configuration cache, and version catalogs
- Metro for dependency injection
- Compose Multiplatform (Material 3) for the shared UI
- Voyager for navigation
- Paparazzi for screenshot tests (
./gradlew verifyPaparazziDebug) - ktlint / detekt for static analysis (
./gradlew ktlintCheck detekt;./gradlew ktlintFormatauto-fixes) - GitHub Actions for CI (
.github/workflows/:ci.ymlpush/PR checks,release.ymltag releases). Cutting a release: push a version tag — seearch-docs/release-process.md.
- Independent tempo & pitch playback controls (see
arch-docs/psf-playback-speed-pitch-design.md) - Bespoke UI for Android TV
- Android Auto control support
- Add support for more emulator cores