Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

native: +4dB peak (soft_clip knee 0.85, 32k multiplier); speaker.mjs loudness test

Squeeze more peak volume out of the tiny Chromebook speakers:
- soft_clip knee raised from 0.6 → 0.85 (less compression headroom
wasted)
- int16 multiplier 26000 → 32000 (~+2dB peak)
- default system_volume 150 → 180
- combined ~4dB louder before audible distortion

speaker.mjs enhancements for testing loudness:
- 'm' key plays a tone through the main audio path (same as
notepat) — real volume test vs testPcm side-channel
- 's' key sweeps 20% → 100% to hear where clipping starts
- volume list extended to include 100%
- frequency list extended (100 Hz - 3520 Hz)

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

+66 -21
+16 -9
fedac/native/src/audio.c
··· 1030 1030 // Soft clamp (tanh-style) to prevent harsh digital clipping 1031 1031 // Smooth curve: starts compressing gently above 0.6, hard-limits at ~0.95 1032 1032 static inline double soft_clip(double x) { 1033 - if (x > 0.6) { 1034 - double over = x - 0.6; 1035 - return 0.6 + 0.35 * (1.0 - 1.0 / (1.0 + over * 2.5)); 1033 + /* Higher knee (0.85) keeps more headroom before compression kicks 1034 + * in, producing louder audible output on tiny Chromebook speakers 1035 + * without audibly harsh distortion. */ 1036 + if (x > 0.85) { 1037 + double over = x - 0.85; 1038 + return 0.85 + 0.15 * (1.0 - 1.0 / (1.0 + over * 4.0)); 1036 1039 } 1037 - if (x < -0.6) { 1038 - double over = -x - 0.6; 1039 - return -0.6 - 0.35 * (1.0 - 1.0 / (1.0 + over * 2.5)); 1040 + if (x < -0.85) { 1041 + double over = -x - 0.85; 1042 + return -0.85 - 0.15 * (1.0 - 1.0 / (1.0 + over * 4.0)); 1040 1043 } 1041 1044 return x; 1042 1045 } ··· 1495 1498 mix_l = soft_clip(mix_l); 1496 1499 mix_r = soft_clip(mix_r); 1497 1500 1498 - buffer[i * 2] = (int16_t)(mix_l * 26000); 1499 - buffer[i * 2 + 1] = (int16_t)(mix_r * 26000); 1501 + /* 32000 = ~97% of int16 max. Was 26000 (~79%), wasting 1502 + * ~2dB of peak output. Combined with higher soft_clip 1503 + * knee this gives ~4dB more audible loudness before any 1504 + * compression artifacts. */ 1505 + buffer[i * 2] = (int16_t)(mix_l * 32000); 1506 + buffer[i * 2 + 1] = (int16_t)(mix_r * 32000); 1500 1507 1501 1508 /* DAPM keepalive: inject ±1 LSB when the buffer would 1502 1509 * otherwise be all zeros. At S32_LE (after <<16) this ··· 2485 2492 // pushing soft_clip into audible distortion. Volume keys still go 2486 2493 // up to 400% for very quiet hardware. 2487 2494 int hw_vol = read_system_volume_card(card_idx); 2488 - audio->system_volume = (hw_vol >= 0) ? hw_vol : 150; 2495 + audio->system_volume = (hw_vol >= 0) ? hw_vol : 180; 2489 2496 fprintf(stderr, "[audio] System volume: %d%% (hw=%d)\n", 2490 2497 audio->system_volume, hw_vol); 2491 2498
+50 -12
system/public/aesthetic.computer/disks/speaker.mjs
··· 16 16 let selected = 0; 17 17 let lastTone = { device: "", freq: 0, ts: 0 }; 18 18 let activeDevice = ""; 19 - let freqs = [200, 440, 880, 1760]; 20 - let freqIdx = 1; // 440 Hz by default 21 - let volumes = [0.1, 0.3, 0.5, 0.8]; 22 - let volIdx = 1; // 30% 23 - let durationsMs = [200, 500, 1000, 2000]; 19 + let freqs = [100, 200, 440, 880, 1760, 3520]; 20 + let freqIdx = 2; // 440 Hz by default 21 + let volumes = [0.1, 0.3, 0.5, 0.8, 1.0]; 22 + let volIdx = 3; // 80% — actually audible 23 + let durationsMs = [200, 500, 1000, 2000, 5000]; 24 24 let durIdx = 1; // 500 ms 25 + let lastMainTone = { freq: 0, vol: 0, ts: 0 }; 25 26 26 27 function boot({ system }) { 27 28 refreshPcms(system); ··· 92 93 const vol = volumes[volIdx]; 93 94 const ok = system?.audio?.testPcm?.(p.device, freq, dur, vol); 94 95 lastTone = { device: p.device, freq, ts: Date.now(), ok }; 95 - // Gentle feedback chirp via the main audio path so the user knows the 96 - // keypress registered even if this particular PCM is silent. 97 - sound?.synth?.({ type: "sine", tone: 660, duration: 0.04, 98 - volume: 0.08, attack: 0.002, decay: 0.03 }); 96 + return; 97 + } 98 + 99 + // 'm' = main audio path test (same path notepat uses). 100 + // This is the real volume test — if 'm' is too quiet but space on 101 + // hw:0,0 is loud, the main audio code has wrong gain settings. 102 + if (e.is("keyboard:down:m")) { 103 + const freq = freqs[freqIdx]; 104 + const dur = durationsMs[durIdx] * 0.001; 105 + const vol = volumes[volIdx]; 106 + sound?.synth?.({ type: "sine", tone: freq, duration: dur, 107 + volume: vol, attack: 0.002, decay: Math.max(0.05, dur * 0.3) }); 108 + lastMainTone = { freq, vol, ts: Date.now() }; 109 + return; 110 + } 111 + 112 + // 's' = sweep — ramps volume from 0→100% to hear max clean loudness. 113 + if (e.is("keyboard:down:s")) { 114 + const freq = freqs[freqIdx]; 115 + const steps = 5; 116 + for (let i = 0; i < steps; i++) { 117 + const v = ((i + 1) / steps); 118 + setTimeout(() => { 119 + sound?.synth?.({ type: "sine", tone: freq, duration: 0.3, 120 + volume: v, attack: 0.01, decay: 0.15 }); 121 + }, i * 350); 122 + } 123 + lastMainTone = { freq, vol: 1.0, ts: Date.now() }; 99 124 return; 100 125 } 101 126 } ··· 157 182 } 158 183 } 159 184 160 - // Key hints 185 + // Last main tone status (the "real" volume test — sound.synth goes 186 + // through the same audio path notepat uses). 187 + if (lastMainTone.ts) { 188 + const sinceMs = Date.now() - lastMainTone.ts; 189 + if (sinceMs < 3000) { 190 + ink(255, 200, 100); 191 + write(`M ${lastMainTone.freq} Hz @ vol ${Math.round(lastMainTone.vol * 100)}%`, 192 + { x: pad, y: h - 32, size: 1, font }); 193 + } 194 + } 195 + 196 + // Key hints (two lines because we added m/s) 161 197 ink(120, 140, 120); 162 - write("↑↓ select space play f freq v vol d dur r refresh esc back", 163 - { x: pad, y: h - 12, size: 1, font }); 198 + write("↑↓ select space play on PCM f freq v vol d dur", 199 + { x: pad, y: h - 20, size: 1, font }); 200 + write("m = main audio test s = sweep 20→100% r refresh esc back", 201 + { x: pad, y: h - 10, size: 1, font }); 164 202 } 165 203 166 204 export { boot, paint, act };