Files
sizzletracker-android/app/build.gradle.kts
Reactorcoremeltdown 12c2092843 Render only active voices via per-block active-voice lists (v0.4.7)
Optimization B, superseding v0.4.6's per-bus skip. Instead of iterating
each active bus's whole POLYPHONY pool per sample (calling render() on
idle slots, which merely early-out), fillBlock now builds compact lists
of the voices that are actually sounding — once per block — and the
per-sample mix loop touches only those.

Correctness for mid-block starts: the sequencer can trigger a note part
way through a block, so the allocators queue the chosen voice into this
block's list (queueSeq/queueSmp), keeping note timing sample-accurate.
A per-voice generation stamp dedups the append so a voice that finishes
and is re-allocated within one block is never rendered twice (which would
double its amplitude). Live/bus-audition voices are snapshotted at block
start (a manual press mid-block sounds at the next block, ≤2 ms later —
imperceptible, and the pre-existing ~30 ms output buffer dwarfs it).

On-device (moto g84, 98% battery, real busy track, ~13 voices, 2.0 ms
budget), avg render callback:
  baseline 2.00 ms (xruns +250..+515) → A 1.44 ms (+0) → B ~1.00 ms (+0)
B removes ~51 inactive-slot render() calls per sample; ART's virtual
dispatch makes each ~90 ns, so this is a real ~30% gain over A (not the
~1% first estimated from the sample track — measurement corrected it).
Fast-tapping audition during playback stays ~1.0-1.3 ms, xruns +0,
voices drain cleanly (no stuck notes, no double-render).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:39:57 +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 = 14
versionName = "0.4.7"
// 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")
}