Stacked keyboards go chromatic when routed to a Sampler (v0.12.0)
A Sampler maps a pad to each MIDI note, so scale filtering left out-of-scale pads greyed-out and unreachable. When the punch-in / audition keyboard's channel routes to a bus with a Sampler instrument, bypass the scale filter and enable all twelve keys; otherwise scale filtering is unchanged. Non-destructive - the project's scale/root setting is untouched, and switching the channel back to a synth/SF2 restores filtering. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,8 +22,8 @@ android {
|
|||||||
// (android.media.midi) and AAudio low-latency audio we rely on.
|
// (android.media.midi) and AAudio low-latency audio we rely on.
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 30
|
versionCode = 31
|
||||||
versionName = "0.11.2"
|
versionName = "0.12.0"
|
||||||
|
|
||||||
// We provide our own instrumentation runner if/when tests are added.
|
// We provide our own instrumentation runner if/when tests are added.
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -221,6 +221,18 @@ class AppViewModel(
|
|||||||
fun kbdNoteOn(midi: Int) { markNoteOn(midi); engine.busNoteOn(kbdChannel, midi, kbdVelocity) }
|
fun kbdNoteOn(midi: Int) { markNoteOn(midi); engine.busNoteOn(kbdChannel, midi, kbdVelocity) }
|
||||||
fun kbdNoteOff(midi: Int) { markNoteOff(midi); engine.busNoteOff(kbdChannel, midi) }
|
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
|
/** Live VU level (peak, ~0..1+) for mixer bus [ch] — polled at frame rate by the
|
||||||
* mixer's fader VU meters. */
|
* mixer's fader VU meters. */
|
||||||
fun channelMeter(ch: Int): Float = engine.channelMeter(ch)
|
fun channelMeter(ch: Int): Float = engine.channelMeter(ch)
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ import space.rcmd.android.sizzle.ui.theme.LocalRetro
|
|||||||
|
|
||||||
private const val VEL_STEP = 8
|
private const val VEL_STEP = 8
|
||||||
|
|
||||||
|
/** All twelve pitch classes — used when scale filtering is bypassed (Sampler routing). */
|
||||||
|
private val CHROMATIC: Set<Int> = (0..11).toSet()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stacked two-octave keyboard shared by the Toolbox (audition) and Tracker
|
* A stacked two-octave keyboard shared by the Toolbox (audition) and Tracker
|
||||||
* (punch-in) tabs. Its channel, octave range and velocity live on the
|
* (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
|
val c = LocalRetro.current
|
||||||
@Suppress("UNUSED_VARIABLE") val rev = vm.revision // refresh key filtering on scale/root change
|
@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
|
// 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 root = vm.project.rootNote
|
||||||
val inKey: Set<Int> = vm.project.scale.intervals.map { (it + root) % 12 }.toSet()
|
val inKey: Set<Int> =
|
||||||
|
if (vm.kbdRoutesToSampler()) CHROMATIC
|
||||||
|
else vm.project.scale.intervals.map { (it + root) % 12 }.toSet()
|
||||||
Column(
|
Column(
|
||||||
modifier.fillMaxWidth().background(c.background).padding(6.dp),
|
modifier.fillMaxWidth().background(c.background).padding(6.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
|||||||
Reference in New Issue
Block a user