Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

notepat: Enter is a hold modifier — latch while held

Enter now works like a modifier key:
- Press + hold Enter: engages hold mode. Any note keys pressed while
Enter is held auto-latch into heldKeys. Also immediately snapshots
anything already playing so the existing chord sustains.
- Release Enter: disengages. New notes stop latching, but the notes
added while Enter was held stay sustained.
- Press Enter again later to add more — as many times as you want.
- Backspace: clear all holds.

Audio cues: ascending pair on Enter-down (660/990Hz), descending pair
on Enter-up (990/660Hz). Visual indicator shows "HOLD n" with a pulsing
green tint while Enter is held, and "H:n" in red once latched.

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

+51 -25
+51 -25
fedac/native/pieces/notepat.mjs
··· 55 55 let endArmed = false; // true while End key is held (arm per-key recording) 56 56 let perKeyRecording = null; // key currently recording in per-key mode 57 57 58 - // Hold/latch: Enter = add currently-playing notes to held set (press again 59 - // to add more). Backspace = clear all holds. New notes never auto-latch — 60 - // you can freely riff over held notes until you explicitly press Enter to 61 - // snapshot them into the held set. 58 + // Hold/latch: Enter works as a hold modifier. While Enter is physically 59 + // held down, any note keys pressed get auto-latched into heldKeys so 60 + // they keep ringing on key-up. Release Enter and the held notes stay — 61 + // you can riff freely over them. Press Enter again later to add more. 62 + // Backspace clears all holds. 62 63 let recitalMode = false; // F12: hide all UI, show only colored backdrops 63 64 let helpPanel = false; // Meta/Win key: show keyboard shortcut help overlay 64 65 let heldKeys = new Set(); // keys that are latched (won't stop on key-up) 66 + let enterHeld = false; // true while Enter key is physically held 65 67 66 68 // Effective pitch shift blended by FX mix (0% fx = no pitch shift) 67 69 function effectivePitchShift() { ··· 1526 1528 entry.midiNote = noteToMidiNumber(entry.note, entry.octave); 1527 1529 entry.midiChannel = 0; 1528 1530 sounds[key] = entry; 1529 - // New notes never auto-latch. Use Enter to snapshot currently-playing 1530 - // notes into the held set. 1531 + // While Enter is held, auto-latch new notes into heldKeys so they 1532 + // sustain on key-up. Otherwise notes release normally. 1533 + if (enterHeld) heldKeys.add(key); 1531 1534 system?.usbMidi?.noteOn?.(entry.midiNote, velocityToMidi(velocity), entry.midiChannel); 1532 1535 sendUdpMidiEvent(system, "note_on", entry.midiNote, velocityToMidi(velocity), entry.midiChannel); 1533 1536 pushUsbMidiRecent(">", entry.note, entry.octave); ··· 1901 1904 } 1902 1905 return; 1903 1906 } 1904 - // Enter: "add holds" — snapshot currently-playing keys into heldKeys. 1905 - // Press again at any time to add newly-playing notes. New notes never 1906 - // auto-latch, so you can riff freely and only commit to sustain by 1907 - // pressing Enter while the notes are still held down. 1907 + // Enter: "hold modifier" — while physically held, note keys pressed 1908 + // auto-latch into heldKeys. Also immediately latches any currently- 1909 + // playing notes so the existing chord sustains as soon as Enter 1910 + // engages. Release Enter and the latched notes keep ringing. 1908 1911 if (key === "enter") { 1909 - let added = 0; 1910 - for (const k of Object.keys(sounds)) { 1911 - if (!heldKeys.has(k)) { heldKeys.add(k); added++; } 1912 - } 1913 - if (added > 0) { 1914 - sound?.synth?.({ type: "sine", tone: 660, duration: 0.06, volume: 0.12, attack: 0.002, decay: 0.05 }); 1912 + if (!enterHeld) { 1913 + enterHeld = true; 1914 + // Snapshot anything playing right now into heldKeys 1915 + for (const k of Object.keys(sounds)) heldKeys.add(k); 1916 + // Engage cue: ascending pair 1917 + sound?.synth?.({ type: "sine", tone: 660, duration: 0.06, volume: 0.14, attack: 0.002, decay: 0.05 }); 1918 + sound?.synth?.({ type: "sine", tone: 990, duration: 0.08, volume: 0.10, attack: 0.005, decay: 0.07 }); 1915 1919 } 1916 1920 return; 1917 1921 } ··· 2190 2194 if (e.is("keyboard:up")) { 2191 2195 const key = e.key?.toLowerCase(); 2192 2196 if (!key) return; 2197 + // Enter release: exit hold-modifier mode. Latched notes stay latched. 2198 + if (key === "enter") { 2199 + if (enterHeld) { 2200 + enterHeld = false; 2201 + // Disengage cue: descending pair 2202 + sound?.synth?.({ type: "sine", tone: 990, duration: 0.06, volume: 0.10, attack: 0.002, decay: 0.05 }); 2203 + sound?.synth?.({ type: "sine", tone: 660, duration: 0.08, volume: 0.08, attack: 0.005, decay: 0.07 }); 2204 + } 2205 + return; 2206 + } 2193 2207 // Space release: stop the reverse playback queue. The duration of the 2194 2208 // press ends up defining the next loop's length. 2195 2209 if (key === "space") { ··· 4204 4218 box(obx, waveRowY, 1, waveRowH, true); 4205 4219 ink(dark ? 140 : 100, dark ? 140 : 100, dark ? 150 : 110); 4206 4220 write("o:" + octave, { x: obx + 3, y: waveRowY + 3, size: 1, font: "font_1" }); 4207 - // Hold count indicator: show "H:n" where n = number of held notes. 4208 - // Drawn to the LEFT of the octave button; only visible when 4209 - // there's something held, so it doesn't fight for screen real estate. 4210 - if (heldKeys.size > 0) { 4211 - const label = "H:" + heldKeys.size; 4221 + // Hold indicator: shown when Enter is held (arming) OR when there 4222 + // are latched notes. While Enter is held the label is "HOLD" with 4223 + // a pulsing green tint; otherwise "H:n" with the current count. 4224 + if (enterHeld || heldKeys.size > 0) { 4225 + const label = enterHeld 4226 + ? "HOLD" + (heldKeys.size > 0 ? " " + heldKeys.size : "") 4227 + : "H:" + heldKeys.size; 4212 4228 const holdW = label.length * 6 + 4; 4213 4229 const hbx = obx - holdW - 2; 4214 - ink(dark ? 80 : 180, dark ? 40 : 100, dark ? 40 : 100); 4215 - box(hbx, waveRowY, holdW, waveRowH, true); 4216 - ink(255, dark ? 180 : 60, dark ? 120 : 40); 4217 - write(label, { x: hbx + 2, y: waveRowY + 3, size: 1, font: "font_1" }); 4230 + if (enterHeld) { 4231 + // Pulsing green while armed 4232 + const pulse = (Math.sin(frame * 0.25) + 1) * 0.5; // 0..1 4233 + const g = Math.round(140 + pulse * 80); 4234 + ink(dark ? 30 : 140, g, dark ? 40 : 160); 4235 + box(hbx, waveRowY, holdW, waveRowH, true); 4236 + ink(0, 0, 0); 4237 + write(label, { x: hbx + 2, y: waveRowY + 3, size: 1, font: "font_1" }); 4238 + } else { 4239 + ink(dark ? 80 : 180, dark ? 40 : 100, dark ? 40 : 100); 4240 + box(hbx, waveRowY, holdW, waveRowH, true); 4241 + ink(255, dark ? 180 : 60, dark ? 120 : 40); 4242 + write(label, { x: hbx + 2, y: waveRowY + 3, size: 1, font: "font_1" }); 4243 + } 4218 4244 } 4219 4245 } 4220 4246