Files
sizzletracker-android/app/build.gradle.kts
Reactorcoremeltdown 63cad6897f Rewrite the NES synth as a 3-oscillator subtractive synth; track-tied note-offs (v0.4.1)
Two changes that landed together (the note-off work was uncommitted when the synth
rewrite began):

Synth rewrite — replace the simple NES tone generator with a classic Minimoog-style
voice: 3 oscillators (+ noise) → mixer → resonant 4-pole ladder low-pass (own
contour envelope + key tracking) → amplifier (own envelope), plus a tempo-synced
LFO and a 4-slot modulation matrix.
- DSP: SynthVoice rewritten (PolyBLEP oscillators, ladder filter, two ADSRs, LFO,
  mod matrix); new SynthPatch parses a slot into an immutable snapshot per note.
  PolyBLEP + control-rate cutoff + precomputed osc tuning keep the RT cost down.
- Model: SynthDefs (shared option lists/defaults), rewritten NES_SYNTH params,
  NesSynthPresets (8 chiptune factory presets) seeded into the preset library.
- Editor: NesSynthEditor — swipeable cards (Oscillators / Filter / Amp+LFO / Mod
  Matrix) with dot pagination; new rotary Knob control (absolute, fader-like
  vertical drag: bottom = empty, top = full); enums via RetroDropdown. The shared
  slot-audition keyboard (renamed SoundFontKeyboard -> SlotAuditionKeyboard) is
  pinned at the editor bottom.
- Engine builds a SynthPatch per note. The enum id stays NES_SYNTH so existing
  projects still route; the old single `wave` param is dropped (amp envelope keys
  carry over).

Track-tied note-offs — a note-off ("===") is now tied to its track, not a channel:
it has no velocity/channel of its own (shown as ".." and non-editable) and simply
releases whatever that track last played, on the bus(es) that note routed to
(AudioEngine.trackLastChannel). `.sng` export writes "OFF .. ..".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:06:47 +02:00

136 lines
5.0 KiB
Kotlin

// Build configuration for the single application module. This is where we turn
// on Compose, set the min/target Android versions, and list dependencies.
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "space.rcmd.android.sizzle"
compileSdk = 34
// Native (Oboe) low-latency output backend; see src/main/cpp.
// NDK r27+ builds native libs 16 KB-page-aligned by default and ships a
// 16 KB-aligned libc++_shared.so (required for Android 15 / Google Play).
ndkVersion = "27.2.12479018"
defaultConfig {
applicationId = "space.rcmd.android.sizzle"
// minSdk 26 (Android 8.0) is the floor for the native MIDI API
// (android.media.midi) and AAudio low-latency audio we rely on.
minSdk = 26
targetSdk = 34
versionCode = 8
versionName = "0.4.1"
// We provide our own instrumentation runner if/when tests are added.
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake { arguments += "-DANDROID_STL=c++_shared" }
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
buildTypes {
release {
// Shrinking is off by default so newcomers get readable stack traces.
// Flip to true once the app stabilises.
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
// A realistic-performance "staging" variant for testing UI responsiveness
// on-device. The `debug` build is `debuggable = true`, which makes ART run
// the app un-optimised and makes Compose several times slower — so measuring
// UI "snappiness" on a debug build is misleading. This variant flips
// debuggability off (the single biggest perf lever) while reusing the debug
// signing key, so it still installs without a release keystore. R8/shrinking
// stays off to avoid needing JNI/Compose keep-rules for now.
create("staging") {
initWith(getByName("debug"))
isDebuggable = false
isJniDebuggable = false
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks += "debug"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
prefab = true // exposes the Oboe AAR's native headers/lib to CMake
}
packaging {
resources.excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
// Give the release APK a predictable, versionCode-stamped name so it drops straight
// into the private F-Droid repo: space.rcmd.android.sizzle_<versionCode>.apk. Uses
// the legacy variant API (the Kotlin-DSL `android {}` receiver doesn't expose it).
(extensions.getByName("android") as com.android.build.gradle.AbstractAppExtension)
.applicationVariants.all {
if (buildType.name == "release") {
val vc = versionCode
outputs.all {
(this as com.android.build.gradle.internal.api.BaseVariantOutputImpl)
.outputFileName = "space.rcmd.android.sizzle_$vc.apk"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.activity.compose)
// Compose UI toolkit — the BOM keeps every Compose artifact on one version.
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
debugImplementation(libs.androidx.compose.ui.tooling)
// Background playback + media-style notification transport controls.
implementation(libs.androidx.media3.session)
implementation(libs.androidx.media3.common)
// Persistence for settings & themes.
implementation(libs.androidx.datastore.preferences)
// Coroutines for the audio/sequencer background work.
implementation(libs.kotlinx.coroutines.android)
// Oboe: low-latency native audio (AAudio) — the native output backend.
// 1.9.3+ ships 16 KB-page-aligned .so files (required by Android 15 / Play).
implementation("com.google.oboe:oboe:1.10.0")
// JVM unit tests for the pure model/io logic (no device needed).
testImplementation("junit:junit:4.13.2")
}