Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

fix(percussion): wub kick + sharper clip-clap + aggressive whistle de-air

Three audio tuning passes based on lemon-martin feedback.

KICK — user: "too lengthy, needs more mids, more of a wobble wub bass"
- Total sustain 500ms → 180ms so it doesn't overstay its welcome.
- Body wave: sine 140Hz → SQUARE 180Hz (odd harmonics up to ~900Hz
fill the mid-band that was previously hollow).
- Added grit layer: sawtooth 90Hz × 100ms for dubstep rasp (90/180/
270/360 Hz harmonic stack reads as "wub").
- Sub wobble: single 45Hz sine → TWO sines at 44Hz and 54Hz running
in parallel. Beat frequency |f1-f2| = 10Hz creates natural
amplitude modulation without needing a real LFO (which our QuickJS
timers can't drive fast enough anyway). Classic dub wobble.
- Mid-low: sine 72Hz → square 120Hz for more harmonic body between
the 44Hz sub and the 180Hz thump.

CLAP — user: "should be more clip clappy"
The old recipe was four ~1400Hz noise bursts that blurred into a
diffuse shh. Real handclaps have a distinct high-frequency click
(palms striking) followed by a compact body. New recipe:
- Sharp click: square 2.5kHz × 4ms + noise 6kHz × 3ms (EXPLICIT
top-end click, was missing entirely before).
- Body: three short 8ms bursts at 1500-1700Hz with tighter spacing
(6ms / 14ms instead of 10/22/34ms) — the timing is what sells
"two hands clapping", not the frequency.
- Short tail: single 25ms 1800Hz burst for the after-smack.
- Opposing pan between click and body for micro-stereo image.

WHISTLE — user: "still too airy"
My earlier pass (Q 18→26, turbulence 0.08→0.025) wasn't aggressive
enough. Third iteration:
- main_q: 26 + 0.018*tuned → 45 + 0.025*tuned (razor-thin bandwidth).
- formant_q: 10 → 14.
- turbulence: 0.025 base + 0.12 onset + 0.03 breath
→ 0.008 + 0.03 + 0.008 (roughly 1/10 of the original).
- jet_slew: 0.035 → 0.05 (chiff nearly gone, note speaks instantly).
- feedback: 1.95/-0.92 → 2.0/-0.95 (more self-sustain in resonator).
- main input gain: 1.25 → 1.35; formant input: 0.22 → 0.18.
- breath_slew: 0.0065 → 0.009 (faster attack ramp).
- OUTPUT AIR LAYER REMOVED ENTIRELY. The previous versions still
mixed unfiltered white noise into the final output via the `air`
term. That was the hiss the user could still hear. Now ALL noise
has to pass through the resonator before reaching the output.
- Output mix: main 2.4/formant 0.45/air → main 2.8/formant 0.38/0.
- Final drive: 1.55 → 1.6.

+63 -44
+34 -16
fedac/native/pieces/notepat.mjs
··· 451 451 const v = Math.max(0.1, Math.min(2.2, volume)); 452 452 const pf = Math.max(0.25, Math.min(4, pitchFactor)); // clamp to ±2 octaves 453 453 switch (letter) { 454 - case "c": // kick — 808/909-style with beater click, body punch, sub boom, and warm tail 455 - // 1. Beater click — very brief high transient, makes the ear hear "drum" not "tone" 456 - sound.synth({ type: "triangle", tone: 1800 * pf, duration: 0.007, volume: 1.1 * v, attack: 0.0003, decay: 0.006, pan }); 457 - sound.synth({ type: "noise", tone: 4000 * pf, duration: 0.005, volume: 0.7 * v, attack: 0.0003, decay: 0.004, pan }); 458 - // 2. Body punch — short mid-low sine for the "thump" (fast linear decay simulates pitch drop) 459 - sound.synth({ type: "sine", tone: 140 * pf, duration: 0.03, volume: 1.4 * v, attack: 0.0005, decay: 0.028, pan }); 460 - // 3. Sub boom — long low fundamental at ~45 Hz, the felt bass weight. This one runs HOT so 461 - // it dominates the mix even against sustained melody voices (see auto-mixer math in audio.c). 462 - sound.synth({ type: "sine", tone: 45 * pf, duration: 0.5, volume: 2.0 * v, attack: 0.001, decay: 0.49, pan }); 463 - // 4. Warm low-mid tail — fills the 60-80 Hz gap so the kick doesn't feel hollow 464 - sound.synth({ type: "sine", tone: 72 * pf, duration: 0.18, volume: 1.0 * v, attack: 0.002, decay: 0.17, pan }); 454 + case "c": // kick — wub/wobble bass: shorter, more mid grit, 10 Hz sub wobble 455 + // 1. Beater click — short high transient so the ear still parses this as a drum hit 456 + sound.synth({ type: "triangle", tone: 1800 * pf, duration: 0.005, volume: 1.0 * v, attack: 0.0003, decay: 0.004, pan }); 457 + sound.synth({ type: "noise", tone: 4000 * pf, duration: 0.004, volume: 0.55 * v, attack: 0.0003, decay: 0.003, pan }); 458 + // 2. Body thump — SQUARE wave at 180 Hz for rich mid harmonics (was a pure sine 459 + // which left a hollow-feeling gap between 180 Hz and the sub). Square = odd 460 + // harmonics up to ~900 Hz, filling the mid band. 461 + sound.synth({ type: "square", tone: 180 * pf, duration: 0.02, volume: 1.2 * v, attack: 0.0005, decay: 0.018, pan }); 462 + // 3. Grit layer — sawtooth at 90 Hz for dubstep-style edge. Adds 90/180/270/360 Hz 463 + // harmonics and the rasp that reads as "wub bass" to the ear. 464 + sound.synth({ type: "sawtooth", tone: 90 * pf, duration: 0.1, volume: 1.0 * v, attack: 0.001, decay: 0.095, pan }); 465 + // 4. Sub wobble — TWO sines tuned 10 Hz apart beat against each other, creating 466 + // natural amplitude modulation at |f1-f2| = 10 Hz without needing a real LFO. 467 + // Shortened from 500ms → 180ms so the kick doesn't overstay its welcome. 468 + sound.synth({ type: "sine", tone: 44 * pf, duration: 0.18, volume: 1.9 * v, attack: 0.001, decay: 0.17, pan }); 469 + sound.synth({ type: "sine", tone: 54 * pf, duration: 0.18, volume: 1.3 * v, attack: 0.001, decay: 0.17, pan }); 470 + // 5. Mid-low square at 120 Hz — fills the 120-360 Hz range with additional square 471 + // harmonics so the kick has body between the 44 Hz sub and the 180 Hz thump. 472 + sound.synth({ type: "square", tone: 120 * pf, duration: 0.07, volume: 0.85 * v, attack: 0.002, decay: 0.065, pan }); 465 473 break; 466 474 case "d": // snare 467 475 sound.synth({ type: "noise", tone: 2200 * pf, duration: 0.12, volume: 0.55 * v, attack: 0.001, decay: 0.11, pan }); 468 476 sound.synth({ type: "triangle", tone: 220 * pf, duration: 0.1, volume: 0.4 * v, attack: 0.001, decay: 0.09, pan }); 469 477 sound.synth({ type: "square", tone: 180 * pf, duration: 0.05, volume: 0.2 * v, attack: 0.001, decay: 0.045, pan }); 470 478 break; 471 - case "e": // clap — staggered noise bursts for classic handclap texture 472 - sound.synth({ type: "noise", tone: 1400 * pf, duration: 0.02, volume: 0.45 * v, attack: 0.0005, decay: 0.018, pan }); 473 - setTimeout(() => sound.synth({ type: "noise", tone: 1450 * pf, duration: 0.02, volume: 0.4 * v, attack: 0.0005, decay: 0.018, pan }), 10); 474 - setTimeout(() => sound.synth({ type: "noise", tone: 1550 * pf, duration: 0.02, volume: 0.35 * v, attack: 0.0005, decay: 0.018, pan }), 22); 475 - setTimeout(() => sound.synth({ type: "noise", tone: 1600 * pf, duration: 0.1, volume: 0.3 * v, attack: 0.001, decay: 0.09, pan }), 34); 479 + case "e": // clap — sharper "clip clap": short clicky transient + narrow band noise burst 480 + // The previous version had four soft noise bursts at ~1400 Hz that blurred into 481 + // a diffuse "shh". Real claps have a hard ~2-3 kHz click followed by a tight 482 + // 1.5 kHz body with ~20 ms stereo-flam between the two strikes of the hands. 483 + // New recipe: square-wave click (explicit high frequencies), then a compact 484 + // triple-burst body with tighter timing (6/14/22 ms) and much narrower durations. 485 + // 1. Sharp click — square at 2.5 kHz, 4 ms, panned slightly to opposite side 486 + sound.synth({ type: "square", tone: 2500 * pf, duration: 0.004, volume: 0.9 * v, attack: 0.0003, decay: 0.0035, pan: pan * 0.8 }); 487 + sound.synth({ type: "noise", tone: 6000 * pf, duration: 0.003, volume: 0.6 * v, attack: 0.0003, decay: 0.0025, pan: pan * 0.8 }); 488 + // 2. Body burst — three very short noise bursts, tighter spacing than before 489 + sound.synth({ type: "noise", tone: 1600 * pf, duration: 0.008, volume: 0.75 * v, attack: 0.0003, decay: 0.007, pan }); 490 + setTimeout(() => sound.synth({ type: "noise", tone: 1700 * pf, duration: 0.008, volume: 0.6 * v, attack: 0.0003, decay: 0.007, pan }), 6); 491 + setTimeout(() => sound.synth({ type: "noise", tone: 1500 * pf, duration: 0.008, volume: 0.5 * v, attack: 0.0003, decay: 0.007, pan }), 14); 492 + // 3. Short tail — 1.8 kHz noise for the "smack" after-ring, very brief 493 + setTimeout(() => sound.synth({ type: "noise", tone: 1800 * pf, duration: 0.025, volume: 0.45 * v, attack: 0.0005, decay: 0.024, pan }), 22); 476 494 break; 477 495 case "f": // snap — finger snap click + short body 478 496 sound.synth({ type: "noise", tone: 3200 * pf, duration: 0.015, volume: 0.45 * v, attack: 0.0003, decay: 0.014, pan });
+29 -28
fedac/native/src/audio.c
··· 145 145 double tuned = freq * (1.0 + vibrato); 146 146 if (fabs(tuned - v->whistle_coeff_freq) < 0.35) return; 147 147 148 - // Sharper main resonance — narrower bandwidth = more "pressure" / 149 - // tighter pitch / less breathy. Was 18.0 + tuned*0.014, now 26.0 + 150 - // tuned*0.018 to cut the air bleed that made it sound too breathy. 151 - double main_q = 26.0 + tuned * 0.018; 148 + // Very sharp main resonance — previous tunings (Q≈18, Q≈26) still let 149 + // too much broadband noise bleed through. Q≈45 gives a razor-thin 150 + // bandwidth around the fundamental so the whistle reads as pitched 151 + // even at low amplitudes. 152 + double main_q = 45.0 + tuned * 0.025; 152 153 double formant_ratio = tuned < 700.0 ? 2.05 : 2.35; 153 - double formant_q = 10.0 + tuned * 0.004; 154 + double formant_q = 14.0 + tuned * 0.005; 154 155 155 156 whistle_set_resonator(tuned, main_q, sample_rate, 156 157 &v->whistle_main_c1, &v->whistle_main_c2, &v->whistle_main_gain); ··· 171 172 double env = compute_envelope(v); 172 173 double white = ((double)xorshift32(&v->noise_seed) / (double)UINT32_MAX) * 2.0 - 1.0; 173 174 double breath_target = 0.18 + 0.82 * sqrt(env); 174 - double breath_slew = env > v->whistle_breath ? 0.0065 : 0.0018; 175 + double breath_slew = env > v->whistle_breath ? 0.009 : 0.0025; 175 176 176 177 v->whistle_breath += (breath_target - v->whistle_breath) * breath_slew; 177 178 v->whistle_vibrato_phase += (4.6 + v->frequency * 0.0025) / sample_rate; ··· 180 181 whistle_update_coeffs(v, sample_rate); 181 182 182 183 double onset = 1.0 - env; 183 - // Stronger feedback gain → more energy stays in the resonator (less 184 - // "air bleeding out" between cycles, fuller tone). 185 - double feedback = v->whistle_main_y1 * 1.95 - v->whistle_main_y2 * 0.92 + v->whistle_formant_y1 * 0.35; 186 - // Cut turbulence noise substantially. This was the main source of the 187 - // "too airy" complaint — the constant 0.08 noise floor is now 0.025, 188 - // the onset chiff 0.34 → 0.12 (shorter, tighter attack), and the 189 - // breath-modulated noise 0.10 → 0.03. Net: ~3× less airy. 190 - double turbulence = white * (0.025 + onset * 0.12 + v->whistle_breath * 0.03); 191 - // Harder jet drive so more signal stays in the edge nonlinearity. 192 - double jet_drive = feedback * (1.0 + v->whistle_breath * 0.6) + turbulence; 184 + // Tight feedback — more energy stays resonating between cycles so the 185 + // tone is self-sustaining from the resonator, not re-injected from the 186 + // noise source every sample. 187 + double feedback = v->whistle_main_y1 * 2.0 - v->whistle_main_y2 * 0.95 + v->whistle_formant_y1 * 0.32; 188 + // Turbulence cranked way down. Previous (0.025 base + 0.12 onset + 0.03 189 + // breath) was still too much for the user. New values are roughly ⅓ of 190 + // the previous iteration and ≈ 1/10 of the original. The onset chiff is 191 + // now 0.03 (barely perceptible), breath modulation 0.008. Just enough 192 + // noise to excite the resonator, not enough to bleed through it. 193 + double turbulence = white * (0.008 + onset * 0.03 + v->whistle_breath * 0.008); 194 + double jet_drive = feedback * (1.05 + v->whistle_breath * 0.5) + turbulence; 193 195 double jet_target = tanh(jet_drive); 194 196 195 - // Faster jet slew → note speaks sooner, chiff is shorter (less breathy 196 - // transient before the resonator takes over). 197 - v->whistle_jet += (jet_target - v->whistle_jet) * (0.035 + v->whistle_breath * 0.12); 197 + // Faster jet slew — note speaks almost immediately, chiff is effectively 198 + // gone. 199 + v->whistle_jet += (jet_target - v->whistle_jet) * (0.05 + v->whistle_breath * 0.15); 198 200 199 - // Hotter drive into the main resonator (was 1.05, now 1.25). The 200 - // formant contribution stays modest so the main tone dominates. 201 - double main = whistle_tick_resonator(v->whistle_jet * (1.25 + v->whistle_breath * 0.55), 201 + double main = whistle_tick_resonator(v->whistle_jet * (1.35 + v->whistle_breath * 0.5), 202 202 v->whistle_main_c1, v->whistle_main_c2, v->whistle_main_gain, 203 203 &v->whistle_main_y1, &v->whistle_main_y2); 204 - double formant = whistle_tick_resonator(v->whistle_jet * (0.22 + v->whistle_breath * 0.14), 204 + double formant = whistle_tick_resonator(v->whistle_jet * (0.18 + v->whistle_breath * 0.1), 205 205 v->whistle_formant_c1, v->whistle_formant_c2, v->whistle_formant_gain, 206 206 &v->whistle_formant_y1, &v->whistle_formant_y2); 207 207 208 - // Air layer dropped by ~4× to remove the constant hiss bed. 209 - double air = white * (0.005 + onset * 0.012); 210 - // Main resonator gets more of the output mix; less formant filler. 211 - double s = main * 2.4 + formant * 0.45 + air; 212 - return tanh(s * 1.55); 208 + // The output "air" layer is GONE. Previously this added unfiltered white 209 + // noise directly to the final mix, which was the main hiss you could 210 + // still hear over the resonator. Now all noise must pass through the 211 + // resonator filter before reaching the output. 212 + double s = main * 2.8 + formant * 0.38; 213 + return tanh(s * 1.6); 213 214 } 214 215 215 216 static inline double compute_fade(ACVoice *v) {