Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

feat: notepat F8 hold/latch — sustain currently-playing notes

F8 captures all currently-held keys and keeps them sounding after
key release. Press F8 again to release all held notes. Visual
HOLD indicator shows next to octave in the wave row.

- High beep on engage, low beep on release
- Held keys immune to key-up until F8 toggled off
- stopAllSounds clears hold state (escape exit, etc.)

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

+35
+35
fedac/native/pieces/notepat.mjs
··· 28 28 let endArmed = false; // true while End key is held (arm per-key recording) 29 29 let perKeyRecording = null; // key currently recording in per-key mode 30 30 31 + // Hold/latch: F8 captures currently-held notes and keeps them sounding 32 + let holdActive = false; // true when hold is engaged 33 + let heldKeys = new Set(); // keys that are latched (won't stop on key-up) 34 + 31 35 // Effective pitch shift blended by FX mix (0% fx = no pitch shift) 32 36 function effectivePitchShift() { 33 37 return pitchShift * fxMix; ··· 348 352 } 349 353 350 354 function stopAllSounds(sound, system, fade = 0.08) { 355 + heldKeys.clear(); 356 + holdActive = false; 351 357 for (const key of Object.keys(sounds)) stopSoundKey(key, sound, system, fade); 352 358 touchNotes = {}; 353 359 system?.usbMidi?.allNotesOff?.(0); ··· 584 590 } 585 591 return; 586 592 } 593 + // F8: Hold/latch — capture currently-playing notes and keep them sounding 594 + if (key === "f8") { 595 + if (holdActive) { 596 + // Release hold: stop all held notes 597 + for (const k of heldKeys) stopSoundKey(k, sound, system, 0.08); 598 + heldKeys.clear(); 599 + holdActive = false; 600 + sound?.synth?.({ type: "sine", tone: 330, duration: 0.06, volume: 0.12, attack: 0.002, decay: 0.05 }); 601 + } else { 602 + // Engage hold: snapshot all currently-sounding keys 603 + heldKeys = new Set(Object.keys(sounds)); 604 + holdActive = heldKeys.size > 0; 605 + if (holdActive) { 606 + sound?.synth?.({ type: "sine", tone: 660, duration: 0.06, volume: 0.12, attack: 0.002, decay: 0.05 }); 607 + } 608 + } 609 + return; 610 + } 587 611 if (key >= "1" && key <= "9") { octave = parseInt(key); return; } 588 612 if (key === "arrowup") { octave = Math.min(9, octave + 1); return; } 589 613 if (key === "arrowdown") { octave = Math.max(1, octave - 1); return; } ··· 741 765 return; 742 766 } 743 767 if (sounds[key]) { 768 + // Don't stop held/latched notes on key release 769 + if (heldKeys.has(key)) return; 744 770 stopSoundKey(key, sound, system, quickMode ? 0.02 : 0.08); 745 771 } 746 772 } ··· 2298 2324 box(obx, waveRowY, 1, waveRowH, true); 2299 2325 ink(dark ? 140 : 100, dark ? 140 : 100, dark ? 150 : 110); 2300 2326 write("o:" + octave, { x: obx + 3, y: waveRowY + 3, size: 1, font: "font_1" }); 2327 + // HOLD indicator 2328 + if (holdActive) { 2329 + obx += octBtnW + 2; 2330 + const holdW = 30; 2331 + ink(dark ? 80 : 180, dark ? 40 : 100, dark ? 40 : 100); 2332 + box(obx, waveRowY, holdW, waveRowH, true); 2333 + ink(255, dark ? 180 : 60, dark ? 120 : 40); 2334 + write("HOLD", { x: obx + 2, y: waveRowY + 3, size: 1, font: "font_1" }); 2335 + } 2301 2336 } 2302 2337 2303 2338 // Mic level meter in sample mode — compact multi-segment bar next to REC.