slab/menuband: 1.1 — sampler latency, pitch-bend range, MIDI fixes, transport master
App-side - MenuBandMIDI.send fixed (was the smoking-gun for "Live receives the source but no notes"): `var packet` so the cursor reassigns from MIDIPacketListAdd's return, `listSize: 1024` instead of the too-tight `MemoryLayout<MIDIPacketList>.size`, and `mach_absolute_time()` timestamp instead of `0` (which CoreMIDI on macOS 14+ treats as past-due on Live's MIDI input thread). UID bumped to 0x4D424E46 ("MBNF") plus kMIDIPropertyDisplayName / TransmitsNotes / TransmitsProgramChanges / MaxTransmitChannels=16 / Private=0 / Offline=0 so Live treats the source as a hardware-style input. RPN 0/0 = 12 announced on midi.start so the ±12-semitone bend range is honored by the DAW. - Pitch-bend range expanded everywhere: `bendSemitonesPerUnit = 12` shared constant, bendAmount accumulator no longer clamped, sample voice varispeed scales freely, staff visual + audio match. - Sample voice latency tightened: noteOff is instant (`voice.node.stop()` with no fade ramp), `stopAllVoices(playing:)` kills same-midi voices across channels on every noteOn so the controller's per-press channel rotation can't stack loops, voice prepare buffer dropped 512 → 256, input tap buffer 256 → 128 frames, record engine prewarms at attach time when mic permission already granted, hotMicIdleSeconds 3 → 30. Sample voice receives pitch-bend via `setBend(amount:)` — AVAudioUnitVarispeed ignores MIDI pitch-bend, so the controller routes trackpad bend in-process. - Recording cleanup: 35ms front-of-buffer skip swallows the mechanical key-press transient; `trimmedStartFrame` now drops any leading hot burst ≤6 windows long that's followed by quiet, so residual click that survives the fixed window is recognized and trimmed. Echo on recordEngine fixed by pinning `mainMixerNode.outputVolume = 0` before AND after start() — CoreMIDI auto-routed input → output on some Apple Silicon graphs, producing audible loopback while the tap was running. - Linger envelope (SHIFT-held release) overhauled: * Sustained voices now run a CC11 (Expression) + CC7 (Volume) ramp locally AND outbound to the DAW so organs/pads/brass fade audibly instead of holding flat then cutting. * `cancelLingerFade(channel:)` cancels in-flight ramps + restores Expression/Volume to 127 on every new noteOn, so "sustain comes after the press" (a new note inheriting the previous ramp's running value) is gone. * Linear ramp + 100 hops over 4 s (was ease-out / 6 s — read as a sharp early cutoff with a long inaudible tail). NoteOff deferred 200 ms past the last ramp tick + no CC reset at end of tail so the release envelope isn't punched back to full mid-decay. - Octave key behavior (,/.): one-press-one-step. Auto-repeat (both system AND the internal hold timer) disabled; the percussive tambourine click dropped. - Octave shift readout restored to popover title row ("octave N"), and QwertyLayoutView dims caps whose mapped note falls outside MIDI 0…127 at the current octave shift — visual cue for "this key is dead at this extreme." - Sample backend pitch-bend cursor: floating overlay panel (PitchBendCursorOverlayWindow) at .screenSaver level draws the bend wheel; CGDisplayHideCursor hides the real system cursor while a keyboard note is held, so the cross-app cursor flicker over other windows is gone (other apps' cursorUpdate handlers can't fight a cursor that isn't being drawn). - Sheet view changes: * `staticLoaded` flag + atomic `replaceChild` SVG swap in sheet.html so a PDF dropped on the staff doesn't get wiped by a stale queueRender; flicker on every keypress (`#score { contain: layout paint style }` + paper-colored backing layer on SheetMusicView) eliminated. * Notation now anchored to wall-clock time (first onset at tick 0, inferred BPM from median onset gap as a quarter note, SECS_PER_TICK scales each subsequent onset onto the 16th grid with rests filling gaps). * Playback highlight: `Sheet.playbackOn/Off(midi)` toggles a .playback-on class on rendered g.note elements during PDF drag-back-in playback. `SheetMusicView.playEvents` / `playImportedMIDI` fire those alongside startPlaybackNote. * Drag-out animates the sheet view away (scale + tumble + fade) with a procedural paper-crumple sound on .delete drops; supports drag to Trash to clear; in-popover trash icon removed. * Playback transport: green ▶ + red ⏹ buttons in the title row next to the metronome, visible only when a Menu Band PDF is loaded. SheetMusicView exposes isPlaybackActive / hasPlaybackEvents / onPlaybackStateChanged + restartPlayback() / stopPlayback(). * Notation flicker fix: debounced PDF refresh (250 ms idle) so the `body.exporting` toggle no longer flashes on every recordNote. - Arrow keys re-enabled as instrument-selector inside the picker: ←/→ step ±1, ↑/↓ step ±cols, each step fires a 300 ms preview blip on the new voice (same audition as palette hover-drag). Auto-repeat allowed so holding an arrow streams. - Menubar icon: 'M' badge in the subscript slot when MIDI mode is on (the GM program number is irrelevant once routing is outbound). Activity square indicator retained. - Popover/floating panel UX: * Collapsed floating panel now shows "078 Acoustic Grand Piano"-style program-number + name readout in YWFT Processing above the GM chooser grid. * `handleInstrumentCommit` no longer auto-disables MIDI mode (clicking a voice while routing to a DAW now sends a program change without yanking the route); digit-key path keeps the explicit "1-128 → play locally" auto-disable. - New file: CrumpleSound.swift — procedural paper-crumple SFX synthesised by random crackle bursts on broadband noise via a dedicated AVAudioEngine (own graph, doesn't interfere with synth). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>