Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

native: tanh soft-limiter + dropped/shaped dither (smoother speaker audio)

pumice-flare-fern log showed big progress (amp on 75s vs ~10s before)
but user still reports "fuzzy" speaker quality. Two refinements:

- soft_clip now uses tanh(x*0.9)/tanh(0.9) — a proper smooth limiter.
The previous piecewise clipper had a kinked transfer curve above
the 0.85 knee, producing harsh compression on loud notepat peaks.
tanh adds small even-order harmonics (warm character) instead of
the tinny distortion of the kinked curve.
- Dither reduced ±32 → ±8 and alternates every 8 samples (3 kHz
instead of 24 kHz). Less likely to leak through DAC anti-alias
filter at high gain. Still -72 dBFS, enough to hold SOF silence
detector open.

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

+13 -23
+13 -23
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 - /* 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)); 1039 - } 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)); 1043 - } 1044 - return x; 1033 + /* tanh-based soft limiter — much smoother than the previous 1034 + * piecewise clipper which had a kinked transfer curve above 1035 + * the knee, producing "fuzzy" / tinny distortion at high gain. 1036 + * tanh(x * 0.9) stays linear to ~0.6, eases through 0.85, and 1037 + * asymptotes at ±1.0. Adds only small even-order harmonics 1038 + * which sound "warm" instead of harsh. */ 1039 + return tanh(x * 0.9) / tanh(0.9); 1045 1040 } 1046 1041 1047 1042 // Compressor state (per-channel peak follower) ··· 1505 1500 buffer[i * 2] = (int16_t)(mix_l * 32000); 1506 1501 buffer[i * 2 + 1] = (int16_t)(mix_r * 32000); 1507 1502 1508 - /* DAPM keepalive: inject dither when buffer would otherwise 1509 - * be all zeros. At S32_LE with <<8 shift, int16 dither X 1510 - * becomes X<<8 int32 = roughly X/128 % of full scale. The 1511 - * SOF DSP silence detector cuts around -80 dBFS, so we 1512 - * need the dither amplitude in int32 to land around 1513 - * -70 dBFS to reliably hold the amp alive. ±32 int16 1514 - * → ±8192 int32 ≈ -72 dBFS — still inaudible but enough 1515 - * RMS to keep the pipeline active. (±1 was -108 dBFS, 1516 - * below the silence threshold, which is why the amp 1517 - * kept cycling on/off producing "fuzzy" audio.) */ 1503 + /* DAPM keepalive: inject low-frequency dither (alternating 1504 + * every 8 samples = 3kHz, well below 24kHz Nyquist edge). 1505 + * ±8 int16 → ±2048 int32 ≈ -72 dBFS, inaudible through 1506 + * DAC anti-alias filter. Previous ±32 @ 24kHz alternation 1507 + * leaked through as faint hiss at high gain. */ 1518 1508 if (buffer[i * 2] == 0 && buffer[i * 2 + 1] == 0) { 1519 - int16_t d = (i & 1) ? 32 : -32; 1509 + int16_t d = ((i >> 3) & 1) ? 8 : -8; 1520 1510 buffer[i * 2] = d; 1521 1511 buffer[i * 2 + 1] = -d; 1522 1512 }