diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5ed2179..934faef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,8 +22,8 @@ android { // (android.media.midi) and AAudio low-latency audio we rely on. minSdk = 26 targetSdk = 34 - versionCode = 30 - versionName = "0.11.2" + versionCode = 31 + versionName = "0.12.0" // We provide our own instrumentation runner if/when tests are added. testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/space/rcmd/android/sizzle/ui/AppViewModel.kt b/app/src/main/java/space/rcmd/android/sizzle/ui/AppViewModel.kt index 7708852..1df5cd3 100644 --- a/app/src/main/java/space/rcmd/android/sizzle/ui/AppViewModel.kt +++ b/app/src/main/java/space/rcmd/android/sizzle/ui/AppViewModel.kt @@ -221,6 +221,18 @@ class AppViewModel( fun kbdNoteOn(midi: Int) { markNoteOn(midi); engine.busNoteOn(kbdChannel, midi, kbdVelocity) } fun kbdNoteOff(midi: Int) { markNoteOff(midi); engine.busNoteOff(kbdChannel, midi) } + /** True if the on-screen keyboard's MIDI channel routes to any bus whose instrument + * is a Sampler. A Sampler maps a pad to each MIDI note, so scale filtering would + * hide pads — the stacked keyboards go fully chromatic in that case. */ + fun kbdRoutesToSampler(): Boolean { + for (ch in project.mixer.channels) { + if (ch.midiChannel != kbdChannel) continue + val slot = project.toolbox.getOrNull(ch.instrumentSlot) ?: continue + if (slot.type == space.rcmd.android.sizzle.model.ToolboxType.SAMPLER) return true + } + return false + } + /** Live VU level (peak, ~0..1+) for mixer bus [ch] — polled at frame rate by the * mixer's fader VU meters. */ fun channelMeter(ch: Int): Float = engine.channelMeter(ch) diff --git a/app/src/main/java/space/rcmd/android/sizzle/ui/StackedKeyboard.kt b/app/src/main/java/space/rcmd/android/sizzle/ui/StackedKeyboard.kt index e263dbb..a596cce 100644 --- a/app/src/main/java/space/rcmd/android/sizzle/ui/StackedKeyboard.kt +++ b/app/src/main/java/space/rcmd/android/sizzle/ui/StackedKeyboard.kt @@ -28,6 +28,9 @@ import space.rcmd.android.sizzle.ui.theme.LocalRetro private const val VEL_STEP = 8 +/** All twelve pitch classes — used when scale filtering is bypassed (Sampler routing). */ +private val CHROMATIC: Set = (0..11).toSet() + /** * A stacked two-octave keyboard shared by the Toolbox (audition) and Tracker * (punch-in) tabs. Its channel, octave range and velocity live on the @@ -41,9 +44,14 @@ fun StackedKeyboard(vm: AppViewModel, punchIn: Boolean, modifier: Modifier) { val c = LocalRetro.current @Suppress("UNUSED_VARIABLE") val rev = vm.revision // refresh key filtering on scale/root change // Only in-scale keys are playable on BOTH the tracker punch-in and the toolbox - // audition keyboard (Chromatic yields all twelve = every key stays enabled). + // audition keyboard (Chromatic yields all twelve = every key stays enabled). But a + // Sampler maps a pad to each MIDI note, so scale filtering would hide pads — when + // the keyboard's channel routes to a Sampler, go fully chromatic so every pad is + // reachable. val root = vm.project.rootNote - val inKey: Set = vm.project.scale.intervals.map { (it + root) % 12 }.toSet() + val inKey: Set = + if (vm.kbdRoutesToSampler()) CHROMATIC + else vm.project.scale.intervals.map { (it + root) % 12 }.toSet() Column( modifier.fillMaxWidth().background(c.background).padding(6.dp), verticalArrangement = Arrangement.spacedBy(4.dp),