From af7f5bbb0fb9f2d40d0a326b7a22bed50382e09d Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Fri, 3 Jul 2026 12:48:27 -0700 Subject: [PATCH] menuband: Ctrl+backtick records a chromatic sample (per-key stays on Shift) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sample instrument now has three entry gestures on the grave key: ` → normal global sample (C4 = raw) Ctrl+` → chromatic global sample (pitch-corrected via detected fundamental) Shift+` (~) → per-key sampling (unchanged) Threads a control flag through playKeyEvent → handleSampleRecordKey → synth.startSampleRecording(chromatic:) → sampleVoice.chromaticSample. Co-Authored-By: Claude Opus 4.8 --- .../Sources/MenuBand/MenuBandController.swift | 23 ++++++++++++------- .../Sources/MenuBand/MenuBandSynth.swift | 9 +++++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/slab/menuband/Sources/MenuBand/MenuBandController.swift b/slab/menuband/Sources/MenuBand/MenuBandController.swift index fb27350c7b..239b729743 100644 --- a/slab/menuband/Sources/MenuBand/MenuBandController.swift +++ b/slab/menuband/Sources/MenuBand/MenuBandController.swift @@ -1603,16 +1603,17 @@ final class MenuBandController { onChange?() } - private func handleSampleRecordKey(isDown: Bool, isRepeat: Bool, source: String) -> Bool { + private func handleSampleRecordKey(isDown: Bool, isRepeat: Bool, chromatic: Bool = false, source: String) -> Bool { if isDown && isRepeat { return true } - NSLog("MenuBand SampleVoice: backtick \(isDown ? "down" : "up") received via \(source)") + NSLog("MenuBand SampleVoice: backtick \(isDown ? "down" : "up") chromatic=\(chromatic) via \(source)") DispatchQueue.main.async { [weak self] in guard let self = self else { return } if isDown { // "Home" gesture: clear all per-key custom samples, then - // record a fresh global sample while held. + // record a fresh global sample while held. ⌃ picks chromatic + // (pitch-corrected) vs the default normal (C4 = raw) mode. self.synth.clearPerKeySamples() - self.synth.startSampleRecording() + self.synth.startSampleRecording(chromatic: chromatic) // Nudge the AppDelegate so the menubar icon immediately // picks up the red "REC" tint on the chip. self.onInstrumentVisualChange?() @@ -2464,7 +2465,8 @@ final class MenuBandController { hasModifier: hasMod, lingerSide: side, chordModifier: flags.contains(.maskCommand) || flags.contains(.maskAlternate), chordMinor: flags.contains(.maskAlternate), - chordSus: flags.contains(.maskCommand) && flags.contains(.maskAlternate)) + chordSus: flags.contains(.maskCommand) && flags.contains(.maskAlternate), + control: flags.contains(.maskControl)) } /// Sandbox-friendly key path: same note logic as the global tap, but @@ -2494,7 +2496,8 @@ final class MenuBandController { hasModifier: hasMod, lingerSide: side, chordModifier: flags.contains(.command) || flags.contains(.option), chordMinor: flags.contains(.option), - chordSus: flags.contains(.command) && flags.contains(.option)) + chordSus: flags.contains(.command) && flags.contains(.option), + control: flags.contains(.control)) } /// Shared note logic for both the global CGEventTap path and the @@ -2504,7 +2507,7 @@ final class MenuBandController { /// past key-up so it rings on its release envelope (sustained /// voices) rather than cutting on release. @discardableResult - private func playKeyEvent(keyCode: UInt16, isDown: Bool, isRepeat: Bool, hasModifier: Bool, lingerSide: LingerSide = .none, chordModifier: Bool = false, chordMinor: Bool = false, chordSus: Bool = false) -> Bool { + private func playKeyEvent(keyCode: UInt16, isDown: Bool, isRepeat: Bool, hasModifier: Bool, lingerSide: LingerSide = .none, chordModifier: Bool = false, chordMinor: Bool = false, chordSus: Bool = false, control: Bool = false) -> Bool { // Modifier-chord: holding ⌘/⌥ + a note key plays & HOLDS that key's // triad (the pressed note is the root), lingering on release just // like Shift. ⌘ = major, ⌥ = minor, ⌘+⌥ = sus; ⌃ is inert (its @@ -2702,7 +2705,11 @@ final class MenuBandController { onInstrumentVisualChange?() return true } - return handleSampleRecordKey(isDown: isDown, isRepeat: isRepeat, source: typeMode ? "type" : "local") + // Plain ` = normal global sample (C4 = raw); ⌃+` = chromatic + // (pitch-corrected) global sample. Two modes of the Sample voice. + return handleSampleRecordKey(isDown: isDown, isRepeat: isRepeat, + chromatic: control, + source: typeMode ? "type" : "local") } // ~ held + a note key = record a per-key sample into that key diff --git a/slab/menuband/Sources/MenuBand/MenuBandSynth.swift b/slab/menuband/Sources/MenuBand/MenuBandSynth.swift index 3d96f3c057..1c1d784e86 100644 --- a/slab/menuband/Sources/MenuBand/MenuBandSynth.swift +++ b/slab/menuband/Sources/MenuBand/MenuBandSynth.swift @@ -1797,15 +1797,18 @@ final class MenuBandSynth { /// Begin recording into the sample voice's buffer. Wakes the audio /// engine first if it was suspended for idle, since the input-node /// tap won't deliver frames against a paused graph. - func startSampleRecording() { startSampleRecording(forKey: nil) } + func startSampleRecording(chromatic: Bool = false) { startSampleRecording(forKey: nil, chromatic: chromatic) } /// Start recording. `forKey` non-nil commits into that key's per-key slot /// (the ~+key gesture); nil records the global sample (the ` gesture). - func startSampleRecording(forKey midi: UInt8?) { + /// `chromatic` picks the global-sample playback mode: false = normal + /// (C4 = raw), true = chromatic (pitch-corrected via detected fundamental). + func startSampleRecording(forKey midi: UInt8?, chromatic: Bool = false) { guard started else { return } - NSLog("MenuBand SampleVoice: synth startSampleRecording forKey=\(midi.map(String.init) ?? "global") (playbackEngineRunning=\(engine.isRunning))") + NSLog("MenuBand SampleVoice: synth startSampleRecording forKey=\(midi.map(String.init) ?? "global") chromatic=\(chromatic) (playbackEngineRunning=\(engine.isRunning))") _ = resumeAudioEngineIfNeeded() sampleRecordingActive = true + sampleVoice.chromaticSample = chromatic sampleVoice.setOutputEnabled(false) sampleVoice.panic() sampleVoice.startRecording(forKey: midi) -- 2.51.2