Global rename of the app/package identifier from com.reactorcoremeltdown.sizzletracker to space.rcmd.android.sizzle: - build.gradle.kts: applicationId + namespace. - Move the Kotlin source tree (main + test) and rewrite every package/import. - native_audio.cpp: JNI symbol names (Java_space_rcmd_android_sizzle_...) so the Oboe callback still links against the relocated NativeAudioBridge. - docs: package/file-map header. The desktop-project GitHub URLs (github.com/reactorcoremeltdown/sizzletracker) and the Reactorcoremeltdown copyright/SPDX author identity are intentionally left unchanged. Note: the new applicationId installs as a separate package (existing installs won't upgrade in place). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
4.5 KiB
Kotlin
96 lines
4.5 KiB
Kotlin
// SPDX-FileCopyrightText: 2026 Reactorcoremeltdown
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package space.rcmd.android.sizzle
|
|
|
|
import android.app.Application
|
|
import space.rcmd.android.sizzle.audio.AudioEngine
|
|
import space.rcmd.android.sizzle.input.BindingStore
|
|
import space.rcmd.android.sizzle.input.GamepadInput
|
|
import space.rcmd.android.sizzle.input.InputRouter
|
|
import space.rcmd.android.sizzle.input.KeyboardInput
|
|
import space.rcmd.android.sizzle.input.MidiInput
|
|
import space.rcmd.android.sizzle.io.PresetLibrary
|
|
import space.rcmd.android.sizzle.io.ProjectStore
|
|
import space.rcmd.android.sizzle.io.SettingsStore
|
|
import space.rcmd.android.sizzle.io.SongLibrary
|
|
import space.rcmd.android.sizzle.io.ThemeLibrary
|
|
import space.rcmd.android.sizzle.model.Project
|
|
import java.io.File
|
|
import kotlinx.coroutines.runBlocking
|
|
|
|
/**
|
|
* The Application object is our (very small) dependency container. Instead of a
|
|
* DI framework — overkill for a learning-friendly codebase — the handful of
|
|
* app-wide singletons live here and are reached via `application as SizzleApp`.
|
|
*
|
|
* These objects outlive any single screen so playback and input keep working
|
|
* across rotation and tab switches.
|
|
*/
|
|
class SizzleApp : Application() {
|
|
|
|
/** The single in-memory song. Everything edits this one object. */
|
|
val project: Project by lazy { Project() }
|
|
|
|
/** Neutral input bus shared by touch / keyboard / gamepad / MIDI. */
|
|
val inputRouter: InputRouter by lazy { InputRouter() }
|
|
|
|
/** The real-time sound engine (started/stopped by the PlaybackService). */
|
|
val audioEngine: AudioEngine by lazy { AudioEngine() }
|
|
|
|
// Input source handlers are singletons so their (re)bindable maps are shared
|
|
// between MainActivity (which feeds them hardware events) and the Settings
|
|
// screen (which edits their bindings via MIDI-learn / gamepad rebind).
|
|
val keyboardInput: KeyboardInput by lazy { KeyboardInput(inputRouter) }
|
|
val gamepadInput: GamepadInput by lazy { GamepadInput(inputRouter) }
|
|
val midiInput: MidiInput by lazy { MidiInput(this, inputRouter) }
|
|
|
|
/** Persists the input binding maps across restarts (DataStore-backed). */
|
|
val bindingStore: BindingStore by lazy { BindingStore(this) }
|
|
|
|
/** On-disk instrument/effect preset library in the app's scoped storage. */
|
|
val presetLibrary: PresetLibrary by lazy { PresetLibrary(File(filesDir, "presets")) }
|
|
|
|
/** On-disk `.sng` song library in the app's scoped storage. */
|
|
val songLibrary: SongLibrary by lazy { SongLibrary(File(filesDir, "songs")) }
|
|
|
|
/** On-disk user colour-theme library (`.szt`) in the app's scoped storage. */
|
|
val themeLibrary: ThemeLibrary by lazy { ThemeLibrary(File(filesDir, "themes")) }
|
|
|
|
/** Autosave of the whole project (crash recovery / resume). */
|
|
val projectStore: ProjectStore by lazy { ProjectStore(File(filesDir, "autosave.sng")) }
|
|
|
|
/** App-level settings persistence (theme, audio backend). */
|
|
val settingsStore: SettingsStore by lazy { SettingsStore(this) }
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
// Crash recovery: restore the last autosaved project before the engine or
|
|
// UI read it, so a crash / process-death resumes where the user left off.
|
|
projectStore.restore(project)
|
|
// Reload the sample audio for any preset-backed Sampler/SoundFont slots —
|
|
// the autosave keeps file references, not the decoded PCM, so this makes a
|
|
// selected preset's samples sound again on a fresh start.
|
|
presetLibrary.rehydrate(project)
|
|
// Write the built-in factory presets (e.g. the Ambience reverb spaces) to the
|
|
// on-disk library on first launch so they appear in the standard browser.
|
|
presetLibrary.seedFactoryPresets()
|
|
audioEngine.setProject(project)
|
|
// Restore saved bindings before any hardware event can arrive. This is a
|
|
// single small read; blocking briefly at cold start keeps the input maps
|
|
// authoritative from the first keypress rather than racing an async load.
|
|
runBlocking { bindingStore.loadInto(keyboardInput, gamepadInput, midiInput) }
|
|
installCrashAutosave()
|
|
}
|
|
|
|
/** Flush an autosave of the current project on any uncaught exception, then let
|
|
* the default handler crash/report as usual — so state survives a crash. */
|
|
private fun installCrashAutosave() {
|
|
val previous = Thread.getDefaultUncaughtExceptionHandler()
|
|
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
|
runCatching { projectStore.save(project) }
|
|
previous?.uncaughtException(thread, throwable)
|
|
}
|
|
}
|
|
}
|