Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

slab/menuband: type a GM voice number while in TYPE mode

Number-row 0–9 builds up a 1–3 digit GM program (0–127) live as you
type. Each press applies the new value through setMelodicProgram so
the menubar voice badge + popover readout update in real time; a
3-digit cap means the 4th digit starts a fresh sequence without an
explicit clear. Buffer resets on note play and on TYPE-mode exit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+56
+56
slab/menuband/Sources/MenuBand/MenuBandController.swift
··· 424 424 keyTap?.stop() 425 425 keyTap = nil 426 426 releaseAllHeldNotes() 427 + voiceDigitBuffer = "" 427 428 UserDefaults.standard.set(false, forKey: typeModeKey) 428 429 if playFeedback { 429 430 NSSound(named: NSSound.Name("Pop"))?.play() ··· 622 623 } 623 624 } 624 625 626 + // MARK: - Voice-by-digit picker (TYPE mode) 627 + 628 + /// Buffer of digit keystrokes typed since the last note play / reset. 629 + /// Each digit press updates the GM program live (clamped to 0–127); 630 + /// after 3 digits the next press starts a fresh sequence so the user 631 + /// can keep typing without an explicit "clear." Resets when any note 632 + /// key is played, when TYPE mode is disabled, or when the buffer 633 + /// reaches its 3-digit cap. 634 + private var voiceDigitBuffer: String = "" 635 + 636 + /// Map a hardware key code to the digit on its key cap, or nil for 637 + /// non-digit keys. Covers the top number row only — keypad digits 638 + /// have separate codes and are intentionally skipped (most laptops 639 + /// don't have one and we don't want a numpad press to silently 640 + /// repurpose itself). 641 + @inline(__always) 642 + private static func digitForKeyCode(_ kc: UInt16) -> Int? { 643 + switch kc { 644 + case 29: return 0 645 + case 18: return 1 646 + case 19: return 2 647 + case 20: return 3 648 + case 21: return 4 649 + case 23: return 5 650 + case 22: return 6 651 + case 26: return 7 652 + case 28: return 8 653 + case 25: return 9 654 + default: return nil 655 + } 656 + } 657 + 625 658 // MARK: - Key handling (runs on KeyEventTap background thread) 626 659 // Returns true to CONSUME the key event (sink it from the focused app); 627 660 // false to let it pass through. ··· 657 690 // Modifier combos pass through so cmd-c, cmd-tab etc. work as usual. 658 691 if hasModifier { return false } 659 692 693 + // Number-row digits 0–9 build up a GM program selection (0–127). 694 + // Each digit appends to the buffer and applies the new value live; 695 + // a 3-digit cap means the 4th press starts over with that digit 696 + // alone, so the user can sweep voices without a clear key. Down- 697 + // events only — repeats are consumed silently. Always consume so 698 + // digit keystrokes never leak through to the focused app. 699 + if let digit = Self.digitForKeyCode(keyCode) { 700 + if isDown && !isRepeat { 701 + if voiceDigitBuffer.count >= 3 { voiceDigitBuffer = "" } 702 + voiceDigitBuffer.append(String(digit)) 703 + if let v = Int(voiceDigitBuffer) { 704 + let program = UInt8(max(0, min(127, v))) 705 + DispatchQueue.main.async { [weak self] in 706 + self?.setMelodicProgram(program) 707 + } 708 + } 709 + } 710 + return true 711 + } 712 + 660 713 let shift = octaveShift // a single UserDefaults read; cheap 661 714 662 715 if isDown { ··· 690 743 if !midiMode { synth.noteOff(prevNote, channel: prevCh ?? 0) } 691 744 midi.noteOff(prevNote) 692 745 } 746 + // A note press confirms the picked voice — clear so the next 747 + // digit starts a fresh sequence instead of extending the old. 748 + voiceDigitBuffer = "" 693 749 lastPlayedNote = note 694 750 // Stereo pan from the qwerty key's physical column — 695 751 // mirrors notepat native, so left-hand keys play left and