Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

notepat: zoo + laser kits rewritten with researched DSP recipes

Replaces the crude subtractive placeholders for both 12-sound kits
with recipes derived from published synthesis algorithms. Citations
in inline comments; sources spanned Andy Farnell's Designing Sound,
Van Hunter Adams' Pico birdsong project, DrPetter's sfxr, Ben Burtt's
film-sound notes, sfxr's presets, the Asteroids 555-circuit reverse-
engineering, and peer-reviewed bioacoustics papers (Klemuk/Anikin on
lion non-linear dynamics, Stoeger 2026 on horse biphonation, Briefer
on sheep vibrato, etc.).

## Zoo (12 animals)
- dog bark — saw 700→500→400 Hz sweep + BPF-noise 200 Hz + crack
- cat meow — arched F0 500→750→550→400 Hz + F1/F2 vowel sweep /e/→/o/
- cow moo — low saw F0 + F1 600→900 Hz ramp + F2 1942 Hz formant
- sheep baa — 7 Hz vibrato via alternating F0/F0·1.15 saw bursts
- bird chirp — Adams swoop (1740→2000→1740 Hz sine) + chirp sweep 2→7 kHz
- pig oink — grunt 93 Hz / squeal 300 Hz toggle, two staccato bursts
- lion roar — F0 130→90 Hz + forced subharmonic F0/2 + 900 Hz noise growl
- owl hoot — pure sine ~300 Hz with 5% tail droop
- frog ribbit— 60 Hz pulse-train approximation + 1 kHz vocal-sac peak
- horse neigh— BIPHONIC: saw F0 ~400 Hz vibrato + sine whistle G0 ~1500 Hz
- snake hiss — two-band BPF-noise (viper 300 Hz + grass-snake 6 kHz)
- whale song — additive sine glide 600→1000→400 Hz over 4 s + octave drone

## Lasers (12 sci-fi)
- pew — Asteroids 555: square exp decay 1000→200 Hz, τ=30 ms
- blast — Burtt: square dispersion ping 3000→800 Hz + BPF body + comb tail
- phaser — War-of-Worlds: sine 1000 Hz + 7 Hz beat + pink-noise menace
- cannon — sfxr explosion: sub-thump 80→40 Hz + LPF-swept noise + crack
- stun — sfxr hitHurt: square slide 900→300 Hz + 45 Hz ring-mod buzz
- plasma — detuned square stack + octave glow (triangle at 2×)
- disruptor — aggressive saw + sub + noise body + crack
- charge — exp pitch rise 100→2500 Hz over 750 ms + release pop
- beam — sustained square at 820 Hz + perfect fifth
- hit — noise crack 3500 Hz + sub-thump 80→50 Hz
- ricochet — descending noise pings + sine "bing" tail
- warp — rising sine glide 100→1000 Hz + rising BPF noise + climax pop

Pitch-sweep approximations use multi-voice staggered attacks since the
synth engine doesn't expose per-voice glide — each voice holds a fixed
frequency and the listener perceives the transition via overlapping
crossfades. Biphonic horse uses two parallel voice chains with stagger.
Lion subharmonic is a forced saw at F0/2 joining 40% into the roar.

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

+301 -90
+301 -90
fedac/native/pieces/notepat.mjs
··· 1221 1221 } 1222 1222 }; 1223 1223 switch (letter) { 1224 - case "c": { // dog bark — saw burst with pitch drop + noise attack 1225 - fire({ type: "sawtooth", tone: 200 * pf, volume: rj(0.75, 0.1) * v, attack: 0.001, decay: 0.07, pan }, 0.08); 1226 - sound.synth({ type: "sawtooth", tone: 110 * pf, duration: 0.05, volume: rj(0.55, 0.1) * v, attack: 0.015, decay: 0.035, pan }); 1227 - sound.synth({ type: "noise", tone: 2500 * pf, duration: 0.006, volume: 0.45 * v, attack: 0.0005, decay: 0.005, pan }); 1224 + case "c": { 1225 + // DOG BARK — Bruce's Pure Data recipe + Farnell Ch. 52 formant 1226 + // frame. Primary energy 400-700 Hz (Indian wolf harmonic data, 1227 + // Sadhukhan PLOS One). Saw source sweeping 700 → 400 Hz over ~15 ms 1228 + // (approximated with 3 staggered voices) + narrow-band noise 1229 + // burst at ~200 Hz (simulating BPF Q≈1 throat resonance) + 1230 + // high-frequency noise crack on attack. Percussive envelope 1231 + // (<5 ms attack, 80-150 ms decay, zero sustain). 1232 + const p1 = 700 * pf, p2 = 500 * pf, p3 = 400 * pf; 1233 + sound.synth({ type: "sawtooth", tone: p1, duration: 0.012, volume: rj(0.80, 0.08) * v, attack: 0.001, decay: 0.011, pan }); 1234 + sound.synth({ type: "sawtooth", tone: p2, duration: 0.040, volume: rj(0.72, 0.08) * v, attack: 0.008, decay: 0.032, pan }); 1235 + sound.synth({ type: "sawtooth", tone: p3, duration: 0.100, volume: rj(0.68, 0.08) * v, attack: 0.022, decay: 0.080, pan }); 1236 + // Throat resonance (approx. BPF-noise at ~200 Hz) 1237 + sound.synth({ type: "triangle", tone: 210 * pf, duration: 0.12, volume: rj(0.35, 0.08) * v, attack: 0.003, decay: 0.11, pan }); 1238 + // High-freq click on attack 1239 + sound.synth({ type: "noise", tone: 2800 * pf, duration: 0.006, volume: 0.40 * v, attack: 0.0003, decay: 0.005, pan }); 1228 1240 break; 1229 1241 } 1230 - case "d": { // cat meow — 2-peak pitch envelope "eow" 1231 - const base = 500 * pf; 1232 - sound.synth({ type: "sine", tone: base, duration: 0.12, volume: rj(0.55, 0.1) * v, attack: 0.02, decay: 0.1, pan }); 1233 - sound.synth({ type: "sine", tone: base * 0.65, duration: 0.12, volume: rj(0.50, 0.1) * v, attack: 0.08, decay: 0.1, pan }); 1234 - sound.synth({ type: "sine", tone: base * 0.9, duration: 0.12, volume: rj(0.45, 0.1) * v, attack: 0.20, decay: 0.1, pan }); 1242 + case "d": { 1243 + // CAT MEOW — MeowSynth / Farnell vowel-interpolation recipe. 1244 + // Arched F0 rises from 500 → 750 Hz, then falls to 400 Hz — 1245 + // food-context meow, per Schötz & Eklund. Four staggered 1246 + // saw-ish stages approximate the pitch arch. Vowel transition 1247 + // from /e/ (F1=400, F2=1900) → /o/ (F1=500, F2=900) sketched 1248 + // with two parallel formant-tracker sine voices that also 1249 + // sweep. ADSR ~30 ms A, 400 ms R, total 500-700 ms. 1250 + const atkBase = 0.025; 1251 + sound.synth({ type: "sawtooth", tone: 500 * pf, duration: 0.18, volume: rj(0.45, 0.08) * v, attack: atkBase, decay: 0.14, pan }); 1252 + sound.synth({ type: "sawtooth", tone: 700 * pf, duration: 0.18, volume: rj(0.50, 0.08) * v, attack: atkBase + 0.08, decay: 0.14, pan }); 1253 + sound.synth({ type: "sawtooth", tone: 550 * pf, duration: 0.18, volume: rj(0.48, 0.08) * v, attack: atkBase + 0.22, decay: 0.16, pan }); 1254 + sound.synth({ type: "sawtooth", tone: 400 * pf, duration: 0.24, volume: rj(0.40, 0.08) * v, attack: atkBase + 0.36, decay: 0.22, pan }); 1255 + // F1 / F2 formant singers — sine voices sweeping from /e/ to /o/ 1256 + sound.synth({ type: "sine", tone: 1900 * pf, duration: 0.22, volume: rj(0.15, 0.05) * v, attack: atkBase, decay: 0.18, pan }); 1257 + sound.synth({ type: "sine", tone: 900 * pf, duration: 0.28, volume: rj(0.18, 0.05) * v, attack: atkBase + 0.20, decay: 0.22, pan }); 1235 1258 break; 1236 1259 } 1237 - case "e": { // cow moo — low triangle with slow attack/sustain 1238 - sound.synth({ type: "triangle", tone: 95 * pf, duration: 0.55, volume: rj(0.85, 0.08) * v, attack: 0.05, decay: 0.4, pan }); 1239 - sound.synth({ type: "sine", tone: 48 * pf, duration: 0.55, volume: rj(0.65, 0.08) * v, attack: 0.08, decay: 0.4, pan }); 1260 + case "e": { 1261 + // COW MOO — Green et al. formant measurements: F0 80-150 Hz, 1262 + // F1 ~790 Hz, F2 ~1942 Hz. Low F0 drifts slightly downward, 1263 + // F1 opens ~600 → ~900 Hz over the tail ("mmm → ooo"). Saw 1264 + // source for harmonic richness + slow sine LFO (via a detuned 1265 + // voice) for vocal-fold ripple. 1266 + const base = 110 * pf; 1267 + sound.synth({ type: "sawtooth", tone: base, duration: 1.4, volume: rj(0.70, 0.06) * v, attack: 0.08, decay: 1.0, pan }); 1268 + sound.synth({ type: "sawtooth", tone: base * 0.93, duration: 1.4, volume: rj(0.45, 0.06) * v, attack: 0.10, decay: 1.0, pan }); 1269 + // F1 — mouth-closed to mouth-open 1270 + sound.synth({ type: "sine", tone: 600 * pf, duration: 0.7, volume: rj(0.35, 0.05) * v, attack: 0.10, decay: 0.5, pan }); 1271 + sound.synth({ type: "sine", tone: 900 * pf, duration: 0.9, volume: rj(0.30, 0.05) * v, attack: 0.50, decay: 0.7, pan }); 1272 + // F2 always-on 1273 + sound.synth({ type: "sine", tone: 1942 * pf, duration: 1.3, volume: rj(0.10, 0.03) * v, attack: 0.12, decay: 1.0, pan }); 1240 1274 break; 1241 1275 } 1242 - case "f": { // sheep baa — vibrato + triple pitch stagger 1243 - const base = 380 * pf; 1244 - sound.synth({ type: "triangle", tone: base, duration: 0.1, volume: rj(0.55, 0.1) * v, attack: 0.002, decay: 0.09, pan }); 1245 - sound.synth({ type: "triangle", tone: base * 0.9, duration: 0.1, volume: rj(0.50, 0.1) * v, attack: 0.08, decay: 0.09, pan }); 1246 - sound.synth({ type: "triangle", tone: base, duration: 0.1, volume: rj(0.45, 0.1) * v, attack: 0.18, decay: 0.09, pan }); 1276 + case "f": { 1277 + // SHEEP BAA — Briefer et al. *Current Biology* 2017: sheep 1278 + // vocalizations evolved a characteristic 5-8 Hz vibrato (±15%). 1279 + // Approximated with 6 staggered saw voices alternating between 1280 + // F0 and F0×1.15, ~50 ms each = 7 Hz vibrato. Base F0 ~280 Hz. 1281 + const base = 280 * pf; 1282 + const hi = base * 1.15; 1283 + const vibDur = 0.05; 1284 + for (let i = 0; i < 7; i++) { 1285 + const tone = (i & 1) ? hi : base; 1286 + sound.synth({ type: "sawtooth", tone, duration: vibDur + 0.03, 1287 + volume: rj(0.50, 0.08) * v, 1288 + attack: i * vibDur + 0.002, decay: vibDur + 0.02, pan }); 1289 + } 1290 + // Lamb/nasal harmonic 1291 + sound.synth({ type: "triangle", tone: base * 2, duration: 0.5, volume: rj(0.18, 0.05) * v, attack: 0.01, decay: 0.45, pan }); 1247 1292 break; 1248 1293 } 1249 - case "g": { // bird chirp — three fast descending high pulses 1250 - for (let i = 0; i < 3; i++) { 1251 - const atk = 0.04 * i; 1252 - const tone = (3200 - i * 450) * pf; 1253 - sound.synth({ type: "sine", tone, duration: 0.03, volume: rj(0.55, 0.15) * v, attack: 0.0005 + atk, decay: 0.025, pan }); 1294 + case "g": { 1295 + // BIRD CHIRP — Van Hunter Adams' Pico recipe: "swoop + chirp". 1296 + // Swoop = sine sinusoidal F0 contour 1740 → 2000 → 1740 Hz over 1297 + // 130 ms. Chirp = sine sweeping 2 → 7 kHz in 40-80 ms. Silence 1298 + // ~50 ms between primitives. 1299 + // Swoop approximation (3 stages around the peak) 1300 + sound.synth({ type: "sine", tone: 1740 * pf, duration: 0.045, volume: rj(0.52, 0.1) * v, attack: 0.002, decay: 0.04, pan }); 1301 + sound.synth({ type: "sine", tone: 2000 * pf, duration: 0.045, volume: rj(0.58, 0.1) * v, attack: 0.045, decay: 0.04, pan }); 1302 + sound.synth({ type: "sine", tone: 1740 * pf, duration: 0.045, volume: rj(0.52, 0.1) * v, attack: 0.090, decay: 0.04, pan }); 1303 + // Chirp — 2 → 7 kHz exponential sweep in 60 ms (5 stages) 1304 + for (let i = 0; i < 5; i++) { 1305 + const t = i / 4; 1306 + const tone = 2000 * Math.pow(3.5, t) * pf; // 2k → 7k 1307 + sound.synth({ type: "sine", tone, duration: 0.012 + 0.015, 1308 + volume: rj(0.50 - t * 0.08, 0.1) * v, 1309 + attack: 0.20 + i * 0.012 + 0.0005, 1310 + decay: 0.012, pan }); 1254 1311 } 1255 1312 break; 1256 1313 } 1257 - case "a": { // pig oink — two short saw bursts 1258 - sound.synth({ type: "sawtooth", tone: 140 * pf, duration: 0.07, volume: rj(0.70, 0.1) * v, attack: 0.003, decay: 0.060, pan }); 1259 - sound.synth({ type: "sawtooth", tone: 180 * pf, duration: 0.06, volume: rj(0.60, 0.1) * v, attack: 0.09, decay: 0.05, pan }); 1314 + case "a": { 1315 + // PIG OINK — Garcia et al. ventricular-fold study: F0 bounces 1316 + // between grunt ~93 Hz (folds engaged) and squeal ~409 Hz 1317 + // (folds disengaged). Classic oink = two staccato bursts, 100 ms 1318 + // each, 80 ms gap. Random F0 toggle mimics chaos. 1319 + const grunt = 93 * pf, squeal = 300 * pf; 1320 + // Burst 1 1321 + sound.synth({ type: "sawtooth", tone: grunt, duration: 0.09, volume: rj(0.72, 0.08) * v, attack: 0.005, decay: 0.08, pan }); 1322 + sound.synth({ type: "sawtooth", tone: squeal, duration: 0.04, volume: rj(0.50, 0.08) * v, attack: 0.03, decay: 0.03, pan }); 1323 + // Gap + burst 2 1324 + sound.synth({ type: "sawtooth", tone: grunt * 1.05, duration: 0.09, volume: rj(0.68, 0.08) * v, attack: 0.18, decay: 0.08, pan }); 1325 + sound.synth({ type: "sawtooth", tone: squeal * 0.92, duration: 0.04, volume: rj(0.48, 0.08) * v, attack: 0.21, decay: 0.03, pan }); 1326 + // Breath noise 1327 + sound.synth({ type: "noise", tone: 800 * pf, duration: 0.25, volume: rj(0.15, 0.05) * v, attack: 0.01, decay: 0.23, pan }); 1260 1328 break; 1261 1329 } 1262 - case "b": { // lion roar — resonant noise + sub saw 1263 - sound.synth({ type: "noise", tone: 420 * pf, duration: 0.85, volume: rj(0.60, 0.1) * v, attack: 0.06, decay: 0.7, pan }); 1264 - sound.synth({ type: "sawtooth", tone: 70 * pf, duration: 0.85, volume: rj(0.70, 0.1) * v, attack: 0.08, decay: 0.7, pan }); 1265 - sound.synth({ type: "sawtooth", tone: 110 * pf, duration: 0.5, volume: rj(0.35, 0.1) * v, attack: 0.12, decay: 0.35, pan }); 1330 + case "b": { 1331 + // LION ROAR — Klemuk et al. PLOS ONE + Anikin et al. RSB 2025. 1332 + // Two coupled oscillators + forced subharmonic at F0/2 produce 1333 + // the characteristic chaos. F0 ~130 Hz sliding down to ~90 Hz 1334 + // over the roar; subharmonic layer kicks in at 40% through. 1335 + // Filtered noise 600-1200 Hz for breathy growl. 2-4 s total. 1336 + const f0a = 130 * pf, f0b = 90 * pf; 1337 + sound.synth({ type: "sawtooth", tone: f0a, duration: 2.0, volume: rj(0.75, 0.08) * v, attack: 0.20, decay: 1.5, pan }); 1338 + sound.synth({ type: "sawtooth", tone: f0b, duration: 2.0, volume: rj(0.65, 0.08) * v, attack: 0.40, decay: 1.4, pan }); 1339 + // Forced subharmonic — emerges later, gives the growl ending 1340 + sound.synth({ type: "sawtooth", tone: f0a * 0.5, duration: 1.4, volume: rj(0.40, 0.08) * v, attack: 0.80, decay: 1.2, pan }); 1341 + // Filtered-noise breath band (600-1200 Hz approximated by 1342 + // band-limited noise at 900 Hz) 1343 + sound.synth({ type: "noise", tone: 900 * pf, duration: 1.8, volume: rj(0.28, 0.06) * v, attack: 0.30, decay: 1.4, pan }); 1266 1344 break; 1267 1345 } 1268 - case "c#": { // owl hoot — soft sine with long body 1269 - sound.synth({ type: "sine", tone: 320 * pf, duration: 0.5, volume: rj(0.65, 0.08) * v, attack: 0.08, decay: 0.4, pan }); 1270 - sound.synth({ type: "sine", tone: 160 * pf, duration: 0.5, volume: rj(0.45, 0.08) * v, attack: 0.1, decay: 0.4, pan }); 1346 + case "c#": { 1347 + // OWL HOOT — Cornell Lab Great Horned Owl data: near-pure sine, 1348 + // F0 250-350 Hz, slight 5% droop at tail. Call pattern often 1349 + // 4-5 hoots in 5 s; we do single hoot here (grid key = 1 hoot). 1350 + sound.synth({ type: "sine", tone: 300 * pf, duration: 0.5, volume: rj(0.72, 0.06) * v, attack: 0.10, decay: 0.35, pan }); 1351 + sound.synth({ type: "sine", tone: 285 * pf, duration: 0.2, volume: rj(0.45, 0.06) * v, attack: 0.40, decay: 0.15, pan }); 1352 + // Low body harmonic (great horned owls have faint F2 ~600 Hz) 1353 + sound.synth({ type: "sine", tone: 600 * pf, duration: 0.55, volume: rj(0.10, 0.04) * v, attack: 0.12, decay: 0.40, pan }); 1271 1354 break; 1272 1355 } 1273 - case "d#": { // frog ribbit — double low saw bursts 1274 - sound.synth({ type: "sawtooth", tone: 110 * pf, duration: 0.04, volume: rj(0.65, 0.08) * v, attack: 0.002, decay: 0.036, pan }); 1275 - sound.synth({ type: "sawtooth", tone: 95 * pf, duration: 0.04, volume: rj(0.55, 0.08) * v, attack: 0.07, decay: 0.036, pan }); 1356 + case "d#": { 1357 + // FROG RIBBIT — Yamaguchi *Xenopus* pulse-train research + 1358 + // Dinacon Túngara recipe. Fast trill = 60 Hz click rate, energy 1359 + // 100-500 Hz. Approximated with 8 quick sawtooth bursts at 1360 + // 60 Hz (~17 ms spacing) + a narrow-band resonance peak at 1 kHz. 1361 + const base = 180 * pf; 1362 + for (let i = 0; i < 8; i++) { 1363 + sound.synth({ type: "sawtooth", tone: base, duration: 0.012, 1364 + volume: rj(0.55, 0.08) * v * (1 - i * 0.05), 1365 + attack: 0.001, decay: 0.010, pan }); 1366 + // Stagger via accumulated attack — next voice starts ~17 ms later 1367 + const nextBase = base + (Math.random() - 0.5) * 20; 1368 + sound.synth({ type: "sawtooth", tone: nextBase, duration: 0.012, 1369 + volume: rj(0.50, 0.08) * v * (1 - i * 0.05), 1370 + attack: 0.016 * (i + 1), decay: 0.010, pan }); 1371 + } 1372 + // Species-specific 1 kHz resonance (vocal sac Helmholtz) 1373 + sound.synth({ type: "sine", tone: 1000 * pf, duration: 0.25, volume: rj(0.20, 0.05) * v, attack: 0.002, decay: 0.22, pan }); 1276 1374 break; 1277 1375 } 1278 - case "f#": { // horse neigh — square with whinny vibrato chain 1279 - const base = 440 * pf; 1280 - sound.synth({ type: "square", tone: base, duration: 0.08, volume: rj(0.5, 0.1) * v, attack: 0.002, decay: 0.075, pan }); 1281 - sound.synth({ type: "square", tone: base * 1.15, duration: 0.08, volume: rj(0.45, 0.1) * v, attack: 0.09, decay: 0.075, pan }); 1282 - sound.synth({ type: "square", tone: base * 0.9, duration: 0.08, volume: rj(0.40, 0.1) * v, attack: 0.18, decay: 0.075, pan }); 1283 - sound.synth({ type: "square", tone: base, duration: 0.08, volume: rj(0.35, 0.1) * v, attack: 0.27, decay: 0.075, pan }); 1376 + case "f#": { 1377 + // HORSE WHINNY — Stoeger et al. *Current Biology* 2026: whinnies 1378 + // are BIPHONIC — two independent non-harmonic fundamentals. 1379 + // Voice 1: F0 ~400 Hz, saw with 7 Hz vibrato ±15 %, descending. 1380 + // Voice 2: G0 ~1500 Hz, pure sine aerodynamic whistle (horse 1381 + // literally whistles through voice box), parallel descent. 1382 + // Overlap ~80%, stagger attacks by ~150 ms. 1383 + const f0 = 400 * pf, g0 = 1500 * pf; 1384 + // Voice 1 — biphonic low with vibrato (3 vibrato cycles) 1385 + for (let i = 0; i < 5; i++) { 1386 + const vibMul = (i & 1) ? 1.15 : 0.85; 1387 + sound.synth({ type: "sawtooth", tone: f0 * vibMul * (1 - i * 0.04), duration: 0.18, 1388 + volume: rj(0.55, 0.06) * v * (1 - i * 0.06), 1389 + attack: 0.05 + i * 0.14, decay: 0.15, pan }); 1390 + } 1391 + // Voice 2 — high aerodynamic whistle (joins 150 ms later, descends parallel) 1392 + for (let i = 0; i < 4; i++) { 1393 + sound.synth({ type: "sine", tone: g0 * (1 - i * 0.08), duration: 0.22, 1394 + volume: rj(0.35, 0.06) * v * (1 - i * 0.10), 1395 + attack: 0.15 + i * 0.18, decay: 0.20, pan }); 1396 + } 1284 1397 break; 1285 1398 } 1286 - case "g#": { // snake hiss — filtered noise, sustained 1287 - const params = { type: "noise", tone: 2000 * pf, volume: rj(0.45, 0.08) * v, attack: 0.04, decay: 0.2, pan }; 1288 - fire(params, 0.9, 0.9); 1399 + case "g#": { 1400 + // SNAKE HISS — Aubret & Mangin: vipers hiss low (200-400 Hz 1401 + // BPF), grass/colubrid snakes hiss high (5-10 kHz). "Cartoon" 1402 + // cobra hiss combines both. Slow 0.5 Hz amplitude wobble for 1403 + // breath rhythm. Sustained while held. 1404 + const low = { type: "noise", tone: 300 * pf, volume: rj(0.40, 0.06) * v, attack: 0.08, decay: 0.2, pan }; 1405 + const high = { type: "noise", tone: 6000 * pf, volume: rj(0.22, 0.05) * v, attack: 0.12, decay: 0.3, pan }; 1406 + fire(low, 1.2, 1.2); 1407 + fire(high, 1.2, 1.2); 1289 1408 break; 1290 1409 } 1291 - case "a#": { // whale song — slow low glide 1292 - const base = 110 * pf; 1293 - sound.synth({ type: "sine", tone: base, duration: 0.8, volume: rj(0.55, 0.08) * v, attack: 0.3, decay: 0.5, pan }); 1294 - sound.synth({ type: "sine", tone: base * 0.7, duration: 0.9, volume: rj(0.45, 0.08) * v, attack: 0.4, decay: 0.5, pan }); 1295 - sound.synth({ type: "sine", tone: base * 1.4, duration: 0.8, volume: rj(0.35, 0.08) * v, attack: 0.5, decay: 0.3, pan }); 1410 + case "a#": { 1411 + // WHALE SONG — Cazau additive-resynthesis approach: multiple 1412 + // sines with slow FM (1-3 Hz vibrato) + long glide 600 → 1000 1413 + // → 400 Hz over 4 s. Long reverb tail would help but not in 1414 + // this kit. Approximate with 3 sine stages tracing the glide 1415 + // + a lower octave drone. 1416 + const p1 = 600 * pf, p2 = 1000 * pf, p3 = 400 * pf; 1417 + sound.synth({ type: "sine", tone: p1, duration: 1.4, volume: rj(0.60, 0.06) * v, attack: 0.4, decay: 0.9, pan }); 1418 + sound.synth({ type: "sine", tone: p2, duration: 1.4, volume: rj(0.55, 0.06) * v, attack: 1.3, decay: 0.9, pan }); 1419 + sound.synth({ type: "sine", tone: p3, duration: 1.4, volume: rj(0.50, 0.06) * v, attack: 2.4, decay: 1.1, pan }); 1420 + // Lower-octave drone for sub presence 1421 + sound.synth({ type: "sine", tone: p1 * 0.5, duration: 3.0, volume: rj(0.25, 0.05) * v, attack: 0.6, decay: 2.2, pan }); 1422 + // Subtle 2 Hz tremolo approximated by mid-call amplitude voice 1423 + sound.synth({ type: "sine", tone: p1 * 1.01, duration: 2.5, volume: rj(0.20, 0.05) * v, attack: 1.0, decay: 1.5, pan }); 1296 1424 break; 1297 1425 } 1298 1426 } ··· 1376 1504 } 1377 1505 }; 1378 1506 switch (letter) { 1379 - case "c": { // pew — fast descending sine (Atari Asteroids) 1380 - sound.synth({ type: "sine", tone: 3200 * pf, duration: 0.03, volume: rj(0.75, 0.1) * v, attack: 0.0005, decay: 0.028, pan }); 1381 - sound.synth({ type: "sine", tone: 900 * pf, duration: 0.06, volume: rj(0.55, 0.1) * v, attack: 0.02, decay: 0.055, pan }); 1382 - sound.synth({ type: "sine", tone: 300 * pf, duration: 0.05, volume: rj(0.45, 0.1) * v, attack: 0.05, decay: 0.045, pan }); 1507 + case "c": { 1508 + // PEW (Asteroids) — 555 astable recipe: square wave with 1509 + // simultaneous pitch + amplitude decay τ≈30 ms, start ~1000 Hz, 1510 + // end ~200 Hz. sfxr's laserShoot uses the same shape. 4 stages 1511 + // approximate the exp decay. 1512 + for (let i = 0; i < 4; i++) { 1513 + const t = i / 3; 1514 + const tone = 1000 * Math.exp(-t * 1.6) * pf; // 1000 → 200 Hz 1515 + sound.synth({ type: "square", tone, duration: 0.035, 1516 + volume: rj(0.70 * Math.exp(-t * 1.3), 0.08) * v, 1517 + attack: 0.0005 + t * 0.028, decay: 0.032, pan }); 1518 + } 1383 1519 break; 1384 1520 } 1385 - case "d": { // blast — saw descending with noise crack (Star Wars) 1386 - sound.synth({ type: "sawtooth", tone: 300 * pf, duration: 0.04, volume: rj(0.80, 0.08) * v, attack: 0.001, decay: 0.038, pan }); 1387 - sound.synth({ type: "sawtooth", tone: 100 * pf, duration: 0.08, volume: rj(0.70, 0.08) * v, attack: 0.03, decay: 0.07, pan }); 1388 - sound.synth({ type: "noise", tone: 3500 * pf, duration: 0.02, volume: rj(0.50, 0.1) * v, attack: 0.0005, decay: 0.018, pan }); 1521 + case "d": { 1522 + // BLAST (Star Wars) — Ben Burtt guy-wire recording. The trick is 1523 + // dispersive delay: high frequencies arrive first, then body. 1524 + // Voice 1: square ping sweeping 3000 → 800 Hz in ~40 ms (metallic). 1525 + // Voice 2: BPF-noise at ~500 Hz Q=3 (body punch), 30 ms. Voice 3: 1526 + // comb-style repeated short pings (poor man's flanger) give the 1527 + // "peee-uuu" after-glow. 1528 + sound.synth({ type: "square", tone: 3000 * pf, duration: 0.012, volume: rj(0.70, 0.08) * v, attack: 0.001, decay: 0.011, pan }); 1529 + sound.synth({ type: "square", tone: 1600 * pf, duration: 0.018, volume: rj(0.60, 0.08) * v, attack: 0.008, decay: 0.016, pan }); 1530 + sound.synth({ type: "square", tone: 800 * pf, duration: 0.050, volume: rj(0.55, 0.08) * v, attack: 0.022, decay: 0.045, pan }); 1531 + // Body punch — narrow-band noise around 500 Hz 1532 + sound.synth({ type: "noise", tone: 500 * pf, duration: 0.04, volume: rj(0.40, 0.08) * v, attack: 0.003, decay: 0.035, pan }); 1533 + // "Comb filter" trailing ping — single echo at 8 ms 1534 + sound.synth({ type: "square", tone: 1200 * pf, duration: 0.03, volume: rj(0.25, 0.06) * v, attack: 0.060, decay: 0.025, pan }); 1389 1535 break; 1390 1536 } 1391 - case "e": { // phaser — sine + co-modulating noise (War-of-Worlds feedback) 1392 - sound.synth({ type: "sine", tone: 700 * pf, duration: 0.25, volume: rj(0.55, 0.1) * v, attack: 0.005, decay: 0.22, pan }); 1393 - sound.synth({ type: "sine", tone: 480 * pf, duration: 0.25, volume: rj(0.40, 0.1) * v, attack: 0.01, decay: 0.22, pan }); 1394 - sound.synth({ type: "noise", tone: 1400 * pf, duration: 0.25, volume: rj(0.25, 0.1) * v, attack: 0.005, decay: 0.22, pan }); 1537 + case "e": { 1538 + // PHASER (Star Trek) — War-of-the-Worlds theremin heritage, Moog 1539 + // replication: steady sine carrier + pink-noise FM/AM modulation. 1540 + // Approximated here with 4 staggered sine voices at 800-1200 Hz 1541 + // + gently beating slightly-detuned voice for the warble + 1542 + // filtered-noise menace layer. ~1 s sustain. 1543 + sound.synth({ type: "sine", tone: 1000 * pf, duration: 0.45, volume: rj(0.55, 0.06) * v, attack: 0.04, decay: 0.38, pan }); 1544 + sound.synth({ type: "sine", tone: 1007 * pf, duration: 0.45, volume: rj(0.50, 0.06) * v, attack: 0.04, decay: 0.38, pan }); // 7 Hz beat 1545 + sound.synth({ type: "sine", tone: 1200 * pf, duration: 0.35, volume: rj(0.22, 0.05) * v, attack: 0.10, decay: 0.30, pan }); 1546 + // Pink-noise FM stand-in: band-limited noise at 300 Hz 1547 + sound.synth({ type: "noise", tone: 300 * pf, duration: 0.50, volume: rj(0.18, 0.05) * v, attack: 0.06, decay: 0.44, pan }); 1395 1548 break; 1396 1549 } 1397 - case "f": { // cannon — descending saw + sub thump 1398 - sound.synth({ type: "sawtooth", tone: 700 * pf, duration: 0.05, volume: rj(0.70, 0.08) * v, attack: 0.001, decay: 0.045, pan }); 1399 - sound.synth({ type: "sawtooth", tone: 200 * pf, duration: 0.10, volume: rj(0.80, 0.08) * v, attack: 0.03, decay: 0.09, pan }); 1400 - sound.synth({ type: "sine", tone: 55 * pf, duration: 0.20, volume: rj(0.90, 0.08) * v, attack: 0.005, decay: 0.19, pan }); 1550 + case "f": { 1551 + // CANNON — sfxr explosion preset + low-end layer. Sub-thump 1552 + // (sine 80 → 40 Hz in 60 ms, huge punch), noise body (LPF sweep 1553 + // 3000 → 200 Hz over ~500 ms), crack layer (HPF-noise 20 ms burst). 1554 + // Sub thump sweep 1555 + sound.synth({ type: "sine", tone: 80 * pf, duration: 0.03, volume: rj(0.95, 0.05) * v, attack: 0.001, decay: 0.028, pan }); 1556 + sound.synth({ type: "sine", tone: 55 * pf, duration: 0.08, volume: rj(0.90, 0.05) * v, attack: 0.020, decay: 0.075, pan }); 1557 + sound.synth({ type: "sine", tone: 40 * pf, duration: 0.18, volume: rj(0.75, 0.05) * v, attack: 0.040, decay: 0.16, pan }); 1558 + // Noise body — staged to approximate LPF sweep (high freqs fade first) 1559 + sound.synth({ type: "noise", tone: 3000 * pf, duration: 0.05, volume: rj(0.55, 0.08) * v, attack: 0.001, decay: 0.045, pan }); 1560 + sound.synth({ type: "noise", tone: 1200 * pf, duration: 0.15, volume: rj(0.50, 0.08) * v, attack: 0.04, decay: 0.13, pan }); 1561 + sound.synth({ type: "noise", tone: 400 * pf, duration: 0.35, volume: rj(0.35, 0.08) * v, attack: 0.15, decay: 0.30, pan }); 1562 + // Crack at attack 1563 + sound.synth({ type: "noise", tone: 4000 * pf, duration: 0.015, volume: rj(0.50, 0.1) * v, attack: 0.0005, decay: 0.013, pan }); 1401 1564 break; 1402 1565 } 1403 - case "g": { // stun — buzzing ring-mod-ish square with slight wobble 1404 - sound.synth({ type: "square", tone: 400 * pf, duration: 0.38, volume: rj(0.35, 0.1) * v, attack: 0.01, decay: 0.35, pan }); 1405 - sound.synth({ type: "square", tone: 420 * pf, duration: 0.38, volume: rj(0.30, 0.1) * v, attack: 0.02, decay: 0.35, pan }); 1566 + case "g": { 1567 + // STUN / zap — sfxr hitHurt style: square with slide, plus a 1568 + // fake ring-mod via beating two squares 45 Hz apart. Sharp 1569 + // downward slide is the canonical zap character. 1570 + sound.synth({ type: "square", tone: 900 * pf, duration: 0.05, volume: rj(0.55, 0.08) * v, attack: 0.0005, decay: 0.045, pan }); 1571 + sound.synth({ type: "square", tone: 500 * pf, duration: 0.09, volume: rj(0.50, 0.08) * v, attack: 0.03, decay: 0.08, pan }); 1572 + sound.synth({ type: "square", tone: 300 * pf, duration: 0.12, volume: rj(0.40, 0.08) * v, attack: 0.08, decay: 0.10, pan }); 1573 + // Ring-mod stand-in: second square 45 Hz higher = buzz 1574 + sound.synth({ type: "square", tone: 345 * pf, duration: 0.12, volume: rj(0.30, 0.08) * v, attack: 0.09, decay: 0.10, pan }); 1406 1575 break; 1407 1576 } 1408 - case "a": { // plasma — slow vibrato square 1577 + case "a": { 1578 + // PLASMA — "electric hum" — detuned-square stack with slight 1579 + // vibrato. Less harsh than stun, more sustained. Three voices 1580 + // with ±6% detune spread + low-pass-ish harmonic (triangle at octave). 1409 1581 const base = 260 * pf; 1410 - sound.synth({ type: "square", tone: base, duration: 0.48, volume: rj(0.45, 0.08) * v, attack: 0.02, decay: 0.44, pan }); 1411 - sound.synth({ type: "square", tone: base * 1.06, duration: 0.48, volume: rj(0.38, 0.08) * v, attack: 0.05, decay: 0.44, pan }); 1412 - sound.synth({ type: "square", tone: base * 0.94, duration: 0.48, volume: rj(0.32, 0.08) * v, attack: 0.08, decay: 0.44, pan }); 1582 + sound.synth({ type: "square", tone: base, duration: 0.45, volume: rj(0.45, 0.08) * v, attack: 0.02, decay: 0.40, pan }); 1583 + sound.synth({ type: "square", tone: base * 1.06, duration: 0.45, volume: rj(0.38, 0.08) * v, attack: 0.05, decay: 0.40, pan }); 1584 + sound.synth({ type: "square", tone: base * 0.94, duration: 0.45, volume: rj(0.32, 0.08) * v, attack: 0.08, decay: 0.40, pan }); 1585 + // Soft upper octave for plasma "glow" 1586 + sound.synth({ type: "triangle", tone: base * 2, duration: 0.45, volume: rj(0.18, 0.05) * v, attack: 0.04, decay: 0.40, pan }); 1413 1587 break; 1414 1588 } 1415 - case "b": { // disruptor — gritty saw + noise body 1416 - sound.synth({ type: "sawtooth", tone: 520 * pf, duration: 0.33, volume: rj(0.55, 0.1) * v, attack: 0.003, decay: 0.32, pan }); 1417 - sound.synth({ type: "noise", tone: 800 * pf, duration: 0.33, volume: rj(0.30, 0.1) * v, attack: 0.01, decay: 0.32, pan }); 1589 + case "b": { 1590 + // DISRUPTOR — aggressive saw + pitched noise body + sub octave. 1591 + // Blaster/cannon hybrid — stays pitched but breathier. 1592 + sound.synth({ type: "sawtooth", tone: 520 * pf, duration: 0.30, volume: rj(0.55, 0.08) * v, attack: 0.003, decay: 0.28, pan }); 1593 + sound.synth({ type: "sawtooth", tone: 260 * pf, duration: 0.35, volume: rj(0.45, 0.08) * v, attack: 0.008, decay: 0.33, pan }); 1594 + sound.synth({ type: "noise", tone: 800 * pf, duration: 0.30, volume: rj(0.28, 0.06) * v, attack: 0.01, decay: 0.28, pan }); 1595 + // Crack attack 1596 + sound.synth({ type: "noise", tone: 2500 * pf, duration: 0.02, volume: rj(0.35, 0.08) * v, attack: 0.001, decay: 0.018, pan }); 1418 1597 break; 1419 1598 } 1420 - case "c#": { // charge-up — exp pitch rise 1599 + case "c#": { 1600 + // CHARGE-UP — exp pitch rise, classic sci-fi laser charge. 1601 + // 6 stages, 100 Hz → 2500 Hz over 750 ms. 1421 1602 for (let i = 0; i < 6; i++) { 1422 1603 const t = i / 5; 1423 1604 const tone = 100 * Math.pow(25, t) * pf; 1424 - sound.synth({ type: "sine", tone, duration: 0.2, volume: rj(0.45 - t * 0.1, 0.08) * v, attack: 0.12 * i, decay: 0.18, pan }); 1605 + sound.synth({ type: "sine", tone, duration: 0.2, 1606 + volume: rj(0.45 - t * 0.1, 0.08) * v, 1607 + attack: 0.12 * i, decay: 0.18, pan }); 1425 1608 } 1609 + // Release pop at top 1610 + sound.synth({ type: "square", tone: 3000 * pf, duration: 0.04, volume: rj(0.30, 0.08) * v, attack: 0.72, decay: 0.035, pan }); 1426 1611 break; 1427 1612 } 1428 - case "d#": { // beam — sustained buzz 1613 + case "d#": { 1614 + // BEAM — sustained buzz, held while key is down. Square at 820 1615 + // with a perfect-fifth under it. Mild detune voice for life. 1429 1616 const params = { type: "square", tone: 820 * pf, volume: rj(0.40, 0.06) * v, attack: 0.02, decay: 0.12, pan }; 1430 1617 fire(params, 0.35); 1431 - sound.synth({ type: "square", tone: 410 * pf, duration: 0.3, volume: rj(0.30, 0.06) * v, attack: 0.02, decay: 0.28, pan }); 1618 + sound.synth({ type: "square", tone: 546 * pf, duration: 0.30, volume: rj(0.30, 0.06) * v, attack: 0.02, decay: 0.28, pan }); 1619 + sound.synth({ type: "square", tone: 826 * pf, duration: 0.30, volume: rj(0.20, 0.05) * v, attack: 0.03, decay: 0.28, pan }); 1432 1620 break; 1433 1621 } 1434 - case "f#": { // hit — noise crack + sub thump 1435 - sound.synth({ type: "noise", tone: 3000 * pf, duration: 0.05, volume: rj(0.60, 0.1) * v, attack: 0.0005, decay: 0.045, pan }); 1436 - sound.synth({ type: "sine", tone: 60 * pf, duration: 0.25, volume: rj(0.75, 0.08) * v, attack: 0.005, decay: 0.24, pan }); 1437 - sound.synth({ type: "sawtooth", tone: 120 * pf, duration: 0.08, volume: rj(0.35, 0.1) * v, attack: 0.01, decay: 0.07, pan }); 1622 + case "f#": { 1623 + // HIT — impact. Same structure as sfxr explosion but short: 1624 + // crack + sub thump. Perfect for "enemy got hit" UI sounds. 1625 + sound.synth({ type: "noise", tone: 3500 * pf, duration: 0.04, volume: rj(0.60, 0.08) * v, attack: 0.0005, decay: 0.035, pan }); 1626 + sound.synth({ type: "sine", tone: 80 * pf, duration: 0.15, volume: rj(0.85, 0.06) * v, attack: 0.005, decay: 0.14, pan }); 1627 + sound.synth({ type: "sine", tone: 50 * pf, duration: 0.25, volume: rj(0.70, 0.06) * v, attack: 0.02, decay: 0.22, pan }); 1628 + sound.synth({ type: "sawtooth", tone: 140 * pf, duration: 0.06, volume: rj(0.35, 0.08) * v, attack: 0.008, decay: 0.055, pan }); 1438 1629 break; 1439 1630 } 1440 - case "g#": { // ricochet — descending filtered noise ping 1441 - sound.synth({ type: "noise", tone: 5500 * pf, duration: 0.04, volume: rj(0.45, 0.08) * v, attack: 0.001, decay: 0.038, pan }); 1442 - sound.synth({ type: "noise", tone: 3000 * pf, duration: 0.06, volume: rj(0.35, 0.08) * v, attack: 0.04, decay: 0.055, pan }); 1443 - sound.synth({ type: "sine", tone: 3000 * pf, duration: 0.08, volume: rj(0.30, 0.1) * v, attack: 0.02, decay: 0.075, pan }); 1631 + case "g#": { 1632 + // RICOCHET — descending filtered-noise pings with a sine tail 1633 + // (the "whiiizz-bing" bouncing off metal). 3 stages: initial 1634 + // impact click, mid-band noise smear, high sine ping. 1635 + sound.synth({ type: "noise", tone: 5500 * pf, duration: 0.025, volume: rj(0.50, 0.08) * v, attack: 0.001, decay: 0.023, pan }); 1636 + sound.synth({ type: "noise", tone: 2800 * pf, duration: 0.05, volume: rj(0.35, 0.08) * v, attack: 0.020, decay: 0.045, pan }); 1637 + // Dispersive "bing" tail — sine sweep 3000 → 2200 Hz in 80 ms 1638 + sound.synth({ type: "sine", tone: 3000 * pf, duration: 0.04, volume: rj(0.38, 0.08) * v, attack: 0.04, decay: 0.035, pan }); 1639 + sound.synth({ type: "sine", tone: 2200 * pf, duration: 0.06, volume: rj(0.28, 0.08) * v, attack: 0.08, decay: 0.055, pan }); 1444 1640 break; 1445 1641 } 1446 - case "a#": { // warp — fast sine sweep down (Doppler) 1642 + case "a#": { 1643 + // WARP / JUMP — Star Trek TOS: rising brown-noise HPF sweep + 1644 + // rising sine carrier, culminating in a sfxr-style "bang" at 1645 + // the top (hyperspace jump). Classic warp was test-oscillator- 1646 + // through-plate-reverb; we approximate with layered rising voices 1647 + // + a final descending pop. 1648 + // Low drone rising (sine 100 → 1000 Hz over ~1.5 s) 1447 1649 for (let i = 0; i < 5; i++) { 1448 1650 const t = i / 4; 1449 - const tone = (1800 * Math.pow(0.1, t)) * pf; 1450 - sound.synth({ type: "sine", tone, duration: 0.12, volume: rj(0.55 - t * 0.1, 0.06) * v, attack: 0.08 * i, decay: 0.1, pan }); 1651 + const tone = 100 * Math.pow(10, t) * pf; 1652 + sound.synth({ type: "sine", tone, duration: 0.35, 1653 + volume: rj(0.45 + t * 0.10, 0.06) * v, 1654 + attack: 0.20 * i, decay: 0.25, pan }); 1451 1655 } 1656 + // Brown-noise HPF sweep (rising band center) 1657 + sound.synth({ type: "noise", tone: 200 * pf, duration: 0.40, volume: rj(0.22, 0.05) * v, attack: 0.10, decay: 0.35, pan }); 1658 + sound.synth({ type: "noise", tone: 800 * pf, duration: 0.40, volume: rj(0.18, 0.05) * v, attack: 0.50, decay: 0.35, pan }); 1659 + sound.synth({ type: "noise", tone: 2500 * pf, duration: 0.25, volume: rj(0.14, 0.05) * v, attack: 0.95, decay: 0.22, pan }); 1660 + // Climax pop into hyperspace — short descending square burst 1661 + sound.synth({ type: "square", tone: 2000 * pf, duration: 0.03, volume: rj(0.45, 0.08) * v, attack: 1.15, decay: 0.028, pan }); 1662 + sound.synth({ type: "square", tone: 500 * pf, duration: 0.06, volume: rj(0.35, 0.08) * v, attack: 1.18, decay: 0.055, pan }); 1452 1663 break; 1453 1664 } 1454 1665 }