Monorepo for Aesthetic.Computer aesthetic.computer
at main 3658 lines 165 kB view raw
1// audio.c — ALSA sound engine for ac-native 2// Dedicated audio thread with multi-voice synthesis, envelopes, and effects. 3 4#include "audio.h" 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8#include <math.h> 9#include <time.h> 10#include <dirent.h> 11#include <unistd.h> 12#include <sched.h> 13#include <alsa/asoundlib.h> 14#include <alsa/use-case.h> 15 16// Defined in ac-native.c — writes to USB log and stderr. 17extern void ac_log(const char *fmt, ...); 18 19// Forward declarations 20static int read_system_volume_card(int card); 21 22// ============================================================ 23// Note frequency table (octave 0 base frequencies) 24// ============================================================ 25 26static const struct { const char *name; double freq; } note_table[] = { 27 {"c", 16.3516}, {"cs", 17.3239}, {"db", 17.3239}, 28 {"d", 18.3540}, {"ds", 19.4454}, {"eb", 19.4454}, 29 {"e", 20.6017}, {"f", 21.8268}, {"fs", 23.1247}, 30 {"gb", 23.1247}, {"g", 24.4997}, {"gs", 25.9565}, 31 {"ab", 25.9565}, {"a", 27.5000}, {"as", 29.1352}, 32 {"bb", 29.1352}, {"b", 30.8677}, 33}; 34#define NOTE_TABLE_SIZE (sizeof(note_table) / sizeof(note_table[0])) 35 36double audio_note_to_freq(const char *note) { 37 if (!note || !*note) return 440.0; 38 39 // Try parsing as a number first 40 char *end; 41 double d = strtod(note, &end); 42 if (end != note && *end == '\0') return d; 43 44 // Parse note string: "C4", "4C#", "C#4", "5A", etc. 45 int octave = 4; 46 char name_buf[8] = {0}; 47 int ni = 0; 48 const char *p = note; 49 50 // Check if starts with digit (octave prefix: "4C#") 51 if (*p >= '0' && *p <= '9') { 52 octave = *p - '0'; 53 p++; 54 } 55 56 // Read note name 57 while (*p && ni < 3) { 58 char ch = *p; 59 if (ch >= 'A' && ch <= 'G') ch += 32; // lowercase 60 if ((ch >= 'a' && ch <= 'g') || ch == '#' || ch == 's' || ch == 'b') { 61 // Map 'f' for flat and '#' for sharp 62 if (ch == '#') { name_buf[ni++] = 's'; } 63 else { name_buf[ni++] = ch; } 64 p++; 65 } else break; 66 } 67 name_buf[ni] = '\0'; 68 69 // Trailing octave number 70 if (*p >= '0' && *p <= '9') { 71 octave = *p - '0'; 72 } 73 74 // Lookup base frequency 75 double base = 440.0; // fallback 76 for (int i = 0; i < (int)NOTE_TABLE_SIZE; i++) { 77 if (strcmp(name_buf, note_table[i].name) == 0) { 78 base = note_table[i].freq; 79 break; 80 } 81 } 82 83 return base * pow(2.0, octave); 84} 85 86// ============================================================ 87// Oscillator sample generation 88// ============================================================ 89 90static inline uint32_t xorshift32(uint32_t *state) { 91 uint32_t x = *state; 92 x ^= x << 13; 93 x ^= x >> 17; 94 x ^= x << 5; 95 *state = x; 96 return x; 97} 98 99static inline double clampd(double x, double lo, double hi) { 100 if (x < lo) return lo; 101 if (x > hi) return hi; 102 return x; 103} 104 105static inline double compute_envelope(ACVoice *v) { 106 double env = 1.0; 107 108 // Attack ramp 109 if (v->attack > 0.0 && v->elapsed < v->attack) { 110 env = v->elapsed / v->attack; 111 } 112 113 // Decay (near end of duration) 114 if (!isinf(v->duration) && v->decay > 0.0) { 115 double decay_start = v->duration - v->decay; 116 if (decay_start < 0.0) decay_start = 0.0; 117 if (v->elapsed > decay_start) { 118 double decay_progress = (v->elapsed - decay_start) / v->decay; 119 if (decay_progress > 1.0) decay_progress = 1.0; 120 env *= (1.0 - decay_progress); 121 } 122 } 123 124 return env; 125} 126 127// Fractional-delay read from a ring buffer. `delay` is in samples, allows 128// non-integer values via linear interpolation between adjacent samples. 129// Returns the sample `delay` positions behind the write cursor. 130static inline double whistle_frac_read(const float *buf, int N, int w, double delay) { 131 if (delay < 0.0) delay = 0.0; 132 if (delay > (double)(N - 2)) delay = (double)(N - 2); 133 double rd = (double)w - delay; 134 while (rd < 0.0) rd += (double)N; 135 int i0 = (int)rd; 136 int i1 = (i0 + 1) % N; 137 double f = rd - (double)i0; 138 return (double)buf[i0] * (1.0 - f) + (double)buf[i1] * f; 139} 140 141// Cook/STK digital waveguide flute model. 142// The signal flow (see reports/research for full derivation): 143// 144// breath ──► (+) ──► jetDelay ──► NL(x*(x*x-1)) ──► dcBlock ──► (+) ──► boreDelay ──┬──► out 145// ▲ ▲ │ 146// │ −jetRefl·temp │ +endRefl·temp │ 147// │ │ │ 148// └───────── 1-pole LPF ◄───────────────────────────┴──────────────────┘ 149// 150// The BORE delay line (length = SR/freq) is the primary resonator. Its 151// closed-loop feedback generates ALL harmonics automatically via comb 152// filtering — the delay line is inherently a periodic waveguide that 153// sustains exactly at integer multiples of its natural pitch. 154// 155// The JET delay (length ≈ 0.32 × bore) models the air jet's travel time 156// across the embouchure hole. The cubic nonlinearity x*(x*x-1) has 157// negative-slope region at x=0 which makes it a LIMIT-CYCLE GENERATOR — 158// it converts steady DC breath pressure into sustained oscillation. 159// This is qualitatively different from tanh, which is monotonic and 160// can only saturate. 161// 162// The 1-pole LPF in the loop models bore losses (viscothermal damping) 163// so the tone darkens as harmonics decay faster than the fundamental. 164// 165// The DC blocker after the NL removes the bias the cubic would pump 166// into the bore loop, which would otherwise drive it into clipping. 167static inline double generate_whistle_sample(ACVoice *v, double sample_rate) { 168 double env = compute_envelope(v); 169 // Breath envelope — DC pressure component + noise modulation + vibrato. 170 // CRITICAL: the DC component is what drives the nonlinearity into 171 // self-oscillation. Without a steady DC term, noise alone cannot 172 // sustain the limit cycle. 173 double breath_target = 0.18 + 0.82 * sqrt(env); 174 double breath_slew = env > v->whistle_breath ? 0.012 : 0.003; 175 v->whistle_breath += (breath_target - v->whistle_breath) * breath_slew; 176 177 // Vibrato LFO — ~5 Hz, small depth 178 v->whistle_vibrato_phase += 5.0 / sample_rate; 179 if (v->whistle_vibrato_phase >= 1.0) v->whistle_vibrato_phase -= 1.0; 180 double vibrato = sin(2.0 * M_PI * v->whistle_vibrato_phase) * 0.03; 181 182 // Breath noise — multiplicatively modulates the DC breath pressure. 183 // Low gain so the noise rides on top of the steady breath instead of 184 // replacing it. Attack phase gets slightly more chiff. 185 double white = ((double)xorshift32(&v->noise_seed) / (double)UINT32_MAX) * 2.0 - 1.0; 186 double onset = 1.0 - env; 187 double noise_gain = 0.08 + 0.05 * onset; 188 double breath = v->whistle_breath * (1.0 + noise_gain * white + vibrato); 189 190 // Bore and jet delay lengths — bore = SR/freq (one wavelength), 191 // jet = 0.32 × bore (Cook's flute ratio; 0.45 for pennywhistle, 192 // 0.5 for ocarina). Clamp to the delay buffer sizes. 193 double freq = clampd(v->frequency, 110.0, sample_rate * 0.20); 194 double bore_delay = sample_rate / freq; 195 double jet_delay = bore_delay * 0.32; 196 // Cap to buffer sizes with safety margin 197 const int BORE_N = 2048; 198 const int JET_N = 512; 199 if (bore_delay > (double)(BORE_N - 2)) bore_delay = (double)(BORE_N - 2); 200 if (jet_delay > (double)(JET_N - 2)) jet_delay = (double)(JET_N - 2); 201 202 // Read bore output and apply 1-pole loop LPF (models bore damping). 203 // 0.35/0.65 coefficients give ~0.65 DC gain — closes the loop just 204 // under unity so it sustains but doesn't blow up. The LPF rolls off 205 // high harmonics so the tone darkens naturally, unlike a biquad 206 // which would over-narrow the spectrum. 207 double bore_out = whistle_frac_read(v->whistle_bore_buf, BORE_N, v->whistle_bore_w, bore_delay); 208 v->whistle_lp1 = 0.35 * (-bore_out) + 0.65 * v->whistle_lp1; 209 double temp = v->whistle_lp1; 210 211 // Jet drive: breath pressure minus jet reflection from bore feedback 212 double jet_refl = 0.5; 213 double end_refl = 0.5; 214 double pd = breath - jet_refl * temp; 215 216 // Write to jet delay, read back with fractional delay 217 v->whistle_jet_buf[v->whistle_jet_w] = (float)pd; 218 v->whistle_jet_w = (v->whistle_jet_w + 1) % JET_N; 219 pd = whistle_frac_read(v->whistle_jet_buf, JET_N, v->whistle_jet_w, jet_delay); 220 221 // THE CUBIC NONLINEARITY — y = x*(x*x - 1). Negative slope at x=0 222 // creates a limit-cycle generator. This is the secret sauce that 223 // makes the tone WHISTLE instead of being filtered noise. 224 pd = pd * (pd * pd - 1.0); 225 if (pd > 1.0) pd = 1.0; 226 if (pd < -1.0) pd = -1.0; 227 228 // 1-pole DC blocker — removes the bias the cubic pumps into the loop. 229 // y[n] = x[n] - x[n-1] + 0.995*y[n-1] 230 double y = pd - v->whistle_hp_x1 + 0.995 * v->whistle_hp_y1; 231 v->whistle_hp_x1 = pd; 232 v->whistle_hp_y1 = y; 233 234 // Close the bore loop: combine the NL-filtered jet output with the 235 // end reflection from the bore delay. 236 double into_bore = y + end_refl * temp; 237 v->whistle_bore_buf[v->whistle_bore_w] = (float)into_bore; 238 v->whistle_bore_w = (v->whistle_bore_w + 1) % BORE_N; 239 240 // Output is a tap off the bore loop. 0.3 gain matches STK Flute. 241 return 0.3 * into_bore; 242} 243 244// ============================================================ 245// Gun synthesis — two models per preset 246// ============================================================ 247// 248// CLASSIC (default) — three-layer kick+snare-style synthesis: 249// 250// noise ──► BPF (mid-Q, ~2-6kHz) ──► amp_env(crack) ──┐ 251// │ 252// sin/tri ──► pitch_sweep(start→end) ──► amp_env(boom)─┼─► sum ──► out 253// │ 254// noise ──► LPF (low-Q, ~200-2000Hz) ──► env(attack+decay)─┘ 255// 256// • crack: instantaneous transient, exp decay 5-30 ms 257// • boom: pitched sine/triangle with fast downward sweep 258// (~250→40 Hz over 30-100 ms), exp amp decay 259// • tail: noisy residual rumble, optional linear attack ramp, 260// exp decay 100-800 ms 261// 262// This is how kick+snare drum synthesis works, applied to gunshots. 263// Cheap, predictable, sounds like the gunshot SFX you remember from 264// classic sample libraries and arcade games. 265// 266// PHYSICAL — digital waveguide barrel resonance + body modes: 267// 268// excitation ──► (+) ──► boreDelay ─┬──► muzzleHPF ──► out 269// ▲ │ 270// │ breech_reflect │ 271// │ ▼ 272// └─── boreLP ◄──── (−1 open-end refl) 273// 274// excitation ──► 3× bodyModes ──► +out (parallel) 275// 276// Bore length sets the cavity resonance ("boom" frequency); body 277// modes give metallic character. Better for cavity-dominated sounds 278// (grenade, RPG launch) where the bore behavior actually matters. 279// 280// Common to both: secondary trigger (N-wave / 2nd click), sustain fire 281// (LMG retrigger), ricochet pitch sweep on release. 282// 283// Bore buffer is SHARED with the whistle (whistle_bore_buf) — only the 284// physical model uses it. 285 286typedef struct { 287 GunModel model; 288 // --- Common (both models) --- 289 double master_amp; // overall layer scaling (0.4–2.0) 290 double secondary_delay_ms; // 0 = no 2nd shot; else delay before re-trigger 291 double secondary_amp; // amplitude of 2nd shot relative to primary 292 int sustain_fire; // 1 = retrigger while held (LMG) 293 double retrig_period_ms; // ms between retrigs (60000/RPM) 294 // --- Classic-only --- 295 double click_amp; // sub-ms HF transient gain (0=off, ~0.6 typical) 296 double click_decay_ms; // very fast (0.3-0.8 ms) — the "tk" snap 297 double crack_amp; // 0..1 mix gain 298 double crack_decay_ms; // exp decay time of crack envelope 299 double crack_fc; // BPF center Hz (2000-8000 typical) 300 double crack_q; // BPF Q (1.0-3.0 typical) 301 double boom_amp; // 0..1 mix gain 302 double boom_freq_start; // Hz at trigger 303 double boom_freq_end; // Hz settled (≈40-80) 304 double boom_pitch_decay_ms; // time const for pitch sweep (10-50) 305 double boom_amp_decay_ms; // amp decay time (30-200) 306 double tail_amp; // 0..1 mix gain 307 double tail_attack_ms; // 0 = instant 308 double tail_decay_ms; // 100-800 309 double tail_fc; // LPF cutoff Hz (200-2000) 310 double tail_q; // LPF Q (0.5-1.5) 311 // --- Physical-only --- 312 double bore_length_s; // seconds (= 2L/c) 313 double bore_loss; // bore LPF alpha 314 double breech_reflect; // 0..1 315 double pressure; // excitation peak 316 double env_rate; // excitation decay rate (1/sec) 317 double noise_gain; // turbulent noise on excitation 318 double body_freq[3]; // mode freqs Hz 319 double body_q[3]; // mode Q 320 double body_amp[3]; // mode mix amplitudes 321 double radiation; // muzzle HPF coeff 322} GunPresetParams; 323 324// Per-weapon parameters. Most presets use CLASSIC for clean impact 325// sounds. Cavity-dominated weapons (grenade, RPG) keep the PHYSICAL 326// bore model where its long resonance helps. 327static const GunPresetParams gun_presets[GUN_PRESET_COUNT] = { 328 // --- GUN_PISTOL (9mm, L≈100mm) — sharp crack, tiny sub, quick tail 329 { .model = GUN_MODEL_CLASSIC, .master_amp = 1.1, 330 .click_amp = 0.65, .click_decay_ms = 0.5, 331 .crack_amp = 0.95, .crack_decay_ms = 7.0, .crack_fc = 3800, .crack_q = 2.6, 332 .boom_amp = 0.55, .boom_freq_start = 220, .boom_freq_end = 55, 333 .boom_pitch_decay_ms = 14, .boom_amp_decay_ms = 55, 334 .tail_amp = 0.35, .tail_attack_ms = 0, .tail_decay_ms = 110, 335 .tail_fc = 900, .tail_q = 0.8, 336 // Physical alt (warB): short barrel, bright body modes 337 .bore_length_s = 0.000588, .bore_loss = 0.55, .breech_reflect = 0.92, 338 .pressure = 1.2, .env_rate = 3000.0, .noise_gain = 0.6, 339 .body_freq = {1500, 4000, 8500}, .body_q = {12, 10, 8}, 340 .body_amp = {0.30, 0.20, 0.15}, .radiation = 0.985 }, 341 // --- GUN_RIFLE (AR-15, L≈400mm) — bright crack + supersonic N-wave tap 342 { .model = GUN_MODEL_CLASSIC, .master_amp = 1.2, 343 .click_amp = 0.75, .click_decay_ms = 0.6, 344 .crack_amp = 1.05, .crack_decay_ms = 8.0, .crack_fc = 4500, .crack_q = 3.0, 345 .boom_amp = 0.70, .boom_freq_start = 280, .boom_freq_end = 50, 346 .boom_pitch_decay_ms = 18, .boom_amp_decay_ms = 90, 347 .tail_amp = 0.45, .tail_attack_ms = 0, .tail_decay_ms = 220, 348 .tail_fc = 1100, .tail_q = 0.7, 349 .secondary_delay_ms = 0.9, .secondary_amp = 0.55, 350 // Physical alt: longer bore, deep mode ring + N-wave secondary 351 .bore_length_s = 0.00235, .bore_loss = 0.50, .breech_reflect = 0.95, 352 .pressure = 1.5, .env_rate = 2500.0, .noise_gain = 0.5, 353 .body_freq = {800, 2400, 6000}, .body_q = {14, 12, 10}, 354 .body_amp = {0.35, 0.25, 0.15}, .radiation = 0.988 }, 355 // --- GUN_SHOTGUN (12ga, L≈660mm, wide bore) — big low boom, noisy tail 356 { .model = GUN_MODEL_CLASSIC, .master_amp = 1.4, 357 .click_amp = 0.55, .click_decay_ms = 0.8, 358 .crack_amp = 0.65, .crack_decay_ms = 12, .crack_fc = 2200, .crack_q = 1.8, 359 .boom_amp = 1.10, .boom_freq_start = 260, .boom_freq_end = 38, 360 .boom_pitch_decay_ms = 22, .boom_amp_decay_ms = 130, 361 .tail_amp = 0.85, .tail_attack_ms = 4, .tail_decay_ms = 380, 362 .tail_fc = 700, .tail_q = 0.6, 363 // Physical alt: wide bore, low body modes 364 .bore_length_s = 0.00388, .bore_loss = 0.40, .breech_reflect = 0.88, 365 .pressure = 1.8, .env_rate = 1800.0, .noise_gain = 0.9, 366 .body_freq = {400, 1200, 3500}, .body_q = {10, 8, 7}, 367 .body_amp = {0.40, 0.25, 0.15}, .radiation = 0.965 }, 368 // --- GUN_SMG (MP5, L≈225mm) — bright fast crack, full-auto ~1000 RPM 369 { .model = GUN_MODEL_CLASSIC, .master_amp = 0.95, 370 .click_amp = 0.55, .click_decay_ms = 0.4, 371 .crack_amp = 0.85, .crack_decay_ms = 5.0, .crack_fc = 4200, .crack_q = 2.5, 372 .boom_amp = 0.40, .boom_freq_start = 200, .boom_freq_end = 60, 373 .boom_pitch_decay_ms = 10, .boom_amp_decay_ms = 40, 374 .tail_amp = 0.28, .tail_attack_ms = 0, .tail_decay_ms = 80, 375 .tail_fc = 1200, .tail_q = 0.7, 376 .sustain_fire = 1, .retrig_period_ms = 60, // 1000 RPM 377 // Physical alt 378 .bore_length_s = 0.00132, .bore_loss = 0.58, .breech_reflect = 0.92, 379 .pressure = 1.0, .env_rate = 3500.0, .noise_gain = 0.5, 380 .body_freq = {1200, 3500, 7500}, .body_q = {12, 10, 8}, 381 .body_amp = {0.30, 0.20, 0.13}, .radiation = 0.978 }, 382 // --- GUN_SUPPRESSED — tiny click, no boom, mid-range "pfft" 383 { .model = GUN_MODEL_CLASSIC, .master_amp = 0.7, 384 .click_amp = 0.08, .click_decay_ms = 0.4, 385 .crack_amp = 0.30, .crack_decay_ms = 6.0, .crack_fc = 1600, .crack_q = 1.1, 386 .boom_amp = 0.10, .boom_freq_start = 150, .boom_freq_end = 80, 387 .boom_pitch_decay_ms = 8, .boom_amp_decay_ms = 30, 388 .tail_amp = 0.85, .tail_attack_ms = 6, .tail_decay_ms = 140, 389 .tail_fc = 1800, .tail_q = 0.6, 390 // Physical alt: heavy bore loss = absorptive baffles, low radiation 391 .bore_length_s = 0.00100, .bore_loss = 0.85, .breech_reflect = 0.80, 392 .pressure = 0.5, .env_rate = 1500.0, .noise_gain = 1.0, 393 .body_freq = {600, 1500, 3000}, .body_q = {6, 5, 4}, 394 .body_amp = {0.15, 0.10, 0.05}, .radiation = 0.85 }, 395 // --- GUN_LMG (M60, L≈560mm) — rifle-class retriggered ~600 RPM 396 // Master amp tamed so the first shot doesn't stand out from the 397 // sustained burst (sustain-fire weapons also start their envelopes 398 // at the average jitter level — see gun_init_voice). 399 { .model = GUN_MODEL_CLASSIC, .master_amp = 0.9, 400 .click_amp = 0.55, .click_decay_ms = 0.5, 401 .crack_amp = 0.85, .crack_decay_ms = 7.0, .crack_fc = 3500, .crack_q = 2.6, 402 .boom_amp = 0.65, .boom_freq_start = 250, .boom_freq_end = 48, 403 .boom_pitch_decay_ms = 16, .boom_amp_decay_ms = 75, 404 .tail_amp = 0.40, .tail_attack_ms = 0, .tail_decay_ms = 160, 405 .tail_fc = 950, .tail_q = 0.7, 406 .sustain_fire = 1, .retrig_period_ms = 100, // 600 RPM 407 // Physical alt 408 .bore_length_s = 0.00329, .bore_loss = 0.48, .breech_reflect = 0.94, 409 .pressure = 1.4, .env_rate = 2200.0, .noise_gain = 0.55, 410 .body_freq = {600, 1800, 4500}, .body_q = {12, 10, 8}, 411 .body_amp = {0.35, 0.25, 0.15}, .radiation = 0.982 }, 412 // --- GUN_SNIPER (.50, L≈740mm) — huge crack + N-wave + long tail 413 { .model = GUN_MODEL_CLASSIC, .master_amp = 1.5, 414 .click_amp = 0.85, .click_decay_ms = 0.7, 415 .crack_amp = 1.20, .crack_decay_ms = 11, .crack_fc = 5000, .crack_q = 3.2, 416 .boom_amp = 1.20, .boom_freq_start = 320, .boom_freq_end = 36, 417 .boom_pitch_decay_ms = 28, .boom_amp_decay_ms = 180, 418 .tail_amp = 0.70, .tail_attack_ms = 3, .tail_decay_ms = 500, 419 .tail_fc = 850, .tail_q = 0.8, 420 .secondary_delay_ms = 1.4, .secondary_amp = 0.70, 421 // Physical alt: high pressure, long ring 422 .bore_length_s = 0.00435, .bore_loss = 0.35, .breech_reflect = 0.97, 423 .pressure = 2.0, .env_rate = 1500.0, .noise_gain = 0.7, 424 .body_freq = {350, 950, 2800}, .body_q = {14, 12, 10}, 425 .body_amp = {0.50, 0.30, 0.15}, .radiation = 0.992 }, 426 // --- GUN_GRENADE — large cavity, slow release. Default = PHYSICAL 427 // (the long bore resonance makes the cavity feel right). Classic 428 // alt is a very low boom + heavy noisy tail for the kaboom. 429 { .model = GUN_MODEL_PHYSICAL, 430 .bore_length_s = 0.01000, .bore_loss = 0.25, .breech_reflect = 0.60, 431 .pressure = 1.6, .env_rate = 400.0, .noise_gain = 1.5, 432 .body_freq = {80, 250, 1200}, .body_q = {6, 5, 4}, 433 .body_amp = {0.60, 0.35, 0.15}, .radiation = 0.70, 434 // Classic alt (warA): tiny click, huge boom, very long tail 435 .master_amp = 1.6, 436 .click_amp = 0.40, .click_decay_ms = 1.0, 437 .crack_amp = 0.45, .crack_decay_ms = 25, .crack_fc = 800, .crack_q = 0.7, 438 .boom_amp = 1.50, .boom_freq_start = 150, .boom_freq_end = 28, 439 .boom_pitch_decay_ms = 60, .boom_amp_decay_ms = 350, 440 .tail_amp = 1.50, .tail_attack_ms = 12, .tail_decay_ms = 800, 441 .tail_fc = 400, .tail_q = 0.4 }, 442 // --- GUN_RPG — long motor burn + delayed boom. Default = PHYSICAL 443 // (the slow bore loop nicely models the rocket exhaust whoosh). 444 { .model = GUN_MODEL_PHYSICAL, 445 .bore_length_s = 0.00300, .bore_loss = 0.30, .breech_reflect = 0.50, 446 .pressure = 1.2, .env_rate = 150.0, .noise_gain = 2.5, 447 .body_freq = {200, 600, 2000}, .body_q = {4, 3, 3}, 448 .body_amp = {0.40, 0.30, 0.20}, .radiation = 0.60, 449 .secondary_delay_ms = 250, .secondary_amp = 1.5, 450 // Classic alt: launch click, sustained noise (motor) + delayed boom 451 .master_amp = 1.3, 452 .click_amp = 0.30, .click_decay_ms = 0.8, 453 .crack_amp = 0.40, .crack_decay_ms = 20, .crack_fc = 1500, .crack_q = 0.8, 454 .boom_amp = 0.30, .boom_freq_start = 120, .boom_freq_end = 60, 455 .boom_pitch_decay_ms = 30, .boom_amp_decay_ms = 100, 456 .tail_amp = 2.00, .tail_attack_ms = 80, .tail_decay_ms = 600, 457 .tail_fc = 600, .tail_q = 0.5 }, 458 // --- GUN_RELOAD — magazine clack: bright HF click + bandpass burst 459 { .model = GUN_MODEL_CLASSIC, .master_amp = 0.75, 460 .click_amp = 0.85, .click_decay_ms = 0.4, 461 .crack_amp = 0.90, .crack_decay_ms = 4.0, .crack_fc = 4500, .crack_q = 3.0, 462 .boom_amp = 0.0, .boom_freq_start = 0, .boom_freq_end = 0, 463 .boom_pitch_decay_ms = 1, .boom_amp_decay_ms = 1, 464 .tail_amp = 0.20, .tail_attack_ms = 0, .tail_decay_ms = 30, 465 .tail_fc = 2500, .tail_q = 0.6, 466 .secondary_delay_ms = 80, .secondary_amp = 0.65, 467 // Physical alt: tiny bore = sharp metallic transient + insert click 468 .bore_length_s = 0.00010, .bore_loss = 0.70, .breech_reflect = 0.90, 469 .pressure = 0.6, .env_rate = 4000.0, .noise_gain = 0.3, 470 .body_freq = {2200, 4500, 8000}, .body_q = {10, 8, 6}, 471 .body_amp = {0.40, 0.30, 0.15}, .radiation = 0.92 }, 472 // --- GUN_COCK — bolt-action click-clack (two crisp clicks) 473 { .model = GUN_MODEL_CLASSIC, .master_amp = 0.8, 474 .click_amp = 0.90, .click_decay_ms = 0.4, 475 .crack_amp = 1.00, .crack_decay_ms = 5.0, .crack_fc = 3800, .crack_q = 3.2, 476 .boom_amp = 0.0, .boom_freq_start = 0, .boom_freq_end = 0, 477 .boom_pitch_decay_ms = 1, .boom_amp_decay_ms = 1, 478 .tail_amp = 0.15, .tail_attack_ms = 0, .tail_decay_ms = 25, 479 .tail_fc = 2000, .tail_q = 0.6, 480 .secondary_delay_ms = 55, .secondary_amp = 0.80, 481 // Physical alt 482 .bore_length_s = 0.00015, .bore_loss = 0.65, .breech_reflect = 0.88, 483 .pressure = 0.7, .env_rate = 3500.0, .noise_gain = 0.35, 484 .body_freq = {1800, 4200, 7500}, .body_q = {10, 8, 7}, 485 .body_amp = {0.45, 0.25, 0.15}, .radiation = 0.92 }, 486 // --- GUN_RICOCHET — pitched ping with downward pitch on release 487 { .model = GUN_MODEL_CLASSIC, .master_amp = 0.85, 488 .click_amp = 0.40, .click_decay_ms = 0.5, 489 .crack_amp = 0.35, .crack_decay_ms = 7.0, .crack_fc = 5500, .crack_q = 3.0, 490 .boom_amp = 0.95, .boom_freq_start = 1800,.boom_freq_end = 1500, 491 .boom_pitch_decay_ms = 60, .boom_amp_decay_ms = 350, 492 .tail_amp = 0.20, .tail_attack_ms = 0, .tail_decay_ms = 200, 493 .tail_fc = 3000, .tail_q = 1.0, 494 // Physical alt: high-Q metallic ring (ricochet really IS that) 495 .bore_length_s = 0.00040, .bore_loss = 0.15, .breech_reflect = 0.90, 496 .pressure = 0.8, .env_rate = 600.0, .noise_gain = 0.3, 497 .body_freq = {3000, 5500, 9000}, .body_q = {30, 25, 20}, 498 .body_amp = {0.40, 0.25, 0.15}, .radiation = 0.975 }, 499}; 500 501// ----- helper: precompute 2-pole resonant filter coefficients ----- 502// y = b0*x + a1*y[n-1] - a2*y[n-2] 503// a1 = 2·r·cos(w), a2 = r², r = exp(-π·f / (Q·sr)), w = 2π·f/sr 504// Output peak gain ≈ 1/(1-a1+a2) at DC and varies with Q. The b0 input 505// gain is scaled so the resonant peak is approximately unity, making 506// per-layer mix amps map to roughly equal loudness regardless of Q. 507static inline void compute_resonator(double f, double q, double sr, 508 double *a1, double *a2, double *b0) { 509 if (q < 0.4) q = 0.4; 510 if (f < 20.0) f = 20.0; 511 if (f > sr * 0.45) f = sr * 0.45; 512 double r = exp(-M_PI * f / (q * sr)); 513 double w = 2.0 * M_PI * f / sr; 514 *a1 = 2.0 * r * cos(w); 515 *a2 = r * r; 516 // Peak gain of a 2-pole resonator ≈ 1/(1 - r). Pre-attenuate input 517 // by that factor so the resonant peak stays near unity amplitude. 518 *b0 = (1.0 - r); 519} 520 521// Initialize a voice's gun state from a preset. Called from audio_synth_gun. 522// `force_model` overrides the preset's default model: -1 = preset default, 523// 0 = CLASSIC, 1 = PHYSICAL. The preset table holds parameters for both 524// models so the override always finds a populated config. 525static void gun_init_voice(ACVoice *v, GunPreset preset, double sr, 526 int force_model) { 527 if (preset < 0 || preset >= GUN_PRESET_COUNT) preset = GUN_PISTOL; 528 const GunPresetParams *p = &gun_presets[preset]; 529 530 v->gun_preset = (int)preset; 531 v->gun_model = (force_model == 0 || force_model == 1) 532 ? force_model : (int)p->model; 533 v->gun_pressure = p->master_amp > 0.0 ? p->master_amp : 1.0; 534 // Sustain-fire weapons (SMG/LMG) start at the same gentler level 535 // their internal retrigger uses (avg jitter ≈ 0.95) so the first 536 // shot blends with the rapid-fire stream instead of standing out. 537 v->gun_pressure_env = p->sustain_fire ? 0.92 : 1.0; 538 v->gun_secondary_trig = p->secondary_delay_ms > 0 539 ? p->secondary_delay_ms * 0.001 * sr : 0.0; 540 v->gun_secondary_amp = p->secondary_amp; 541 v->gun_sustain_fire = p->sustain_fire; 542 v->gun_retrig_timer = 0.0; 543 v->gun_retrig_period = p->retrig_period_ms * 0.001; 544 545 // Pitch sweep: nominal 1.0 at trigger. Ricochet sets target<1.0 on 546 // release so boom freq drops (doppler-style). 547 v->gun_pitch_mult = 1.0; 548 v->gun_pitch_target = 1.0; 549 v->gun_pitch_slew = 1.0 / (0.3 * sr); 550 551 if (v->gun_model == GUN_MODEL_CLASSIC) { 552 // Crack: exp decay multiplier from time-constant tau (in ms). 553 double tau_crack = (p->crack_decay_ms > 0.1 ? p->crack_decay_ms : 0.1) * 0.001; 554 v->gun_env_decay_mult = exp(-1.0 / (tau_crack * sr)); 555 556 // Boom: pitch sweep from start→end via geometric approach. 557 // After tau seconds, distance to target is ~e^{-1} of original. 558 v->gun_boom_freq_start = p->boom_freq_start; 559 v->gun_boom_freq_end = p->boom_freq_end; 560 v->gun_boom_freq = p->boom_freq_start; 561 v->gun_boom_phase = 0.0; 562 double tau_pitch = (p->boom_pitch_decay_ms > 0.1 ? p->boom_pitch_decay_ms : 0.1) * 0.001; 563 v->gun_boom_pitch_mult = exp(-1.0 / (tau_pitch * sr)); 564 double tau_boom = (p->boom_amp_decay_ms > 0.1 ? p->boom_amp_decay_ms : 0.1) * 0.001; 565 v->gun_boom_decay_mult = exp(-1.0 / (tau_boom * sr)); 566 v->gun_boom_env = (p->boom_amp > 0.0) ? (p->sustain_fire ? 0.92 : 1.0) : 0.0; 567 568 // Tail: linear attack ramp + exp decay. 569 v->gun_tail_env = (p->tail_attack_ms > 0.0) ? 0.0 : 1.0; 570 if (p->tail_attack_ms > 0.0) { 571 v->gun_tail_attack_inc = 1.0 / (p->tail_attack_ms * 0.001 * sr); 572 } else { 573 v->gun_tail_attack_inc = 0.0; 574 } 575 double tau_tail = (p->tail_decay_ms > 0.1 ? p->tail_decay_ms : 0.1) * 0.001; 576 v->gun_tail_decay_mult = exp(-1.0 / (tau_tail * sr)); 577 578 // Filter coeffs: body slot [0] = crack BPF, [1] = tail LPF. 579 compute_resonator(p->crack_fc, p->crack_q, sr, 580 &v->gun_body_a1[0], &v->gun_body_a2[0], &v->gun_crack_b0); 581 compute_resonator(p->tail_fc, p->tail_q, sr, 582 &v->gun_body_a1[1], &v->gun_body_a2[1], &v->gun_tail_b0); 583 v->gun_tail_b1 = 0.0; 584 v->gun_tail_b2 = 0.0; 585 v->gun_body_y1[0] = v->gun_body_y2[0] = 0.0; 586 v->gun_body_y1[1] = v->gun_body_y2[1] = 0.0; 587 v->gun_body_y1[2] = v->gun_body_y2[2] = 0.0; 588 // Layer mix gains. 589 v->gun_body_amp[0] = p->crack_amp; 590 v->gun_body_amp[1] = p->boom_amp; 591 v->gun_body_amp[2] = p->tail_amp; 592 // Click layer (sub-ms HF transient — adds the "tk" snap). 593 v->gun_click_amp = p->click_amp; 594 v->gun_click_env = (p->click_amp > 0.0) ? (p->sustain_fire ? 0.92 : 1.0) : 0.0; 595 v->gun_click_prev = 0.0; 596 double tau_click = (p->click_decay_ms > 0.05 ? p->click_decay_ms : 0.05) * 0.001; 597 v->gun_click_decay_mult = exp(-1.0 / (tau_click * sr)); 598 // Physical-only fields zeroed for safety. 599 v->gun_bore_delay = 0.0; 600 v->gun_bore_loss = 0.0; 601 v->gun_bore_lp = 0.0; 602 v->gun_breech_reflect = 0.0; 603 v->gun_noise_gain = 0.0; 604 v->gun_radiation_a = 0.0; 605 v->gun_rad_prev = 0.0; 606 memset(v->whistle_bore_buf, 0, sizeof(v->whistle_bore_buf)); 607 v->whistle_bore_w = 0; 608 } else { 609 // PHYSICAL model — DWG bore + body modes. 610 v->gun_bore_delay = p->bore_length_s * sr; 611 if (v->gun_bore_delay < 4.0) v->gun_bore_delay = 4.0; 612 if (v->gun_bore_delay > 2040.0) v->gun_bore_delay = 2040.0; 613 v->gun_bore_loss = p->bore_loss; 614 v->gun_bore_lp = 0.0; 615 v->gun_breech_reflect = p->breech_reflect; 616 v->gun_pressure = p->pressure; // physical uses its own pressure scale 617 v->gun_env_decay_mult = exp(-p->env_rate / sr); 618 v->gun_noise_gain = p->noise_gain; 619 v->gun_radiation_a = p->radiation; 620 v->gun_rad_prev = 0.0; 621 for (int i = 0; i < 3; i++) { 622 double a1, a2, b0_unused; 623 compute_resonator(p->body_freq[i], p->body_q[i], sr, &a1, &a2, &b0_unused); 624 v->gun_body_a1[i] = a1; 625 v->gun_body_a2[i] = a2; 626 v->gun_body_amp[i] = p->body_amp[i]; 627 v->gun_body_y1[i] = 0.0; 628 v->gun_body_y2[i] = 0.0; 629 } 630 memset(v->whistle_bore_buf, 0, sizeof(v->whistle_bore_buf)); 631 v->whistle_bore_w = 0; 632 // Friedlander pulse params. t+ derived from env_rate so existing 633 // preset tunings still feel right: shorter env_rate → wider pulse 634 // (grenade ~7ms, pistol ~1ms). Friedlander A = 1.5 is a good 635 // default for the positive-phase decay shape. 636 v->gun_phys_t = 0.0; 637 v->gun_phys_t_plus = (3.0 / (p->env_rate > 100 ? p->env_rate : 100.0)) * sr; 638 if (v->gun_phys_t_plus < 32.0) v->gun_phys_t_plus = 32.0; // ≥ ~0.17ms 639 if (v->gun_phys_t_plus > 4096.0) v->gun_phys_t_plus = 4096.0; // ≤ ~21ms 640 v->gun_phys_friedlander_a = 1.5; 641 v->gun_phys_neg_amp = 0.18; 642 // Ground reflection — fixed ~3.5ms tap with 18% gain. Caps at 643 // 1023 samples = ~5.3ms at 192kHz. Tunable per-preset later. 644 v->gun_phys_echo_delay = 0.0035 * sr; 645 if (v->gun_phys_echo_delay > 1023.0) v->gun_phys_echo_delay = 1023.0; 646 v->gun_phys_echo_amp = 0.22; 647 memset(v->gun_phys_echo_buf, 0, sizeof(v->gun_phys_echo_buf)); 648 v->gun_phys_echo_w = 0; 649 // Classic-only fields zeroed. 650 v->gun_boom_phase = 0.0; 651 v->gun_boom_freq = 0.0; 652 v->gun_boom_freq_start = 0.0; 653 v->gun_boom_freq_end = 0.0; 654 v->gun_boom_pitch_mult = 1.0; 655 v->gun_boom_env = 0.0; 656 v->gun_boom_decay_mult = 1.0; 657 v->gun_tail_env = 0.0; 658 v->gun_tail_attack_inc = 0.0; 659 v->gun_tail_decay_mult = 1.0; 660 v->gun_crack_b0 = 0.0; 661 v->gun_tail_b0 = v->gun_tail_b1 = v->gun_tail_b2 = 0.0; 662 v->gun_click_amp = 0.0; 663 v->gun_click_env = 0.0; 664 v->gun_click_decay_mult = 1.0; 665 v->gun_click_prev = 0.0; 666 v->gun_phys_t = 0.0; 667 v->gun_phys_t_plus = 0.0; 668 v->gun_phys_friedlander_a = 0.0; 669 v->gun_phys_neg_amp = 0.0; 670 v->gun_phys_echo_delay = 0.0; 671 v->gun_phys_echo_amp = 0.0; 672 v->gun_phys_echo_w = 0; 673 } 674} 675 676// Called when a gun voice enters VOICE_KILLING — sets up release-time 677// behaviors (ricochet pitch drop applies to both models via gun_pitch_mult). 678static inline void gun_on_release(ACVoice *v) { 679 if (v->type != WAVE_GUN) return; 680 if (v->gun_preset == GUN_RICOCHET) { 681 // Drop pitch on release — for classic this scales boom freq down; 682 // for physical it stretches the bore delay (doppler). 683 v->gun_pitch_target = (v->gun_model == GUN_MODEL_CLASSIC) ? 0.35 : 2.8; 684 } 685} 686 687// Three-layer kick/snare-style gunshot synthesis. Output is summed 688// crack (BPF noise) + boom (pitched osc with downward sweep) + tail 689// (LPF noise with attack-decay), then scaled by master amp and the 690// piece-supplied envelope. 691static inline double generate_gun_classic_sample(ACVoice *v, double sr) { 692 // --- Secondary trigger (rifle N-wave / 2nd click of cock/reload) --- 693 if (v->gun_secondary_trig > 0.0) { 694 v->gun_secondary_trig -= 1.0; 695 if (v->gun_secondary_trig <= 0.0) { 696 v->gun_pressure_env = v->gun_secondary_amp; // refire crack 697 v->gun_boom_env = v->gun_secondary_amp * 0.6; // gentler boom 698 v->gun_click_env = v->gun_secondary_amp; // refire click too 699 v->gun_secondary_trig = 0.0; 700 } 701 } 702 703 // --- LMG sustain-fire retrigger --- 704 if (v->gun_sustain_fire && v->state == VOICE_ACTIVE 705 && isinf(v->duration) && v->gun_retrig_period > 0.0) { 706 v->gun_retrig_timer += 1.0 / sr; 707 if (v->gun_retrig_timer >= v->gun_retrig_period) { 708 v->gun_retrig_timer -= v->gun_retrig_period; 709 double j = (double)xorshift32(&v->noise_seed) / (double)UINT32_MAX; 710 double jitter = 0.82 + j * 0.32; // ±18% 711 v->gun_pressure_env = jitter; 712 v->gun_boom_env = jitter; 713 v->gun_click_env = jitter; 714 v->gun_boom_freq = v->gun_boom_freq_start; // restart pitch sweep 715 // Tail keeps decaying (no re-attack) so rapid-fire feels continuous. 716 } 717 } 718 719 // --- Pitch sweep (ricochet release doppler) --- 720 if (v->gun_pitch_mult != v->gun_pitch_target) { 721 v->gun_pitch_mult += (v->gun_pitch_target - v->gun_pitch_mult) * 0.00012; 722 } 723 724 // === Layer 0: CLICK — sub-millisecond HF transient === 725 // 1-zero HPF on white noise (y = x - x[n-1]) emphasizes the highest 726 // frequencies. Combined with a ~0.5ms tau exp envelope it reads as 727 // the sharp "tk" attack you expect at the front of a gunshot — 728 // without it, the BPF crack on its own sounds like a shaped hiss. 729 double click = 0.0; 730 if (v->gun_click_env > 0.00002 && v->gun_click_amp > 0.0) { 731 double white = ((double)xorshift32(&v->noise_seed) / (double)UINT32_MAX) * 2.0 - 1.0; 732 double hp = white - v->gun_click_prev; 733 v->gun_click_prev = white; 734 click = hp * v->gun_click_env * v->gun_click_amp; 735 v->gun_click_env *= v->gun_click_decay_mult; 736 } 737 738 // === Layer 1: CRACK — bandpass-filtered noise burst === 739 double crack = 0.0; 740 if (v->gun_pressure_env > 0.00002 && v->gun_body_amp[0] > 0.0) { 741 double white = ((double)xorshift32(&v->noise_seed) / (double)UINT32_MAX) * 2.0 - 1.0; 742 // 2-pole resonator (bandpass-like) on white noise. 743 double y = v->gun_crack_b0 * white 744 + v->gun_body_a1[0] * v->gun_body_y1[0] 745 - v->gun_body_a2[0] * v->gun_body_y2[0]; 746 v->gun_body_y2[0] = v->gun_body_y1[0]; 747 v->gun_body_y1[0] = y; 748 crack = y * v->gun_pressure_env * v->gun_body_amp[0]; 749 v->gun_pressure_env *= v->gun_env_decay_mult; 750 } 751 752 // === Layer 2: BOOM — pitched triangle with exponential pitch drop === 753 double boom = 0.0; 754 if (v->gun_boom_env > 0.00002 && v->gun_body_amp[1] > 0.0) { 755 // Geometric approach toward end freq. For typical 14ms tau at 756 // 192kHz, this glides 250→55 Hz audibly within ~50ms. 757 v->gun_boom_freq = v->gun_boom_freq_end 758 + (v->gun_boom_freq - v->gun_boom_freq_end) * v->gun_boom_pitch_mult; 759 double f = v->gun_boom_freq * v->gun_pitch_mult; 760 if (f < 1.0) f = 1.0; 761 v->gun_boom_phase += f / sr; 762 if (v->gun_boom_phase >= 1.0) v->gun_boom_phase -= 1.0; 763 if (v->gun_boom_phase < 0.0) v->gun_boom_phase += 1.0; 764 // Triangle wave — fatter low-end punch than sine, less harsh than square. 765 double tp = v->gun_boom_phase; 766 double s = (tp < 0.5) ? (4.0 * tp - 1.0) : (3.0 - 4.0 * tp); 767 boom = s * v->gun_boom_env * v->gun_body_amp[1]; 768 v->gun_boom_env *= v->gun_boom_decay_mult; 769 } 770 771 // === Layer 3: TAIL — lowpass-filtered noise rumble === 772 double tail = 0.0; 773 if (v->gun_body_amp[2] > 0.0) { 774 // Envelope: linear ramp during attack phase, then exp decay. 775 if (v->gun_tail_attack_inc > 0.0) { 776 v->gun_tail_env += v->gun_tail_attack_inc; 777 if (v->gun_tail_env >= 1.0) { 778 v->gun_tail_env = 1.0; 779 v->gun_tail_attack_inc = 0.0; // attack done; switch to decay 780 } 781 } else if (v->gun_tail_env > 0.00001) { 782 v->gun_tail_env *= v->gun_tail_decay_mult; 783 } 784 if (v->gun_tail_env > 0.00001) { 785 double white = ((double)xorshift32(&v->noise_seed) / (double)UINT32_MAX) * 2.0 - 1.0; 786 // 2-pole resonator at low freq, low Q ≈ 1-pole-ish lowpass behavior. 787 double y = v->gun_tail_b0 * white 788 + v->gun_body_a1[1] * v->gun_body_y1[1] 789 - v->gun_body_a2[1] * v->gun_body_y2[1]; 790 v->gun_body_y2[1] = v->gun_body_y1[1]; 791 v->gun_body_y1[1] = y; 792 tail = y * v->gun_tail_env * v->gun_body_amp[2]; 793 } 794 } 795 796 // Click also retriggers on secondary/sustain events because gun_click_env 797 // gets reset in those branches via gun_pressure_env (no — actually it 798 // doesn't; we only reset crack/boom there). Fold a small click retrigger 799 // into the secondary path so reload+cock 2nd hits feel just as crisp. 800 double out = (click + crack + boom + tail) * v->gun_pressure; 801 return out * compute_envelope(v); 802} 803 804// Physical-model gunshot — Friedlander blast wave excitation feeding a 805// DWG bore + parallel body modes + muzzle radiation HPF + ground-echo 806// tap. Better for cavity-dominated weapons (grenade, RPG launch tube) 807// where the bore length is meaningful. 808// 809// The Friedlander waveform models the actual pressure-vs-time curve of 810// a free-air blast wave: 811// P(t) = P_peak · (1 − t/t+) · exp(−A·t/t+) for 0 ≤ t ≤ t+ 812// P(t) ≈ −P_peak · neg_amp · (1−tn) · exp(−2·tn) for t > t+ 813// (where tn = (t−t+) / (4·t+)) 814// (Friedlander 1946; widely used in blast-wave acoustics — see Mengual 815// et al. 2017 "Procedural Synthesis of Gunshot Sounds…") 816// 817// The ground-echo tap (a single delayed copy of the radiated signal, 818// ~3-5 ms behind, attenuated) gives the spatial sense of an outdoor 819// shot — without it, the whole thing sounds anechoic and wrong. 820static inline double generate_gun_physical_sample(ACVoice *v, double sr) { 821 // === Excitation: Friedlander envelope shaping a noise burst === 822 // Pure Friedlander pulses are too smooth between samples — the muzzle 823 // radiation HPF (1-zero differentiator at α≈0.985) annihilates anything 824 // that doesn't change between adjacent samples, killing the radiated 825 // path entirely. So we use Friedlander as the AMPLITUDE ENVELOPE of a 826 // noise burst (rather than the signal itself). The smooth shape gives 827 // us the right onset/decay character; the noise content gives the HPF 828 // and bore loop high-frequency material to actually radiate and ring. 829 double t = v->gun_phys_t; 830 double t_plus = v->gun_phys_t_plus; 831 double A = v->gun_phys_friedlander_a; 832 double pulse = 0.0; 833 if (t < t_plus) { 834 // Positive phase — sharp peak then exp decay. 835 double f = t / t_plus; 836 pulse = (1.0 - f) * exp(-A * f); 837 } else if (t < t_plus * 5.0) { 838 // Negative phase — sub-atmospheric dip after the wave passes. 839 double tn = (t - t_plus) / (t_plus * 4.0); 840 pulse = -v->gun_phys_neg_amp * (1.0 - tn) * exp(-2.0 * tn); 841 } 842 uint32_t n = xorshift32(&v->noise_seed); 843 double white = ((double)n / (double)UINT32_MAX) * 2.0 - 1.0; 844 // Smooth deterministic component + noise rider. noise_gain mixes the 845 // turbulent content. The smooth term keeps low-freq energy for the 846 // bore loop; the noise term feeds the radiation HPF + body modes. 847 double excite = v->gun_pressure * pulse * (1.0 + v->gun_noise_gain * white); 848 v->gun_phys_t += 1.0; 849 850 // Secondary trigger — rifle N-wave or RPG delayed explosion. Restarts 851 // the Friedlander pulse from t=0 with a scaled peak. 852 if (v->gun_secondary_trig > 0.0) { 853 v->gun_secondary_trig -= 1.0; 854 if (v->gun_secondary_trig <= 0.0) { 855 v->gun_phys_t = 0.0; 856 v->gun_pressure *= v->gun_secondary_amp; 857 v->gun_secondary_trig = 0.0; 858 } 859 } 860 861 // Sustain fire — restart pulse at jitter scale. 862 if (v->gun_sustain_fire && v->state == VOICE_ACTIVE 863 && isinf(v->duration) && v->gun_retrig_period > 0.0) { 864 v->gun_retrig_timer += 1.0 / sr; 865 if (v->gun_retrig_timer >= v->gun_retrig_period) { 866 v->gun_retrig_timer -= v->gun_retrig_period; 867 v->gun_phys_t = 0.0; 868 double j = (double)xorshift32(&v->noise_seed) / (double)UINT32_MAX; 869 // Tiny per-shot pressure variance around 1.0 (no permanent drift). 870 v->gun_pressure *= 0.92 + j * 0.16; 871 } 872 } 873 874 // Pitch sweep approach (ricochet — currently classic-only, kept for parity). 875 if (v->gun_pitch_mult != v->gun_pitch_target) { 876 v->gun_pitch_mult += (v->gun_pitch_target - v->gun_pitch_mult) * 0.00012; 877 } 878 double bore_delay = v->gun_bore_delay * v->gun_pitch_mult; 879 if (bore_delay < 4.0) bore_delay = 4.0; 880 if (bore_delay > 2040.0) bore_delay = 2040.0; 881 882 // === Bore: closed breech (+refl) / open muzzle (−refl + LPF damping) === 883 const int BORE_N = 2048; 884 double bore_out = whistle_frac_read(v->whistle_bore_buf, BORE_N, 885 v->whistle_bore_w, bore_delay); 886 v->gun_bore_lp = v->gun_bore_loss * (-bore_out) 887 + (1.0 - v->gun_bore_loss) * v->gun_bore_lp; 888 double refl = v->gun_bore_lp; 889 double into_bore = excite + refl * v->gun_breech_reflect; 890 v->whistle_bore_buf[v->whistle_bore_w] = (float)into_bore; 891 v->whistle_bore_w = (v->whistle_bore_w + 1) % BORE_N; 892 893 // === Muzzle radiation: 1-zero HPF (open end emphasizes highs) === 894 double radiated = into_bore - v->gun_radiation_a * v->gun_rad_prev; 895 v->gun_rad_prev = into_bore; 896 897 // === Body modes: parallel pole-pair resonators on the excitation === 898 double body = 0.0; 899 for (int i = 0; i < 3; i++) { 900 double y = excite 901 + v->gun_body_a1[i] * v->gun_body_y1[i] 902 - v->gun_body_a2[i] * v->gun_body_y2[i]; 903 v->gun_body_y2[i] = v->gun_body_y1[i]; 904 v->gun_body_y1[i] = y; 905 body += y * v->gun_body_amp[i]; 906 } 907 908 double dry = radiated * 0.55 + body * 0.45; 909 910 // === Ground reflection echo — short delayed copy. Even at low 911 // amplitude this turns the dry shot into a "fired outdoors" shot. 912 double echo_out = 0.0; 913 if (v->gun_phys_echo_amp > 0.0 && v->gun_phys_echo_delay > 1.0) { 914 const int ECHO_N = 1024; 915 int read_pos = v->gun_phys_echo_w - (int)v->gun_phys_echo_delay; 916 while (read_pos < 0) read_pos += ECHO_N; 917 echo_out = (double)v->gun_phys_echo_buf[read_pos % ECHO_N] 918 * v->gun_phys_echo_amp; 919 v->gun_phys_echo_buf[v->gun_phys_echo_w] = (float)dry; 920 v->gun_phys_echo_w = (v->gun_phys_echo_w + 1) % ECHO_N; 921 } 922 923 return (dry + echo_out) * compute_envelope(v); 924} 925 926static inline double generate_gun_sample(ACVoice *v, double sr) { 927 if (v->gun_model == GUN_MODEL_PHYSICAL) { 928 return generate_gun_physical_sample(v, sr); 929 } 930 return generate_gun_classic_sample(v, sr); 931} 932 933static inline double compute_fade(ACVoice *v) { 934 if (v->state != VOICE_KILLING) return 1.0; 935 if (v->fade_duration <= 0.0) return 0.0; 936 double progress = v->fade_elapsed / v->fade_duration; 937 if (progress >= 1.0) return 0.0; 938 return 1.0 - progress; 939} 940 941static inline double generate_sample(ACVoice *v, double sample_rate) { 942 double s; 943 switch (v->type) { 944 case WAVE_SINE: 945 s = sin(2.0 * M_PI * v->phase); 946 break; 947 case WAVE_SQUARE: 948 s = v->phase < 0.5 ? 1.0 : -1.0; 949 break; 950 case WAVE_TRIANGLE: { 951 // Offset phase by 0.25 to start at zero crossing (matches synth.mjs) 952 double tp = v->phase + 0.25; 953 if (tp >= 1.0) tp -= 1.0; 954 s = 4.0 * fabs(tp - 0.5) - 1.0; 955 break; 956 } 957 case WAVE_SAWTOOTH: 958 s = 2.0 * v->phase - 1.0; 959 break; 960 case WAVE_NOISE: { 961 // Filtered white noise using biquad LPF 962 double white = ((double)xorshift32(&v->noise_seed) / (double)UINT32_MAX) * 2.0 - 1.0; 963 double y = v->noise_b0 * white + v->noise_b1 * v->noise_x1 + v->noise_b2 * v->noise_x2 964 - v->noise_a1 * v->noise_y1 - v->noise_a2 * v->noise_y2; 965 v->noise_x2 = v->noise_x1; 966 v->noise_x1 = white; 967 v->noise_y2 = v->noise_y1; 968 v->noise_y1 = y; 969 s = y; 970 break; 971 } 972 case WAVE_WHISTLE: 973 s = generate_whistle_sample(v, sample_rate); 974 break; 975 case WAVE_GUN: 976 s = generate_gun_sample(v, sample_rate); 977 break; 978 default: 979 s = 0.0; 980 } 981 982 // Smooth frequency toward target (uses precomputed alpha from caller) 983 if (v->target_frequency > 0 && v->frequency != v->target_frequency) { 984 v->frequency += (v->target_frequency - v->frequency) * 0.0003; // ~5ms at 192kHz 985 } 986 987 // Advance phase for basic oscillators; whistle/gun use their own DWG state. 988 if (v->type != WAVE_WHISTLE && v->type != WAVE_GUN) { 989 v->phase += v->frequency / sample_rate; 990 if (v->phase >= 1.0) v->phase -= 1.0; 991 } 992 993 return s; 994} 995 996// Setup biquad LPF coefficients for noise voice 997static void setup_noise_filter(ACVoice *v, double sample_rate) { 998 double cutoff = v->frequency; 999 if (cutoff < 20.0) cutoff = 20.0; 1000 if (cutoff > sample_rate / 2.0) cutoff = sample_rate / 2.0; 1001 1002 double Q = 1.0; 1003 double w0 = 2.0 * M_PI * cutoff / sample_rate; 1004 double alpha = sin(w0) / (2.0 * Q); 1005 1006 double b0 = (1.0 - cos(w0)) / 2.0; 1007 double b1 = 1.0 - cos(w0); 1008 double b2 = (1.0 - cos(w0)) / 2.0; 1009 double a0 = 1.0 + alpha; 1010 double a1 = -2.0 * cos(w0); 1011 double a2 = 1.0 - alpha; 1012 1013 v->noise_b0 = b0 / a0; 1014 v->noise_b1 = b1 / a0; 1015 v->noise_b2 = b2 / a0; 1016 v->noise_a1 = a1 / a0; 1017 v->noise_a2 = a2 / a0; 1018 v->noise_x1 = v->noise_x2 = v->noise_y1 = v->noise_y2 = 0.0; 1019} 1020 1021// ============================================================ 1022// Audio thread 1023// ============================================================ 1024 1025#define ROOM_DELAY_SAMPLES (int)(0.12 * AUDIO_SAMPLE_RATE) // 120ms 1026#define ROOM_SIZE (ROOM_DELAY_SAMPLES * 3) 1027#define ROOM_FEEDBACK 0.3 1028#define ROOM_MIX 0.35 1029 1030// Soft clamp (tanh-style) to prevent harsh digital clipping 1031// Smooth curve: starts compressing gently above 0.6, hard-limits at ~0.95 1032static 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)); 1036 } 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 } 1041 return x; 1042} 1043 1044// Compressor state (per-channel peak follower) 1045static double comp_env = 0.0; // envelope follower level 1046static unsigned long xrun_count = 0; 1047static unsigned long short_write_count = 0; 1048 1049static void mix_sample_voice(SampleVoice *sv, const float *buf, int slen, int smax, 1050 double rate, double *mix_l, double *mix_r) { 1051 if (!sv || !sv->active || !buf || slen <= 0 || smax <= 0) { 1052 if (sv) sv->active = 0; 1053 return; 1054 } 1055 1056 if (slen > smax) slen = smax; 1057 1058 // Fade envelope (5ms attack/release at output rate) 1059 double fade_speed = 1.0 / (0.005 * rate); 1060 if (sv->fade < sv->fade_target) { 1061 sv->fade += fade_speed; 1062 if (sv->fade > sv->fade_target) sv->fade = sv->fade_target; 1063 } else if (sv->fade > sv->fade_target) { 1064 sv->fade -= fade_speed; 1065 if (sv->fade <= 0.0) { sv->fade = 0.0; sv->active = 0; return; } 1066 } 1067 1068 // Pan controls both amplitude and a small Haas-style stereo offset. 1069 double delay_samps = sv->pan * 0.0004 * rate; 1070 double pos_l = sv->position - (delay_samps > 0 ? delay_samps : 0); 1071 double pos_r = sv->position + (delay_samps > 0 ? 0 : delay_samps); 1072 if (pos_l < 0) pos_l = 0; 1073 if (pos_r < 0) pos_r = 0; 1074 1075 int p0l = (int)pos_l; 1076 if (sv->loop) { 1077 p0l = ((p0l % slen) + slen) % slen; 1078 } else if (p0l >= slen) { 1079 sv->active = 0; 1080 return; 1081 } 1082 int p1l = p0l + 1; 1083 if (p1l >= slen) p1l = sv->loop ? 0 : p0l; 1084 if (p0l >= smax || p1l >= smax) { sv->active = 0; return; } 1085 double fl = pos_l - p0l; 1086 double samp_l = buf[p0l] * (1.0 - fl) + buf[p1l] * fl; 1087 1088 int p0r = (int)pos_r; 1089 if (sv->loop) { 1090 p0r = ((p0r % slen) + slen) % slen; 1091 } else if (p0r >= slen) { 1092 p0r = slen - 1; 1093 } 1094 if (p0r < 0) p0r = 0; 1095 int p1r = p0r + 1; 1096 if (p1r >= slen) p1r = sv->loop ? 0 : p0r; 1097 if (p0r >= smax || p1r >= smax) { sv->active = 0; return; } 1098 double fr = pos_r - p0r; 1099 double samp_r = buf[p0r] * (1.0 - fr) + buf[p1r] * fr; 1100 1101 double vol = sv->volume * sv->fade; 1102 double l_gain = sv->pan <= 0 ? 1.0 : 1.0 - sv->pan * 0.6; 1103 double r_gain = sv->pan >= 0 ? 1.0 : 1.0 + sv->pan * 0.6; 1104 *mix_l += samp_l * vol * l_gain; 1105 *mix_r += samp_r * vol * r_gain; 1106 1107 sv->position += sv->speed; 1108 if (sv->position >= slen) { 1109 if (sv->loop) { 1110 while (sv->position >= slen) sv->position -= slen; 1111 } else { 1112 sv->active = 0; 1113 } 1114 } else if (sv->position < 0.0) { 1115 if (sv->loop) { 1116 while (sv->position < 0.0) sv->position += slen; 1117 } else { 1118 sv->active = 0; 1119 } 1120 } 1121} 1122 1123static void *audio_thread_fn(void *arg) { 1124 ACAudio *audio = (ACAudio *)arg; 1125 const unsigned int period_frames = audio->actual_period ? audio->actual_period : AUDIO_PERIOD_SIZE; 1126 int16_t *buffer = calloc(period_frames * AUDIO_CHANNELS, sizeof(int16_t)); 1127 int32_t *buffer32 = NULL; 1128 if (audio->use_s32) 1129 buffer32 = calloc(period_frames * AUDIO_CHANNELS, sizeof(int32_t)); 1130 if (!buffer || (audio->use_s32 && !buffer32)) { 1131 fprintf(stderr, "[audio] thread: alloc failed\n"); return NULL; 1132 } 1133 const double rate = (double)(audio->actual_rate ? audio->actual_rate : AUDIO_SAMPLE_RATE); 1134 const double dt = 1.0 / rate; 1135 double mix_divisor = 1.0; // Smooth auto-mix (matches speaker.mjs) 1136 // Auto-mix smoothing: fast-ish attack, slower release to avoid zipper clicks. 1137 const double mix_att_coeff = 1.0 - exp(-1.0 / (0.004 * rate)); // ~4ms 1138 const double mix_rel_coeff = 1.0 - exp(-1.0 / (0.060 * rate)); // ~60ms 1139 1140 // Drum bus peak compressor — gives percussion proper "stacking" feel. 1141 // The drum bus sums additively (no auto-mix divide) so rapid hits 1142 // would otherwise saturate through soft_clip tanh, flattening peaks 1143 // and making each new hit sound QUIETER. A real peak compressor 1144 // with fast attack / slower release keeps the drum bus below ~0.95 1145 // so transients retain impact AND the compressor recovers between 1146 // hits so each kick/snare feels punchy on its own. 1147 double drum_gain = 1.0; 1148 const double DRUM_THRESH = 0.95; 1149 // 5ms attack — slower than a 2ms beater transient so the first peak 1150 // of each hit passes through at full amplitude before compression 1151 // engages. This preserves the "snap" of each individual kick/snare. 1152 const double drum_att_coeff = 1.0 - exp(-1.0 / (0.005 * rate)); 1153 // 200ms release — recovers quickly enough that successive hits at 1154 // typical tempos (120-200 BPM, 300-500ms between hits) each get 1155 // the benefit of full dynamic range. 1156 const double drum_rel_coeff = 1.0 - exp(-1.0 / (0.200 * rate)); 1157 1158 // Set real-time priority to prevent audio glitches from background tasks 1159 struct sched_param sp = { .sched_priority = 50 }; 1160 if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) != 0) 1161 fprintf(stderr, "[audio] Warning: couldn't set RT priority\n"); 1162 1163 while (audio->running) { 1164 memset(buffer, 0, sizeof(buffer)); 1165 1166 pthread_mutex_lock(&audio->lock); 1167 1168 for (unsigned int i = 0; i < period_frames; i++) { 1169 // Split the voice bus in two: TONES get auto-mix normalization 1170 // (divide by total voice weight so held chords stay balanced), 1171 // DRUMS stack additively (so a kick+snare+hat transient sums to 1172 // a louder peak instead of ducking itself). soft_clip at the end 1173 // catches any drum peak excess with tanh saturation — which 1174 // gives percussion a natural analog "push" character. 1175 // 1176 // Heuristic: a voice is percussive if it has a SHORT FINITE 1177 // duration (< 0.5s). Held tones (duration = Infinity) and 1178 // long one-shot tones always go through the auto-mix bus. 1179 double tone_l = 0.0, tone_r = 0.0; 1180 double drum_l = 0.0, drum_r = 0.0; 1181 double voice_sum = 0.0; // Tone-only voice weight for auto-mix 1182 1183 for (int v = 0; v < AUDIO_MAX_VOICES; v++) { 1184 ACVoice *voice = &audio->voices[v]; 1185 if (voice->state == VOICE_INACTIVE) continue; 1186 1187 double s = generate_sample(voice, rate); 1188 double env = compute_envelope(voice); 1189 double fade = compute_fade(voice); 1190 double amp = s * env * fade * voice->volume; 1191 1192 double left_gain = (1.0 - voice->pan) * 0.5; 1193 double right_gain = (1.0 + voice->pan) * 0.5; 1194 1195 int is_percussive = !isinf(voice->duration) && voice->duration < 0.5; 1196 if (is_percussive) { 1197 // Drum bus — no auto-mix normalization. Drums stack 1198 // additively and rely on soft_clip for peak control. 1199 drum_l += amp * left_gain; 1200 drum_r += amp * right_gain; 1201 } else { 1202 // Tone bus — contributes to voice_sum for auto-mix. 1203 tone_l += amp * left_gain; 1204 tone_r += amp * right_gain; 1205 if (voice->state == VOICE_KILLING) { 1206 voice_sum += voice->volume * (1.0 - voice->fade_elapsed / voice->fade_duration); 1207 } else { 1208 voice_sum += voice->volume; 1209 } 1210 } 1211 1212 voice->elapsed += dt; 1213 if (voice->state == VOICE_KILLING) { 1214 voice->fade_elapsed += dt; 1215 if (voice->fade_elapsed >= voice->fade_duration) 1216 voice->state = VOICE_INACTIVE; 1217 } else if (!isinf(voice->duration) && voice->elapsed >= voice->duration) { 1218 voice->state = VOICE_INACTIVE; 1219 } 1220 } 1221 1222 // Smooth auto-mix divisor — fast attack, slow release. 1223 // Applied ONLY to the tone bus. Drums bypass it entirely. 1224 double target = voice_sum > 1.0 ? voice_sum : 1.0; 1225 if (mix_divisor < target) 1226 mix_divisor += (target - mix_divisor) * mix_att_coeff; 1227 else if (mix_divisor > target) 1228 mix_divisor += (target - mix_divisor) * mix_rel_coeff; 1229 if (mix_divisor < 1.0) mix_divisor = 1.0; 1230 1231 tone_l /= mix_divisor; 1232 tone_r /= mix_divisor; 1233 1234 // Drum bus peak compressor: detect peak, attack fast if over 1235 // threshold, release slow. Unlike the tone auto-mix divide, 1236 // this preserves individual hit dynamics — a single drum hit 1237 // passes through at full amplitude, but sustained buildup 1238 // from overlapping hits gets gain-reduced gracefully so they 1239 // stack linearly instead of saturating through soft_clip. 1240 { 1241 double peak = fabs(drum_l); 1242 double peak_r = fabs(drum_r); 1243 if (peak_r > peak) peak = peak_r; 1244 double target = (peak > DRUM_THRESH) ? (DRUM_THRESH / peak) : 1.0; 1245 if (target < drum_gain) { 1246 drum_gain += (target - drum_gain) * drum_att_coeff; 1247 } else { 1248 drum_gain += (target - drum_gain) * drum_rel_coeff; 1249 } 1250 drum_l *= drum_gain; 1251 drum_r *= drum_gain; 1252 } 1253 1254 // Merge the two buses. Drums land compressed to ~0.95 peak 1255 // so they retain impact without saturating the final output. 1256 double mix_l = tone_l + drum_l; 1257 double mix_r = tone_r + drum_r; 1258 1259 // Mix sample voices (pitch-shifted playback) 1260 // Lock already held from line 246 — safe to read sample_buf 1261 for (int v = 0; v < AUDIO_MAX_SAMPLE_VOICES; v++) { 1262 SampleVoice *sv = &audio->sample_voices[v]; 1263 mix_sample_voice(sv, audio->sample_buf, audio->sample_len, audio->sample_max_len, 1264 rate, &mix_l, &mix_r); 1265 } 1266 1267 // Dedicated global replay voice. Uses its own buffer so reverse 1268 // playback can overlap normal sample-bank activity. 1269 mix_sample_voice(&audio->replay_voice, audio->replay_buf, 1270 audio->replay_len, audio->replay_max_len, 1271 rate, &mix_l, &mix_r); 1272 // (lock released at end of buffer loop) 1273 1274 // Mix DJ deck audio (lock-free: single consumer = audio thread) 1275 // Speed control: advance ring read by `speed` samples per output sample 1276 // with linear interpolation for smooth pitch shifting / scratching. 1277 for (int d = 0; d < AUDIO_MAX_DECKS; d++) { 1278 ACDeck *dk = &audio->decks[d]; 1279 if (!dk->active || !dk->playing || !dk->decoder) continue; 1280 ACDeckDecoder *dec = dk->decoder; 1281 double spd = dec->speed; 1282 if (spd < -4.0) spd = -4.0; 1283 if (spd > 4.0) spd = 4.0; 1284 int64_t avail = dec->ring_write - dec->ring_read; 1285 if (avail <= 1) continue; 1286 // Fractional ring position for interpolation 1287 double frac_pos = dec->ring_frac; 1288 int64_t base = dec->ring_read; 1289 int64_t idx0 = base + (int64_t)frac_pos; 1290 if (idx0 < base || idx0 + 1 >= dec->ring_write) { 1291 // Not enough data — skip 1292 continue; 1293 } 1294 double t = frac_pos - (int64_t)frac_pos; 1295 int ri0 = (idx0 % dec->ring_size) * 2; 1296 int ri1 = ((idx0 + 1) % dec->ring_size) * 2; 1297 float sl = dec->ring[ri0] * (1.0f - (float)t) + dec->ring[ri1] * (float)t; 1298 float sr = dec->ring[ri0 + 1] * (1.0f - (float)t) + dec->ring[ri1 + 1] * (float)t; 1299 // Advance fractional position by speed 1300 dec->ring_frac += spd; 1301 // Consume whole samples from ring 1302 int consumed = (int)dec->ring_frac; 1303 if (consumed > 0) { 1304 dec->ring_read += consumed; 1305 dec->ring_frac -= consumed; 1306 } else if (consumed < 0) { 1307 // Reverse: clamp to not go before ring_read 1308 // (reverse scratching won't replay old audio, just stops) 1309 dec->ring_frac = 0; 1310 } 1311 // Crossfader: 0.0 = full deck A, 1.0 = full deck B 1312 float cf = (d == 0) 1313 ? (1.0f - audio->crossfader) 1314 : audio->crossfader; 1315 float vol = dk->volume * cf * audio->deck_master_volume; 1316 mix_l += sl * vol; 1317 mix_r += sr * vol; 1318 // Wake decoder thread if ring drained below 50% 1319 if ((dec->ring_write - dec->ring_read) < dec->ring_size / 2) { 1320 pthread_mutex_lock(&dec->mutex); 1321 pthread_cond_signal(&dec->cond); 1322 pthread_mutex_unlock(&dec->mutex); 1323 } 1324 } 1325 1326 // Smooth room_mix toward target (~10ms at 192kHz) 1327 if (audio->room_mix != audio->target_room_mix) { 1328 audio->room_mix += (audio->target_room_mix - audio->room_mix) * 0.00005f; 1329 } 1330 1331 // Smooth fx_mix toward target 1332 if (audio->fx_mix != audio->target_fx_mix) { 1333 audio->fx_mix += (audio->target_fx_mix - audio->fx_mix) * 0.00005f; 1334 } 1335 1336 // Smooth bitcrush mix toward target 1337 if (audio->glitch_mix != audio->target_glitch_mix) { 1338 audio->glitch_mix += (audio->target_glitch_mix - audio->glitch_mix) * 0.00005f; 1339 } 1340 1341 // Save dry signal before FX chain 1342 double dry_l = mix_l, dry_r = mix_r; 1343 1344 // Capture recent dry output for true reverse replay. This stores 1345 // the actual mixed audio (not note events) before room/glitch/TTS 1346 // so the reverse replay can run back through the live FX chain. 1347 if (audio->output_history_buf && audio->output_history_size > 0) { 1348 unsigned int stride = audio->output_history_downsample_n; 1349 if (stride == 0) stride = 1; 1350 audio->output_history_downsample_pos++; 1351 if (audio->output_history_downsample_pos >= stride) { 1352 audio->output_history_downsample_pos = 0; 1353 uint64_t wp = audio->output_history_write_pos; 1354 audio->output_history_buf[wp % (uint64_t)audio->output_history_size] = 1355 (float)((dry_l + dry_r) * 0.5); 1356 audio->output_history_write_pos = wp + 1; 1357 } 1358 } 1359 1360 // Room (reverb) effect — tap delays based on actual sample rate 1361 if (audio->room_enabled && audio->room_buf_l) { 1362 float rmix = audio->room_mix; 1363 int rs = audio->room_size; 1364 1365 // At 0% mix, skip all reverb processing (no buffer feed, no output) 1366 if (rmix > 0.001f) { 1367 int room_delay = (int)(0.12 * rate); // 120ms in samples 1368 int tap1 = (audio->room_pos - room_delay + rs) % rs; 1369 int tap2 = (audio->room_pos - room_delay * 2 + rs) % rs; 1370 int tap3 = (audio->room_pos - room_delay * 3 + rs) % rs; 1371 1372 // Weighted sum of taps, normalized 1373 float wet_l = (audio->room_buf_l[tap1] * 0.5f 1374 + audio->room_buf_l[tap2] * 0.3f 1375 + audio->room_buf_l[tap3] * 0.2f); 1376 float wet_r = (audio->room_buf_r[tap1] * 0.5f 1377 + audio->room_buf_r[tap2] * 0.3f 1378 + audio->room_buf_r[tap3] * 0.2f); 1379 1380 // Feed buffer: dry input + attenuated wet feedback 1381 float fb_l = (float)mix_l + wet_l * ROOM_FEEDBACK; 1382 float fb_r = (float)mix_r + wet_r * ROOM_FEEDBACK; 1383 // Damping — ensures reverb tail always decays 1384 fb_l *= 0.995f; 1385 fb_r *= 0.995f; 1386 // Soft-limit feedback to avoid hard-clamp discontinuities under transients. 1387 fb_l = tanhf(fb_l * 0.65f) / 0.65f; 1388 fb_r = tanhf(fb_r * 0.65f) / 0.65f; 1389 audio->room_buf_l[audio->room_pos] = fb_l; 1390 audio->room_buf_r[audio->room_pos] = fb_r; 1391 1392 // Mix wet into output 1393 mix_l = mix_l * (1.0 - rmix) + wet_l * rmix; 1394 mix_r = mix_r * (1.0 - rmix) + wet_r * rmix; 1395 } else { 1396 // Mix is ~0%: just clear the current buffer position (drain residue) 1397 audio->room_buf_l[audio->room_pos] = 0.0f; 1398 audio->room_buf_r[audio->room_pos] = 0.0f; 1399 } 1400 audio->room_pos = (audio->room_pos + 1) % rs; 1401 } 1402 1403 // Glitch (sample-hold + bitcrush) 1404 // `glitch_mix` scales the intensity of the stage itself, while 1405 // `fx_mix` still controls the dry/wet blend of the whole FX chain. 1406 { 1407 float gmix = audio->glitch_mix; 1408 if (gmix > 0.001f) { 1409 float crush = gmix * gmix; 1410 int hold_interval = 1 + (int)roundf((float)(audio->glitch_rate - 1) * crush); 1411 int bits = 12 - (int)roundf(gmix * 8.0f); // 12-bit -> 4-bit 1412 if (bits < 4) bits = 4; 1413 if (bits > 12) bits = 12; 1414 int levels = 1 << bits; 1415 1416 audio->glitch_counter++; 1417 if (audio->glitch_counter >= hold_interval) { 1418 audio->glitch_counter = 0; 1419 audio->glitch_hold_l = roundf((float)mix_l * levels) / levels; 1420 audio->glitch_hold_r = roundf((float)mix_r * levels) / levels; 1421 } 1422 1423 mix_l = mix_l * (1.0f - gmix) + audio->glitch_hold_l * gmix; 1424 mix_r = mix_r * (1.0f - gmix) + audio->glitch_hold_r * gmix; 1425 } 1426 } 1427 1428 // Blend dry/wet based on FX mix 1429 { 1430 float fxm = audio->fx_mix; 1431 if (fxm < 0.999f) { 1432 mix_l = dry_l * (1.0 - fxm) + mix_l * fxm; 1433 mix_r = dry_r * (1.0 - fxm) + mix_r * fxm; 1434 } 1435 } 1436 1437 // Mix in TTS audio after FX chain (bypasses reverb/glitch) 1438 // Fade envelope prevents hard-start/stop clicks 1439 { 1440 int tts_has_data = audio->tts_buf && 1441 (audio->tts_read_pos != audio->tts_write_pos); 1442 // ~3ms ramp at 192kHz (1/576 per sample) 1443 float ramp = 1.0f / 576.0f; 1444 if (tts_has_data) { 1445 audio->tts_fade += ramp; 1446 if (audio->tts_fade > 1.0f) audio->tts_fade = 1.0f; 1447 float tts_sample = audio->tts_buf[audio->tts_read_pos] 1448 * audio->tts_volume * audio->tts_fade; 1449 mix_l += tts_sample; 1450 mix_r += tts_sample; 1451 audio->tts_read_pos = (audio->tts_read_pos + 1) % audio->tts_buf_size; 1452 } else { 1453 // Fade out: keep adding the last scaled zero-ish sample 1454 if (audio->tts_fade > 0.0f) { 1455 audio->tts_fade -= ramp; 1456 if (audio->tts_fade < 0.0f) audio->tts_fade = 0.0f; 1457 } 1458 } 1459 } 1460 1461 // Compressor: peak-following gain reduction (threshold 0.4, ratio ~8:1) 1462 { 1463 double peak = fabs(mix_l); 1464 double pr = fabs(mix_r); 1465 if (pr > peak) peak = pr; 1466 // Attack: very fast (0.2ms), Release: medium (40ms) 1467 double att_coeff = 1.0 - exp(-1.0 / (0.0002 * rate)); 1468 double rel_coeff = 1.0 - exp(-1.0 / (0.04 * rate)); 1469 if (peak > comp_env) 1470 comp_env += att_coeff * (peak - comp_env); 1471 else 1472 comp_env += rel_coeff * (peak - comp_env); 1473 if (comp_env > 0.4) { 1474 double gain = 0.4 + (comp_env - 0.4) * 0.125; // ~8:1 ratio above threshold 1475 double reduction = gain / comp_env; 1476 mix_l *= reduction; 1477 mix_r *= reduction; 1478 } 1479 } 1480 1481 // Apply system volume (software gain — hardware mixer may not attenuate) 1482 { 1483 int sv = audio->system_volume; 1484 // -1 means no Master mixer found (SOF cards) — treat as 100% 1485 if (sv < 0) sv = 100; 1486 double vol = sv * 0.01; // 0-100 → 0.0-1.0 1487 // Use squared curve for more natural volume perception 1488 vol = vol * vol; 1489 mix_l *= vol; 1490 mix_r *= vol; 1491 } 1492 1493 // Soft clip and convert to int16 1494 mix_l = soft_clip(mix_l); 1495 mix_r = soft_clip(mix_r); 1496 1497 buffer[i * 2] = (int16_t)(mix_l * 26000); 1498 buffer[i * 2 + 1] = (int16_t)(mix_r * 26000); 1499 1500 /* DAPM keepalive: inject ±1 LSB when the buffer would 1501 * otherwise be all zeros. At S32_LE (after <<16) this 1502 * becomes ±65536 ≈ -90 dBFS — utterly inaudible. 1503 * Previous ±160 was audible as 24kHz fizz through the 1504 * amp. ±1 is enough to prevent the SOF DSP silence 1505 * detector from powering down the SSP1 BE DAI. */ 1506 if (buffer[i * 2] == 0 && buffer[i * 2 + 1] == 0) { 1507 buffer[i * 2] = (i & 1) ? 1 : -1; 1508 buffer[i * 2 + 1] = (i & 1) ? -1 : 1; 1509 } 1510 1511 // HDMI audio: 1-pole low-pass filter + downsample 1512 // (volume already applied above to mix_l/mix_r) 1513 if (audio->hdmi_pcm) { 1514 // LP filter (alpha ≈ 0.18 → ~3kHz cutoff at 48kHz) 1515 float alpha = 0.18f; 1516 audio->hdmi_lp_l = alpha * (float)mix_l + (1.0f - alpha) * audio->hdmi_lp_l; 1517 audio->hdmi_lp_r = alpha * (float)mix_r + (1.0f - alpha) * audio->hdmi_lp_r; 1518 // Downsample: one HDMI sample per N primary samples 1519 audio->hdmi_downsample_pos++; 1520 if (audio->hdmi_downsample_pos >= audio->hdmi_downsample_n) { 1521 audio->hdmi_downsample_pos = 0; 1522 int pp = audio->hdmi_period_pos; 1523 if (pp + 1 < (int)(sizeof(audio->hdmi_period) / sizeof(int16_t)) / 2) { 1524 audio->hdmi_period[pp * 2] = (int16_t)(audio->hdmi_lp_l * 28000); 1525 audio->hdmi_period[pp * 2 + 1] = (int16_t)(audio->hdmi_lp_r * 28000); 1526 audio->hdmi_period_pos++; 1527 if (audio->hdmi_period_pos >= audio->hdmi_period_size) { 1528 snd_pcm_t *hpcm = (snd_pcm_t *)audio->hdmi_pcm; 1529 int hw = snd_pcm_writei(hpcm, audio->hdmi_period, 1530 audio->hdmi_period_size); 1531 if (hw == -EPIPE || hw == -ESTRPIPE) 1532 snd_pcm_recover(hpcm, hw, 1); 1533 audio->hdmi_period_pos = 0; 1534 } 1535 } 1536 } 1537 } 1538 1539 // Store waveform for visualization 1540 int wp = audio->waveform_pos; 1541 audio->waveform_left[wp] = (float)mix_l; 1542 audio->waveform_right[wp] = (float)mix_r; 1543 audio->waveform_pos = (wp + 1) % AUDIO_WAVEFORM_SIZE; 1544 1545 // Track amplitude 1546 float al = fabsf((float)mix_l); 1547 float ar = fabsf((float)mix_r); 1548 audio->amplitude_left = audio->amplitude_left * 0.99f + al * 0.01f; 1549 audio->amplitude_right = audio->amplitude_right * 0.99f + ar * 0.01f; 1550 } 1551 1552 // BPM metronome 1553 audio->beat_elapsed += (double)period_frames * dt; 1554 double beat_interval = 60.0 / audio->bpm; 1555 if (audio->beat_elapsed >= beat_interval) { 1556 audio->beat_elapsed -= beat_interval; 1557 audio->beat_triggered = 1; 1558 } 1559 1560 pthread_mutex_unlock(&audio->lock); 1561 1562 audio->total_frames += period_frames; 1563 audio->time = (double)audio->total_frames / rate; 1564 1565 // Recording tap: send mixed PCM to recorder (if active). Used by 1566 // the MP4 tape recorder (recorder.c) for the audio track. 1567 if (audio->rec_callback) 1568 audio->rec_callback(buffer, period_frames, audio->rec_userdata); 1569 1570 // Write to ALSA (handle short writes to avoid dropped samples/clicks) 1571 snd_pcm_t *pcm = (snd_pcm_t *)audio->pcm; 1572 /* Tee to parallel PCM (sof-rt5682+max98360a auto-route). 1573 * Best-effort, never blocks the primary write — short writes, 1574 * EPIPE underruns, and even outright failures are tolerated 1575 * because the *real* output is the primary PCM. The DAPM 1576 * jack-sense in the codec mutes whichever side isn't being 1577 * driven by the active jack state. */ 1578 snd_pcm_t *pcm2 = (snd_pcm_t *)audio->headphone_pcm; 1579 if (pcm2) { 1580 int rem2 = (int)period_frames; 1581 int off2 = 0; 1582 while (rem2 > 0) { 1583 int f2 = snd_pcm_writei(pcm2, 1584 buffer + off2 * AUDIO_CHANNELS, 1585 rem2); 1586 if (f2 == -EAGAIN) break; /* don't spin on secondary */ 1587 if (f2 < 0) { 1588 snd_pcm_recover(pcm2, f2, 1); 1589 break; 1590 } 1591 rem2 -= f2; off2 += f2; 1592 } 1593 } 1594 /* Widen int16→int32 for S32_LE PCMs (SOF topology). 1595 * The SSP1 BE DAI runs S24_LE. SOF DSP uses the bottom 24 1596 * bits of the S32 container (bits 23:0). Shifting int16 by 1597 * 8 places our 16-bit audio in bits 23:8, which fills the 1598 * top portion of the 24-bit window — correct for S24-in-S32 1599 * bottom-aligned format. (<<16 put data in bits 31:16 which 1600 * the DSP's 24-bit window barely saw → super quiet.) */ 1601 const void *write_buf = buffer; 1602 if (buffer32) { 1603 for (int j = 0; j < (int)(period_frames * AUDIO_CHANNELS); j++) 1604 buffer32[j] = (int32_t)buffer[j] << 8; 1605 write_buf = buffer32; 1606 } 1607 int remaining = (int)period_frames; 1608 int offset = 0; 1609 while (remaining > 0) { 1610 const void *wptr = buffer32 1611 ? (const void *)(buffer32 + offset * AUDIO_CHANNELS) 1612 : (const void *)(buffer + offset * AUDIO_CHANNELS); 1613 int frames = snd_pcm_writei(pcm, wptr, remaining); 1614 if (frames == -EAGAIN) continue; 1615 if (frames < 0) { 1616 int rec = snd_pcm_recover(pcm, frames, 1); 1617 if (frames == -EPIPE || frames == -ESTRPIPE) { 1618 xrun_count++; 1619 if ((xrun_count % 32) == 1) { 1620 fprintf(stderr, "[audio] XRUN recovered x%lu\n", xrun_count); 1621 } 1622 } 1623 if (rec < 0) { 1624 fprintf(stderr, "[audio] ALSA write failed: %s\n", snd_strerror(rec)); 1625 break; 1626 } 1627 continue; 1628 } 1629 if (frames == 0) continue; 1630 if (frames < remaining) { 1631 short_write_count++; 1632 if ((short_write_count % 64) == 1) { 1633 fprintf(stderr, "[audio] Short write x%lu (%d/%d)\n", 1634 short_write_count, frames, remaining); 1635 } 1636 } 1637 remaining -= frames; 1638 offset += frames; 1639 } 1640 } 1641 1642 free(buffer); 1643 free(buffer32); 1644 return NULL; 1645} 1646 1647// ============================================================ 1648// Public API 1649// ============================================================ 1650 1651// Seed a small default sample so sample mode is playable before first mic recording. 1652// This roughly matches the "startup" one-shot feel used in web notepat. 1653static void seed_default_sample(ACAudio *audio) { 1654 if (!audio || !audio->sample_buf || audio->sample_max_len <= 0) return; 1655 1656 const unsigned int rate = 48000; 1657 int len = (int)(0.55 * (double)rate); // 550ms one-shot 1658 if (len > audio->sample_max_len) len = audio->sample_max_len; 1659 1660 double p1 = 0.0, p2 = 0.0, p3 = 0.0; 1661 for (int i = 0; i < len; i++) { 1662 double t = (double)i / (double)rate; 1663 double env = exp(-6.0 * t) * (1.0 - exp(-35.0 * t)); // fast attack, exponential decay 1664 double f0 = 240.0 + 50.0 * sin(t * 6.0); // slight wobble 1665 double f1 = f0 * 2.01; 1666 double f2 = f0 * 3.02; 1667 p1 += 2.0 * M_PI * f0 / (double)rate; 1668 p2 += 2.0 * M_PI * f1 / (double)rate; 1669 p3 += 2.0 * M_PI * f2 / (double)rate; 1670 1671 double s = 0.78 * sin(p1) + 0.22 * sin(p2 + 0.25) + 0.08 * sin(p3 + 0.15); 1672 audio->sample_buf[i] = (float)(s * env * 0.85); 1673 } 1674 for (int i = len; i < audio->sample_max_len; i++) audio->sample_buf[i] = 0.0f; 1675 1676 audio->sample_len = len; 1677 audio->sample_rate = rate; 1678 ac_log("[sample] seeded default startup sample (%d frames @ %u Hz)\n", 1679 audio->sample_len, audio->sample_rate); 1680} 1681 1682ACAudio *audio_init(void) { 1683 ACAudio *audio = calloc(1, sizeof(ACAudio)); 1684 if (!audio) return NULL; 1685 1686 audio->bpm = 120.0; 1687 audio->actual_rate = AUDIO_SAMPLE_RATE; // default, overwritten after ALSA negotiation 1688 audio->glitch_rate = AUDIO_SAMPLE_RATE / 1600; 1689 pthread_mutex_init(&audio->lock, NULL); 1690 1691 // Allocate reverb buffers 1692 audio->room_size = ROOM_SIZE; 1693 audio->room_mix = 0.0f; // Start dry, trackpad Y controls 1694 audio->room_enabled = 1; // Always on, mix controls wet amount 1695 audio->glitch_mix = 0.0f; 1696 audio->target_glitch_mix = 0.0f; 1697 audio->fx_mix = 1.0f; // FX chain fully wet by default 1698 audio->target_fx_mix = 1.0f; 1699 audio->room_buf_l = calloc(ROOM_SIZE, sizeof(float)); 1700 audio->room_buf_r = calloc(ROOM_SIZE, sizeof(float)); 1701 1702 // Sample buffer (10 seconds at max 48kHz capture rate) 1703 audio->sample_max_len = 48000 * AUDIO_MAX_SAMPLE_SECS; 1704 audio->sample_buf = calloc(audio->sample_max_len, sizeof(float)); 1705 audio->sample_buf_back = calloc(audio->sample_max_len, sizeof(float)); 1706 audio->sample_len = 0; 1707 audio->sample_rate = 48000; // default, overwritten by actual capture rate 1708 audio->sample_next_id = 1; 1709 audio->replay_max_len = AUDIO_OUTPUT_HISTORY_RATE * AUDIO_OUTPUT_HISTORY_SECS; 1710 audio->replay_buf = calloc(audio->replay_max_len, sizeof(float)); 1711 audio->replay_buf_back = calloc(audio->replay_max_len, sizeof(float)); 1712 audio->replay_len = 0; 1713 audio->replay_rate = AUDIO_OUTPUT_HISTORY_RATE; 1714 memset(&audio->replay_voice, 0, sizeof(audio->replay_voice)); 1715 audio->mic_connected = 0; 1716 audio->mic_hot = 0; 1717 audio->mic_level = 0.0f; 1718 audio->mic_last_chunk = 0; 1719 audio->capture_thread_running = 0; 1720 memset(audio->mic_waveform, 0, sizeof(audio->mic_waveform)); 1721 audio->mic_waveform_pos = 0; 1722 audio->mic_ring = calloc(audio->sample_max_len, sizeof(float)); 1723 audio->mic_ring_pos = 0; 1724 audio->rec_start_ring_pos = 0; 1725 audio->output_history_buf = calloc(AUDIO_OUTPUT_HISTORY_RATE * AUDIO_OUTPUT_HISTORY_SECS, sizeof(float)); 1726 audio->output_history_size = AUDIO_OUTPUT_HISTORY_RATE * AUDIO_OUTPUT_HISTORY_SECS; 1727 audio->output_history_rate = AUDIO_OUTPUT_HISTORY_RATE; 1728 audio->output_history_downsample_n = 1; 1729 audio->output_history_downsample_pos = 0; 1730 audio->output_history_write_pos = 0; 1731 snprintf(audio->mic_device, sizeof(audio->mic_device), "none"); 1732 audio->mic_last_error[0] = 0; 1733 seed_default_sample(audio); 1734 1735 // DJ decks: initialize with default volumes 1736 audio->crossfader = 0.5f; // centered 1737 audio->deck_master_volume = 0.8f; // default master 1738 for (int d = 0; d < AUDIO_MAX_DECKS; d++) { 1739 audio->decks[d].active = 0; 1740 audio->decks[d].playing = 0; 1741 audio->decks[d].volume = 1.0f; 1742 audio->decks[d].decoder = NULL; 1743 } 1744 1745 // TTS PCM ring buffer (5 seconds at max output rate) 1746 audio->tts_buf_size = AUDIO_SAMPLE_RATE * 5; // allocated at max, actual_rate adjusts usage 1747 audio->tts_buf = calloc(audio->tts_buf_size, sizeof(float)); 1748 audio->tts_read_pos = 0; 1749 audio->tts_write_pos = 0; 1750 audio->tts_volume = 2.5f; // Boost flite output (naturally quiet) 1751 1752 snprintf(audio->audio_device, sizeof(audio->audio_device), "none"); 1753 snprintf(audio->audio_status, sizeof(audio->audio_status), "initializing"); 1754 audio->audio_init_retries = 0; 1755 1756 // Wait for sound card to appear 1757 fprintf(stderr, "[audio] Waiting for sound card...\n"); 1758 int card_found = 0; 1759 for (int w = 0; w < 400; w++) { // up to 8 seconds 1760 if (access("/dev/snd/pcmC0D0p", F_OK) == 0 || 1761 access("/dev/snd/pcmC1D0p", F_OK) == 0 || 1762 access("/dev/snd/pcmC2D0p", F_OK) == 0) { card_found = 1; break; } 1763 usleep(20000); 1764 } 1765 if (!card_found) { 1766 // Distinguish: HDA controller present (codec probe failed) vs no hardware at all 1767 if (access("/dev/snd/controlC0", F_OK) == 0) { 1768 fprintf(stderr, "[audio] WARNING: HDA controller present but codec not probed after 8s\n"); 1769 snprintf(audio->audio_status, sizeof(audio->audio_status), "HDA ctrl ok, codec not probed"); 1770 } else { 1771 fprintf(stderr, "[audio] WARNING: no sound card after 8s wait\n"); 1772 snprintf(audio->audio_status, sizeof(audio->audio_status), "no card (8s timeout)"); 1773 } 1774 } 1775 1776 // Dump sound card info for diagnostics (write to USB log if mounted) 1777 FILE *alog = fopen("/mnt/ac-audio.log", "w"); 1778 if (!alog) alog = stderr; // fallback to stderr 1779 { 1780 FILE *cards = fopen("/proc/asound/cards", "r"); 1781 if (cards) { 1782 char line[256]; 1783 fprintf(alog, "[audio] === /proc/asound/cards ===\n"); 1784 while (fgets(line, sizeof(line), cards)) 1785 fprintf(alog, "[audio] %s", line); 1786 fclose(cards); 1787 } else { 1788 fprintf(alog, "[audio] WARNING: /proc/asound/cards not found!\n"); 1789 } 1790 // Also check /dev/snd/ 1791 DIR *snddir = opendir("/dev/snd"); 1792 if (snddir) { 1793 struct dirent *ent; 1794 fprintf(alog, "[audio] /dev/snd/:"); 1795 while ((ent = readdir(snddir))) { 1796 if (ent->d_name[0] != '.') fprintf(alog, " %s", ent->d_name); 1797 } 1798 fprintf(alog, "\n"); 1799 closedir(snddir); 1800 } else { 1801 fprintf(alog, "[audio] WARNING: /dev/snd/ not found!\n"); 1802 } 1803 } 1804 1805 // Open ALSA — try multiple cards and devices, with retries for race conditions. 1806 // On fast NVMe boots the HDA codec may not be fully probed when we first try. 1807 // If AC_AUDIO_DEVICE is set, try it first (used for stream tee via asound.conf). 1808 snd_pcm_t *pcm = NULL; 1809 1810 // SOF rt5682+max98360a (G7/Drawcia and friends) splits playback across two 1811 // PCMs: SSP0 → RT5682 headphones (PCM 0) and SSP1 → MAX98360A speakers 1812 // (PCM 1). The historical hw:0,0 default routes everything to headphones, 1813 // which is why speakers stayed silent even with the Speaker UCM verb fully 1814 // applied. Ask UCM what PCM it maps Speaker(s) to and try that FIRST so 1815 // playback hits the speaker amp by default; the headphone PCM still wins 1816 // if/when AC_AUDIO_DEVICE overrides or jack-sense flips routing later. 1817 char ucm_speaker_pcm[64] = ""; 1818 char ucm_headphone_pcm[64] = ""; 1819 { 1820 char card_id[32] = ""; 1821 int spk_card = 0; /* card index where Speaker UCM lives */ 1822 for (int c = 0; c < 4 && !card_id[0]; c++) { 1823 char p[64]; snprintf(p, sizeof(p), "/proc/asound/card%d/id", c); 1824 FILE *fp = fopen(p, "r"); 1825 if (fp) { 1826 if (fgets(card_id, sizeof(card_id), fp)) { 1827 char *nl = strchr(card_id, '\n'); if (nl) *nl = 0; 1828 } 1829 fclose(fp); 1830 if (card_id[0]) spk_card = c; 1831 } 1832 } 1833 if (card_id[0]) { 1834 const char *cands[] = { card_id, "sof-rt5682", "sof-cs42l42", 1835 "sof-nau8825", "sof-da7219", NULL }; 1836 snd_use_case_mgr_t *uc = NULL; 1837 for (int i = 0; cands[i] && (!ucm_speaker_pcm[0] || !ucm_headphone_pcm[0]); i++) { 1838 if (snd_use_case_mgr_open(&uc, cands[i]) != 0) continue; 1839 if (snd_use_case_set(uc, "_verb", "HiFi") == 0) { 1840 const char *spk_names[] = { "Speaker", "Speakers", NULL }; 1841 for (int s = 0; spk_names[s] && !ucm_speaker_pcm[0]; s++) { 1842 char id[64]; const char *val = NULL; 1843 snprintf(id, sizeof(id), "PlaybackPCM/%s", spk_names[s]); 1844 if (snd_use_case_get(uc, id, &val) == 0 && val) { 1845 /* UCM v2 returns strings like 1846 * "_ucm0002.hw:sofrt5682,0" — that is a 1847 * UCM-internal namespace tag, not a path 1848 * snd_pcm_open accepts. Strip the 1849 * "_ucmNNNN." prefix to get the underlying 1850 * "hw:CARD,DEV" form, then convert the card 1851 * id to a numeric index since snd_pcm_open 1852 * also rejects "hw:sofrt5682,0". */ 1853 const char *clean = val; 1854 const char *dot = strchr(clean, '.'); 1855 if (dot && strncmp(clean, "_ucm", 4) == 0) 1856 clean = dot + 1; 1857 const char *comma = strrchr(clean, ','); 1858 if (comma && strncmp(clean, "hw:", 3) == 0) { 1859 snprintf(ucm_speaker_pcm, 1860 sizeof(ucm_speaker_pcm), 1861 "hw:%d%s", spk_card, comma); 1862 } else { 1863 snprintf(ucm_speaker_pcm, 1864 sizeof(ucm_speaker_pcm), 1865 "%s", clean); 1866 } 1867 fprintf(stderr, 1868 "[audio] UCM Speaker PCM: raw=%s -> %s (%s/%s)\n", 1869 val, ucm_speaker_pcm, cands[i], spk_names[s]); 1870 free((void *)val); 1871 } 1872 } 1873 const char *hp_names[] = { "Headphone", "Headphones", 1874 "Headset", NULL }; 1875 for (int s = 0; hp_names[s] && !ucm_headphone_pcm[0]; s++) { 1876 char id[64]; const char *val = NULL; 1877 snprintf(id, sizeof(id), "PlaybackPCM/%s", hp_names[s]); 1878 if (snd_use_case_get(uc, id, &val) == 0 && val) { 1879 const char *clean = val; 1880 const char *dot = strchr(clean, '.'); 1881 if (dot && strncmp(clean, "_ucm", 4) == 0) 1882 clean = dot + 1; 1883 const char *comma = strrchr(clean, ','); 1884 if (comma && strncmp(clean, "hw:", 3) == 0) { 1885 snprintf(ucm_headphone_pcm, 1886 sizeof(ucm_headphone_pcm), 1887 "hw:%d%s", spk_card, comma); 1888 } else { 1889 snprintf(ucm_headphone_pcm, 1890 sizeof(ucm_headphone_pcm), 1891 "%s", clean); 1892 } 1893 fprintf(stderr, 1894 "[audio] UCM Headphone PCM: raw=%s -> %s (%s/%s)\n", 1895 val, ucm_headphone_pcm, cands[i], hp_names[s]); 1896 free((void *)val); 1897 } 1898 } 1899 } 1900 snd_use_case_mgr_close(uc); 1901 uc = NULL; 1902 } 1903 } 1904 } 1905 1906 /* Build the device probe list. If UCM gave us a Speaker PCM, prepend it 1907 * (and a `plug:` wrapped variant for rate negotiation safety). The legacy 1908 * fallback list still runs after, so non-SOF boards behave as before. */ 1909 const char *devices_default[] = { 1910 "hw:0,0", "hw:1,0", "hw:0,1", "hw:1,1", 1911 "hw:0,2", "hw:0,3", "hw:1,2", "hw:1,3", 1912 "plughw:0,0", "plughw:1,0", 1913 "default", NULL 1914 }; 1915 const char *devices_with_spk[16] = {0}; 1916 const char **devices = devices_default; 1917 char ucm_speaker_plug[80] = ""; 1918 if (ucm_speaker_pcm[0]) { 1919 snprintf(ucm_speaker_plug, sizeof(ucm_speaker_plug), 1920 "plughw%s", ucm_speaker_pcm + 2); /* hw:0,0 → plughw:0,0 */ 1921 int n = 0; 1922 /* Prefer raw hw: first — SOF topology FE PCM runs at S32_LE 1923 * internally, and we now negotiate S32_LE directly so the DSP 1924 * does zero conversion. plughw: as fallback if hw: fails. */ 1925 devices_with_spk[n++] = ucm_speaker_pcm; 1926 devices_with_spk[n++] = ucm_speaker_plug; 1927 for (int i = 0; devices_default[i] && n < 15; i++) 1928 devices_with_spk[n++] = devices_default[i]; 1929 devices_with_spk[n] = NULL; 1930 devices = devices_with_spk; 1931 } 1932 int err = -1; 1933 int card_idx = 0; 1934 1935 // AC_AUDIO_DEVICE override — try the env var device before the hardcoded list. 1936 const char *env_dev = getenv("AC_AUDIO_DEVICE"); 1937 if (env_dev && env_dev[0]) { 1938 err = snd_pcm_open(&pcm, env_dev, SND_PCM_STREAM_PLAYBACK, 0); 1939 if (err >= 0) { 1940 fprintf(stderr, "[audio] Opened AC_AUDIO_DEVICE=%s\n", env_dev); 1941 snprintf(audio->audio_device, sizeof(audio->audio_device), "%s", env_dev); 1942 if (sscanf(env_dev, "hw:%d", &card_idx) != 1 && 1943 sscanf(env_dev, "plughw:%d", &card_idx) != 1) 1944 card_idx = 0; 1945 } else { 1946 fprintf(stderr, "[audio] AC_AUDIO_DEVICE=%s failed: %s — falling back\n", 1947 env_dev, snd_strerror(err)); 1948 } 1949 } 1950 1951 for (int attempt = 0; attempt < 5 && err < 0; attempt++) { 1952 if (attempt > 0) { 1953 fprintf(alog, "[audio] Retry %d/4 — waiting 2s for codec probe...\n", attempt); 1954 fprintf(stderr, "[audio] Retry %d/4 — waiting 2s for codec probe...\n", attempt); 1955 usleep(2000000); // 2 seconds between retries 1956 } 1957 for (int i = 0; devices[i]; i++) { 1958 audio->audio_init_retries++; 1959 err = snd_pcm_open(&pcm, devices[i], SND_PCM_STREAM_PLAYBACK, 0); 1960 if (err >= 0) { 1961 fprintf(alog, "[audio] Opened ALSA device: %s (attempt %d)\n", devices[i], attempt); 1962 fprintf(stderr, "[audio] Opened ALSA device: %s (attempt %d)\n", devices[i], attempt); 1963 snprintf(audio->audio_device, sizeof(audio->audio_device), "%s", devices[i]); 1964 if (sscanf(devices[i], "hw:%d", &card_idx) != 1 && 1965 sscanf(devices[i], "plughw:%d", &card_idx) != 1) 1966 card_idx = 0; 1967 break; 1968 } 1969 if (attempt == 0) 1970 fprintf(alog, "[audio] Failed %s: %s\n", devices[i], snd_strerror(err)); 1971 } 1972 } 1973 audio->card_index = card_idx; 1974 if (alog != stderr) { fflush(alog); fclose(alog); } 1975 if (err < 0) { 1976 fprintf(stderr, "[audio] Cannot open any ALSA device after 5 attempts\n"); 1977 snprintf(audio->audio_status, sizeof(audio->audio_status), "no ALSA device found"); 1978 // Audio is optional — return the struct but with no PCM 1979 audio->pcm = NULL; 1980 return audio; 1981 } 1982 1983 // Configure ALSA — negotiate rate dynamically. 1984 // Try preferred rates from highest to lowest. The hardware decides what it 1985 // actually supports; we adapt period/buffer sizes to match the negotiated rate. 1986 snd_pcm_hw_params_t *params; 1987 snd_pcm_hw_params_alloca(&params); 1988 snd_pcm_hw_params_any(pcm, params); 1989 snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); 1990 /* SOF topology FE PCMs use S32_LE internally; the SSP1 BE DAI 1991 * (MAX98360A) runs S24_LE. Writing S16_LE to this pipeline 1992 * causes 48dB attenuation + quantization noise ("crunchy quiet"). 1993 * Try S32_LE first — if the FE PCM accepts it, we write int32 1994 * samples and the DSP does zero conversion. Fall back to S16_LE 1995 * for non-SOF hardware (HDA, USB, etc). */ 1996 audio->use_s32 = 0; 1997 if (snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S32_LE) == 0) { 1998 audio->use_s32 = 1; 1999 fprintf(stderr, "[audio] Negotiated S32_LE format\n"); 2000 } else { 2001 snd_pcm_hw_params_any(pcm, params); 2002 snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); 2003 snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S16_LE); 2004 fprintf(stderr, "[audio] Negotiated S16_LE format (S32_LE not supported)\n"); 2005 } 2006 snd_pcm_hw_params_set_channels(pcm, params, AUDIO_CHANNELS); 2007 2008 // Query hardware rate range 2009 unsigned int rate_min = 0, rate_max = 0; 2010 snd_pcm_hw_params_get_rate_min(params, &rate_min, NULL); 2011 snd_pcm_hw_params_get_rate_max(params, &rate_max, NULL); 2012 fprintf(stderr, "[audio] Hardware rate range: %u–%u Hz\n", rate_min, rate_max); 2013 2014 // Pick sample rate: 48kHz is the safe default that all hardware can sustain. 2015 // Many codecs (e.g. Cirrus Logic CS4206) claim 192kHz support but can't 2016 // sustain it without constant XRUNs. Only use high rates on known-good 2017 // hardware (ThinkPad HDA with Realtek codec handles 192kHz fine). 2018 // Heuristic: if max rate > 48kHz AND min rate <= 32kHz, the codec is 2019 // likely a laptop HDA that works better at 48kHz. 2020 unsigned int rate = 48000; 2021 if (rate_max >= 192000 && rate_min > 44100) { 2022 // Dedicated audio interface — likely supports high rates reliably 2023 rate = 192000; 2024 } else if (rate_max >= 96000 && rate_min > 44100) { 2025 rate = 96000; 2026 } 2027 // Override: environment variable AC_AUDIO_RATE forces a specific rate 2028 const char *env_rate = getenv("AC_AUDIO_RATE"); 2029 if (env_rate) { 2030 unsigned int r = (unsigned int)atoi(env_rate); 2031 if (r >= rate_min && r <= rate_max) rate = r; 2032 } 2033 fprintf(stderr, "[audio] Selected rate: %u Hz (hw range %u–%u)\n", rate, rate_min, rate_max); 2034 snd_pcm_hw_params_set_rate_near(pcm, params, &rate, 0); 2035 2036 // Period + buffer sizing. The old config aimed for ~1ms latency (period = 2037 // rate/1000, buffer = 4 periods) which works on HDA-direct codecs but 2038 // breaks SOF+MAX98360A on Jasper Lake Chromebooks: the MAX98357A DAPM 2039 // event handler toggles the amp's SD_MODE GPIO on every PMU/PMD event, 2040 // and with a 4ms buffer the stream underruns constantly → DAPM rapid- 2041 // cycles the amp on/off → audio never stabilizes → speakers stay silent 2042 // despite mixer, codec, and GPIO all looking correct. We saw 10,686 2043 // sdmode toggles in a single boot's kmsg on the G7 at the 1ms setting. 2044 // 2045 // Probe whether we're on a SOF platform (sound/soc/sof is one path) and 2046 // bump to 10ms / 40ms which is what ChromeOS itself uses. Non-SOF HDA 2047 // devices (ThinkPads, most laptops) keep the tight latency because their 2048 // amp/codec model doesn't gate on per-period DAPM events. 2049 int sof_active = (access("/sys/class/sound/card0/id", R_OK) == 0) && 2050 (access("/proc/asound/card0/codec97#0", F_OK) != 0); 2051 snd_pcm_uframes_t period; 2052 snd_pcm_uframes_t buffer_size; 2053 if (sof_active) { 2054 period = rate / 100; // 10ms (480 frames at 48kHz) 2055 buffer_size = period * 4; // 40ms total — SOF DAPM-friendly 2056 fprintf(stderr, "[audio] SOF platform detected — period=%lu buffer=%lu (10ms/40ms)\n", 2057 (unsigned long)period, (unsigned long)buffer_size); 2058 } else { 2059 period = rate / 1000; // 1ms on HDA-direct paths 2060 if (period < 64) period = 64; 2061 buffer_size = period * 4; 2062 } 2063 snd_pcm_hw_params_set_period_size_near(pcm, params, &period, 0); 2064 snd_pcm_hw_params_set_buffer_size_near(pcm, params, &buffer_size); 2065 2066 err = snd_pcm_hw_params(pcm, params); 2067 if (err < 0) { 2068 fprintf(stderr, "[audio] Cannot configure ALSA at %uHz: %s\n", rate, snd_strerror(err)); 2069 // Last resort: try plughw with default params 2070 fprintf(stderr, "[audio] Trying plughw fallback...\n"); 2071 snd_pcm_close(pcm); 2072 err = snd_pcm_open(&pcm, "plughw:0,0", SND_PCM_STREAM_PLAYBACK, 0); 2073 if (err >= 0) { 2074 snd_pcm_hw_params_any(pcm, params); 2075 snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); 2076 snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S16_LE); 2077 snd_pcm_hw_params_set_channels(pcm, params, AUDIO_CHANNELS); 2078 rate = 48000; 2079 snd_pcm_hw_params_set_rate_near(pcm, params, &rate, 0); 2080 period = 256; 2081 snd_pcm_hw_params_set_period_size_near(pcm, params, &period, 0); 2082 buffer_size = 1024; 2083 snd_pcm_hw_params_set_buffer_size_near(pcm, params, &buffer_size); 2084 err = snd_pcm_hw_params(pcm, params); 2085 } 2086 if (err < 0) { 2087 fprintf(stderr, "[audio] All ALSA config attempts failed: %s\n", snd_strerror(err)); 2088 snd_pcm_close(pcm); 2089 audio->pcm = NULL; 2090 return audio; 2091 } 2092 snprintf(audio->audio_device, sizeof(audio->audio_device), "plughw:0,0"); 2093 } 2094 2095 snd_pcm_prepare(pcm); 2096 audio->pcm = pcm; 2097 audio->actual_rate = rate; 2098 audio->actual_period = (unsigned int)period; 2099 2100 /* Open the *other* PCM for jack-sense auto-routing. 2101 * 2102 * On sof-rt5682+max98360a, the SOF topology exposes two FE PCMs: 2103 * PCM 0 (UCM "Headphone") → SSP0 → RT5682 codec → headphone jack 2104 * PCM 1 (UCM "Speaker" ) → SSP1 → MAX98360A → speaker amp 2105 * 2106 * Each PCM goes to a *different* DAI, and the codec's DAPM jack-sense 2107 * mutes whichever side isn't currently in use. So if we open BOTH and 2108 * tee the same audio to both, the hardware automatically picks the 2109 * right output: speakers when no jack is plugged, headphones when one 2110 * is plugged. No userspace jack monitor needed. 2111 * 2112 * Only fires when the secondary PCM is a different device than the one 2113 * we already opened — duplicate-open of the same hw: device would just 2114 * fail with -EBUSY. */ 2115 audio->headphone_pcm = NULL; 2116 { 2117 const char *secondary = NULL; 2118 /* Opt-in: opening the headphone PCM at boot was powering up 2119 * the HP DAPM path even with UCM Headphones disabled, which 2120 * overrode jack-sense and silenced the MAX98360A amp. Keep 2121 * the secondary PCM closed by default; a future jack-watcher 2122 * thread will open/close it in response to plug events. Set 2123 * AC_AUDIO_TEE=1 to force-open anyway (for ThinkPad etc. 2124 * single-PCM HDA hardware, where it's a noop). */ 2125 const char *tee_env = getenv("AC_AUDIO_TEE"); 2126 int tee_enabled = (tee_env && tee_env[0] == '1'); 2127 if (tee_enabled && ucm_speaker_pcm[0] && ucm_headphone_pcm[0] && 2128 strcmp(ucm_speaker_pcm, ucm_headphone_pcm) != 0) { 2129 /* Pick whichever the main PCM didn't open. */ 2130 if (strstr(audio->audio_device, ucm_speaker_pcm)) 2131 secondary = ucm_headphone_pcm; 2132 else if (strstr(audio->audio_device, ucm_headphone_pcm)) 2133 secondary = ucm_speaker_pcm; 2134 else 2135 secondary = ucm_headphone_pcm; /* legacy fallback opened */ 2136 } 2137 if (secondary) { 2138 snd_pcm_t *pcm2 = NULL; 2139 int e2 = snd_pcm_open(&pcm2, secondary, 2140 SND_PCM_STREAM_PLAYBACK, 0); 2141 if (e2 == 0) { 2142 /* Same params as the main PCM so audio thread can write 2143 * the same int16 buffer to both without resampling. */ 2144 snd_pcm_hw_params_t *hp; snd_pcm_hw_params_alloca(&hp); 2145 snd_pcm_hw_params_any(pcm2, hp); 2146 snd_pcm_hw_params_set_access(pcm2, hp, 2147 SND_PCM_ACCESS_RW_INTERLEAVED); 2148 snd_pcm_hw_params_set_format(pcm2, hp, SND_PCM_FORMAT_S16_LE); 2149 snd_pcm_hw_params_set_channels(pcm2, hp, AUDIO_CHANNELS); 2150 unsigned int r2 = audio->actual_rate; 2151 snd_pcm_hw_params_set_rate_near(pcm2, hp, &r2, 0); 2152 snd_pcm_uframes_t p2 = audio->actual_period; 2153 snd_pcm_hw_params_set_period_size_near(pcm2, hp, &p2, 0); 2154 snd_pcm_uframes_t b2 = audio->actual_period * 4; 2155 snd_pcm_hw_params_set_buffer_size_near(pcm2, hp, &b2); 2156 int herr = snd_pcm_hw_params(pcm2, hp); 2157 if (herr == 0) { 2158 snd_pcm_prepare(pcm2); 2159 audio->headphone_pcm = pcm2; 2160 fprintf(stderr, 2161 "[audio] Parallel PCM opened: %s (%uHz, %lufrm) — auto-routing enabled\n", 2162 secondary, r2, (unsigned long)p2); 2163 } else { 2164 fprintf(stderr, 2165 "[audio] Parallel PCM hw_params failed for %s: %s — auto-routing disabled\n", 2166 secondary, snd_strerror(herr)); 2167 snd_pcm_close(pcm2); 2168 } 2169 } else { 2170 fprintf(stderr, 2171 "[audio] Parallel PCM open failed for %s: %s — auto-routing disabled\n", 2172 secondary, snd_strerror(e2)); 2173 } 2174 } 2175 } 2176 2177 // Update glitch rate for actual sample rate 2178 audio->glitch_rate = rate / 1600; 2179 2180 // Recent output history targets ~48k mono regardless of playback rate. 2181 unsigned int hist_target = rate > AUDIO_OUTPUT_HISTORY_RATE ? AUDIO_OUTPUT_HISTORY_RATE : rate; 2182 unsigned int hist_stride = rate > hist_target ? (rate + hist_target / 2) / hist_target : 1; 2183 if (hist_stride == 0) hist_stride = 1; 2184 audio->output_history_rate = rate / hist_stride; 2185 if (audio->output_history_rate == 0) audio->output_history_rate = rate; 2186 audio->output_history_downsample_n = hist_stride; 2187 audio->output_history_downsample_pos = 0; 2188 audio->output_history_size = (int)(audio->output_history_rate * AUDIO_OUTPUT_HISTORY_SECS); 2189 if (audio->output_history_size <= 0) { 2190 audio->output_history_size = AUDIO_OUTPUT_HISTORY_RATE * AUDIO_OUTPUT_HISTORY_SECS; 2191 audio->output_history_rate = AUDIO_OUTPUT_HISTORY_RATE; 2192 audio->output_history_downsample_n = 1; 2193 } 2194 2195 // Reallocate room buffers for actual rate 2196 int actual_room_size = (int)(0.12 * rate) * 3; 2197 if (actual_room_size != audio->room_size) { 2198 free(audio->room_buf_l); free(audio->room_buf_r); 2199 audio->room_size = actual_room_size; 2200 audio->room_buf_l = calloc(actual_room_size, sizeof(float)); 2201 audio->room_buf_r = calloc(actual_room_size, sizeof(float)); 2202 audio->room_pos = 0; 2203 } 2204 2205 /* Log the actual negotiated params — channels and format are 2206 * particularly important for diagnosing the "crunchy quiet" bug 2207 * on SOF boards where SSP1 may expect different bit depth. */ 2208 { 2209 snd_pcm_format_t fmt; 2210 unsigned int ch = 0; 2211 snd_pcm_hw_params_get_format(params, &fmt); 2212 snd_pcm_hw_params_get_channels(params, &ch); 2213 fprintf(stderr, "[audio] ALSA: %uHz %uch fmt=%s period=%lu buf=%lu (%.1fms)\n", 2214 rate, ch, snd_pcm_format_name(fmt), 2215 (unsigned long)period, (unsigned long)buffer_size, 2216 (double)period / rate * 1000.0); 2217 } 2218 snprintf(audio->audio_status, sizeof(audio->audio_status), 2219 "ok %uHz %lufrm", rate, (unsigned long)period); 2220 if (rate != AUDIO_SAMPLE_RATE) 2221 fprintf(stderr, "[audio] WARNING: got %uHz instead of %dHz\n", rate, AUDIO_SAMPLE_RATE); 2222 2223 // ChromeOS UCM verb activation. sof-rt5682 on Jasper Lake has no 2224 // upstream UCM, so without this the Speaker verb's csets 2225 // ('Spk Switch on' plus DSP pipeline routes) never fire and the 2226 // MAX98360A amp receives no I2S even with SD_MODE asserted. The 2227 // WeirdTreeThing/alsa-ucm-conf-cros bundle in /usr/share/alsa/ucm2/ 2228 // provides the downstream ChromeOS versions. Noop on boards whose 2229 // UCM is already upstream (ThinkPad HDA, Macs) — snd_use_case_mgr_open 2230 // returns -ENOENT and we fall through to the manual mixer path below. 2231 { 2232 char card_id[32] = ""; 2233 char id_path[64]; 2234 snprintf(id_path, sizeof(id_path), "/proc/asound/card%d/id", card_idx); 2235 FILE *idfp = fopen(id_path, "r"); 2236 if (idfp) { 2237 if (fgets(card_id, sizeof(card_id), idfp)) { 2238 char *nl = strchr(card_id, '\n'); if (nl) *nl = 0; 2239 } 2240 fclose(idfp); 2241 } 2242 if (card_id[0]) { 2243 /* The kernel strips hyphens from card IDs (so our machine 2244 * driver name `jsl_rt5682_def` + topology `sof-rt5682` both 2245 * become card id `sofrt5682`). The ChromeOS UCM tree keeps 2246 * the canonical hyphenated names. Try a few permutations so 2247 * whichever matches wins. */ 2248 const char *candidates[] = { 2249 card_id, /* e.g. "sofrt5682" */ 2250 "sof-rt5682", 2251 "sof-cs42l42", 2252 "sof-nau8825", 2253 "sof-da7219", 2254 NULL 2255 }; 2256 snd_use_case_mgr_t *uc = NULL; 2257 int uerr = -1; 2258 const char *opened = NULL; 2259 for (int i = 0; candidates[i]; i++) { 2260 uerr = snd_use_case_mgr_open(&uc, candidates[i]); 2261 if (uerr == 0) { opened = candidates[i]; break; } 2262 } 2263 if (uerr == 0) { 2264 fprintf(stderr, "[audio] UCM: opened '%s' (card=%s)\n", 2265 opened, card_id); 2266 if (snd_use_case_set(uc, "_verb", "HiFi") == 0) { 2267 fprintf(stderr, "[audio] UCM: _verb=HiFi set\n"); 2268 } else { 2269 fprintf(stderr, "[audio] UCM: _verb=HiFi failed\n"); 2270 } 2271 /* Enable ONLY Speaker at boot — enumerating every device 2272 * (including Headphones/Headset) was running ChromeOS 2273 * UCM EnableSequences that set `Headphone Jack Switch on` 2274 * + `HPOL/HPOR Playback Switch 1`. That forces DAPM to 2275 * route audio through the RT5682 headphone path and 2276 * powers down the MAX98360A amp (kmsg showed `sdmode 2277 * to 0` at 35s and never recovering). 2278 * 2279 * The rt5682-init BootSequence from WeirdTreeThing's 2280 * UCM deliberately ships with HP jack/switch OFF so 2281 * the speaker amp stays live when nothing is plugged 2282 * in. Keep that intact — only run the Speaker (and 2283 * mic) EnableSequence, not any headphone one. Jack- 2284 * plug routing becomes a later follow-up (a jack- 2285 * state watcher thread that flips _enadev on plug). */ 2286 const char **devlist = NULL; 2287 int ndev = snd_use_case_get_list(uc, "_devices/HiFi", 2288 &devlist); 2289 int enabled_speaker = 0; 2290 if (ndev > 0 && devlist) { 2291 for (int i = 0; i < ndev; i += 2) { 2292 const char *dev = devlist[i]; 2293 if (!dev || !dev[0]) continue; 2294 /* Skip anything that would re-enable the 2295 * headphone path at boot. Speakers first, 2296 * mics/HDMI are safe (no DAPM routing to HP). */ 2297 if (strstr(dev, "Headphone") || 2298 strstr(dev, "Headset") || 2299 strstr(dev, "Headphones")) { 2300 fprintf(stderr, 2301 "[audio] UCM: skip _enadev=%s (jack-gated)\n", 2302 dev); 2303 continue; 2304 } 2305 int rr = snd_use_case_set(uc, "_enadev", dev); 2306 fprintf(stderr, "[audio] UCM: _enadev=%s %s\n", 2307 dev, rr == 0 ? "ok" : "FAIL"); 2308 if (rr == 0 && (strstr(dev, "Speaker") || 2309 strstr(dev, "Speakers"))) 2310 enabled_speaker = 1; 2311 } 2312 snd_use_case_free_list(devlist, ndev); 2313 } else { 2314 /* Fallback: enable Speaker variants only. */ 2315 const char *names[] = {"Speaker", "Speakers", NULL}; 2316 for (int i = 0; names[i]; i++) { 2317 if (snd_use_case_set(uc, "_enadev", names[i]) == 0) { 2318 fprintf(stderr, "[audio] UCM: _enadev=%s ok\n", 2319 names[i]); 2320 enabled_speaker = 1; 2321 } 2322 } 2323 } 2324 if (!enabled_speaker) 2325 fprintf(stderr, "[audio] UCM: WARNING no Speaker device enabled\n"); 2326 snd_use_case_mgr_close(uc); 2327 } else { 2328 fprintf(stderr, "[audio] UCM: no config matched card '%s' — manual mixer fallback\n", 2329 card_id); 2330 } 2331 } 2332 2333 /* Defensive audio diagnostic — dump the full ASoC DAPM graph and 2334 * every kcontrol's current value so post-mortem log analysis can 2335 * tell whether a PGA is sitting at -inf, a DAPM widget is stuck 2336 * OFF, or a DAI isn't active. These files are debugfs-backed so 2337 * require CONFIG_DEBUG_FS=y and debugfs mounted at 2338 * /sys/kernel/debug (init already does the mount). Non-fatal if 2339 * absent. */ 2340 { 2341 const char *dbg_dir = "/sys/kernel/debug/asoc/card0"; 2342 if (access(dbg_dir, R_OK) == 0) { 2343 fprintf(stderr, "[audio-diag] ASoC debugfs dump — %s\n", dbg_dir); 2344 /* dapm/ subdir has one file per widget with its power state, 2345 * input/output connections, and active stream info. */ 2346 DIR *dapm = opendir("/sys/kernel/debug/asoc/card0/dapm"); 2347 if (dapm) { 2348 struct dirent *de; 2349 while ((de = readdir(dapm))) { 2350 if (de->d_name[0] == '.') continue; 2351 char widget_path[256]; 2352 snprintf(widget_path, sizeof(widget_path), 2353 "/sys/kernel/debug/asoc/card0/dapm/%s", 2354 de->d_name); 2355 FILE *wf = fopen(widget_path, "r"); 2356 if (!wf) continue; 2357 /* Each widget file's first line is the state: 2358 * "WidgetName: On in 0 out 0 stream ..." */ 2359 char wline[256]; 2360 if (fgets(wline, sizeof(wline), wf)) { 2361 char *nl = strchr(wline, '\n'); 2362 if (nl) *nl = 0; 2363 fprintf(stderr, "[audio-diag] dapm: %s\n", wline); 2364 } 2365 fclose(wf); 2366 } 2367 closedir(dapm); 2368 } 2369 /* /sys/kernel/debug/gpio dump shows MAX98360A SD_MODE + 2370 * RT5682 IRQ GPIO current state so we can tell if the 2371 * amp was powered at snapshot time. */ 2372 FILE *gf = fopen("/sys/kernel/debug/gpio", "r"); 2373 if (gf) { 2374 char gline[256]; 2375 int lines = 0; 2376 while (lines < 30 && fgets(gline, sizeof(gline), gf)) { 2377 char *nl = strchr(gline, '\n'); 2378 if (nl) *nl = 0; 2379 if (strstr(gline, "sdmode") || strstr(gline, "RT58") || 2380 strstr(gline, "gpiochip")) { 2381 fprintf(stderr, "[audio-diag] gpio: %s\n", gline); 2382 lines++; 2383 } 2384 } 2385 fclose(gf); 2386 } 2387 } else { 2388 fprintf(stderr, "[audio-diag] debugfs not mounted — no ASoC state available\n"); 2389 } 2390 } 2391 } 2392 2393 // Unmute ALL outputs (HDA Intel codecs have many controls that can mute) 2394 char mixer_card[16]; 2395 snprintf(mixer_card, sizeof(mixer_card), "hw:%d", card_idx); 2396 fprintf(stderr, "[audio] Using mixer: %s\n", mixer_card); 2397 2398 snd_mixer_t *mixer = NULL; 2399 if (snd_mixer_open(&mixer, 0) >= 0) { 2400 snd_mixer_attach(mixer, mixer_card); 2401 snd_mixer_selem_register(mixer, NULL, NULL); 2402 snd_mixer_load(mixer); 2403 2404 snd_mixer_elem_t *elem; 2405 for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { 2406 const char *name = snd_mixer_selem_get_name(elem); 2407 if (!snd_mixer_selem_is_active(elem)) continue; 2408 2409 // Log all mixer elements — with pre-set values so a silent audio 2410 // log can be diagnosed without flashing again. If a playback- 2411 // switch element is already off before our unmute, or a volume 2412 // element reports a suspicious range (e.g. min==max at 0), we 2413 // want to know which one. 2414 fprintf(stderr, "[audio] Mixer: %s", name); 2415 if (snd_mixer_selem_has_playback_volume(elem)) { 2416 long vmin = 0, vmax = 0, vcur = 0; 2417 snd_mixer_selem_get_playback_volume_range(elem, &vmin, &vmax); 2418 snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &vcur); 2419 fprintf(stderr, " [vol %ld..%ld now=%ld]", vmin, vmax, vcur); 2420 long dbmin = 0, dbmax = 0, dbcur = 0; 2421 if (snd_mixer_selem_get_playback_dB_range(elem, &dbmin, &dbmax) == 0 && 2422 snd_mixer_selem_get_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, &dbcur) == 0) { 2423 fprintf(stderr, " [dB %.1f..%.1f now=%.1f]", 2424 dbmin / 100.0, dbmax / 100.0, dbcur / 100.0); 2425 } 2426 } 2427 if (snd_mixer_selem_has_playback_switch(elem)) { 2428 int sw = 0; 2429 snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &sw); 2430 fprintf(stderr, " [sw now=%s]", sw ? "on" : "OFF"); 2431 } 2432 if (snd_mixer_selem_has_capture_switch(elem)) fprintf(stderr, " [cap-sw]"); 2433 if (snd_mixer_selem_has_capture_volume(elem)) fprintf(stderr, " [cap-vol]"); 2434 fprintf(stderr, "\n"); 2435 2436 // Unmute every playback switch we find — except the ones 2437 // the UCM BootSequence explicitly turns off to keep audio 2438 // routed to the speaker amp on unplugged-headphone state. 2439 // Flipping "Headphone Jack Switch" on re-enables the HP 2440 // DAPM path and silences MAX98360A even when nothing is 2441 // plugged in (see G7/Drawcia debug session). 2442 if (snd_mixer_selem_has_playback_switch(elem)) { 2443 int skip = 0; 2444 const char *jack_gated[] = { 2445 "Headphone Jack", /* rt5682 */ 2446 "Headphone Jack Switch", 2447 "HPOL Playback", 2448 "HPOR Playback", 2449 "Headset", 2450 NULL 2451 }; 2452 for (int j = 0; jack_gated[j]; j++) { 2453 if (strstr(name, jack_gated[j])) { skip = 1; break; } 2454 } 2455 if (!skip) { 2456 snd_mixer_selem_set_playback_switch_all(elem, 1); 2457 fprintf(stderr, "[audio] Unmuted: %s\n", name); 2458 } else { 2459 fprintf(stderr, "[audio] Skip unmute (jack-gated): %s\n", name); 2460 } 2461 } 2462 2463 // Set volume to max for output controls 2464 if (snd_mixer_selem_has_playback_volume(elem)) { 2465 long min, max; 2466 snd_mixer_selem_get_playback_volume_range(elem, &min, &max); 2467 snd_mixer_selem_set_playback_volume_all(elem, max); 2468 fprintf(stderr, "[audio] Volume %s: %ld/%ld\n", name, max, max); 2469 } 2470 2471 // NOTE: Do NOT touch capture mixer controls here — on the 11e Yoga 2472 // Gen 5, enabling capture switches at boot (before any capture PCM 2473 // is open) puts the HDA codec into a bad state that causes EIO when 2474 // the capture stream is later opened with period/buffer params. 2475 } 2476 snd_mixer_close(mixer); 2477 } else { 2478 fprintf(stderr, "[audio] Cannot open mixer\n"); 2479 } 2480 2481 // Read initial system volume 2482 audio->system_volume = read_system_volume_card(card_idx); 2483 fprintf(stderr, "[audio] System volume: %d%%\n", audio->system_volume); 2484 2485 // HDMI audio disabled — opening HDMI PCM streams on the same HDA controller 2486 // can exhaust controller streams and cause EIO on capture. 2487 audio->hdmi_pcm = NULL; 2488 fprintf(stderr, "[audio] HDMI audio: disabled\n"); 2489 2490 // Start audio thread 2491 audio->running = 1; 2492 pthread_create(&audio->thread, NULL, audio_thread_fn, audio); 2493 2494 /* Force PCI runtime-PM to "on" for the sound card now that the 2495 * driver is loaded and card_idx is known. The init-script attempt 2496 * may fire before probe — doing it here guarantees the sysfs node 2497 * exists. Without this the SOF DSP auto-suspends after ~20-40s 2498 * of silence, which stops the SSP1 BE DAI and drops MAX98360A 2499 * sdmode to 0 (speaker amp off), and the amp never comes back. 2500 * Also try the generic PCI "power_save" disable for HDA paths. */ 2501 { 2502 char pm_path[128]; 2503 snprintf(pm_path, sizeof(pm_path), 2504 "/sys/class/sound/card%d/device/power/control", card_idx); 2505 FILE *pm = fopen(pm_path, "w"); 2506 if (pm) { 2507 fputs("on", pm); 2508 fclose(pm); 2509 fprintf(stderr, "[audio] Disabled runtime-PM: %s\n", pm_path); 2510 } else { 2511 fprintf(stderr, "[audio] Could not set runtime-PM: %s\n", pm_path); 2512 } 2513 /* Also try the autosuspend delay — set to -1 (never) */ 2514 snprintf(pm_path, sizeof(pm_path), 2515 "/sys/class/sound/card%d/device/power/autosuspend_delay_ms", 2516 card_idx); 2517 pm = fopen(pm_path, "w"); 2518 if (pm) { 2519 fputs("-1", pm); 2520 fclose(pm); 2521 fprintf(stderr, "[audio] Set autosuspend_delay=-1: %s\n", pm_path); 2522 } 2523 } 2524 2525 fprintf(stderr, "[audio] Ready\n"); 2526 return audio; 2527} 2528 2529uint64_t audio_synth(ACAudio *audio, WaveType type, double freq, 2530 double duration, double volume, double attack, 2531 double decay, double pan) { 2532 if (!audio) return 0; 2533 2534 pthread_mutex_lock(&audio->lock); 2535 2536 // Find free voice slot 2537 int slot = -1; 2538 for (int i = 0; i < AUDIO_MAX_VOICES; i++) { 2539 if (audio->voices[i].state == VOICE_INACTIVE) { 2540 slot = i; 2541 break; 2542 } 2543 } 2544 if (slot < 0) { 2545 // Steal oldest voice 2546 double oldest = 0; 2547 slot = 0; 2548 for (int i = 0; i < AUDIO_MAX_VOICES; i++) { 2549 if (audio->voices[i].elapsed > oldest) { 2550 oldest = audio->voices[i].elapsed; 2551 slot = i; 2552 } 2553 } 2554 } 2555 2556 ACVoice *v = &audio->voices[slot]; 2557 memset(v, 0, sizeof(ACVoice)); 2558 v->state = VOICE_ACTIVE; 2559 v->type = type; 2560 v->phase = 0.0; 2561 v->frequency = freq; 2562 v->target_frequency = freq; 2563 v->volume = volume; 2564 v->pan = pan; 2565 v->attack = attack > 0 ? attack : 0.005; 2566 v->decay = decay > 0 ? decay : 0.1; 2567 v->duration = duration; 2568 v->id = ++audio->next_id; 2569 v->started_at = audio->time; 2570 2571 if (type == WAVE_NOISE || type == WAVE_WHISTLE || type == WAVE_GUN) { 2572 v->noise_seed = (uint32_t)(audio->next_id * 2654435761u); 2573 } 2574 if (type == WAVE_NOISE) { 2575 setup_noise_filter(v, (double)(audio->actual_rate ? audio->actual_rate : AUDIO_SAMPLE_RATE)); 2576 } else if (type == WAVE_GUN) { 2577 // Caller (audio_synth_gun) sets the preset via gun_init_voice 2578 // after this base init runs. 2579 } else if (type == WAVE_WHISTLE) { 2580 // Clear the waveguide state — bore + jet delay buffers and the 2581 // loop filter / DC blocker. Without this, leftover state from a 2582 // previous voice reuse would produce startup artifacts. 2583 memset(v->whistle_bore_buf, 0, sizeof(v->whistle_bore_buf)); 2584 memset(v->whistle_jet_buf, 0, sizeof(v->whistle_jet_buf)); 2585 v->whistle_bore_w = 0; 2586 v->whistle_jet_w = 0; 2587 v->whistle_breath = 0.0; 2588 v->whistle_vibrato_phase = 0.0; 2589 v->whistle_lp1 = 0.0; 2590 v->whistle_hp_x1 = 0.0; 2591 v->whistle_hp_y1 = 0.0; 2592 } 2593 2594 pthread_mutex_unlock(&audio->lock); 2595 return v->id; 2596} 2597 2598void audio_kill(ACAudio *audio, uint64_t id, double fade) { 2599 if (!audio) return; 2600 pthread_mutex_lock(&audio->lock); 2601 for (int i = 0; i < AUDIO_MAX_VOICES; i++) { 2602 if (audio->voices[i].id == id && audio->voices[i].state == VOICE_ACTIVE) { 2603 audio->voices[i].state = VOICE_KILLING; 2604 audio->voices[i].fade_duration = fade > 0 ? fade : 0.025; 2605 audio->voices[i].fade_elapsed = 0.0; 2606 // Gun-specific release behaviors (e.g. ricochet pitch drop). 2607 if (audio->voices[i].type == WAVE_GUN) { 2608 gun_on_release(&audio->voices[i]); 2609 } 2610 break; 2611 } 2612 } 2613 pthread_mutex_unlock(&audio->lock); 2614} 2615 2616uint64_t audio_synth_gun(ACAudio *audio, GunPreset preset, double duration, 2617 double volume, double attack, double decay, 2618 double pan, double pressure_scale, int force_model) { 2619 if (!audio) return 0; 2620 // Delegate base voice setup (slot alloc, envelope fields, noise seed). 2621 // Frequency is unused for guns — the DWG cavity resonance comes from 2622 // the preset's bore_length, not v->frequency. We pass 110 to keep 2623 // the smoothing code happy. 2624 uint64_t id = audio_synth(audio, WAVE_GUN, 110.0, duration, volume, 2625 attack, decay, pan); 2626 if (!id) return 0; 2627 2628 pthread_mutex_lock(&audio->lock); 2629 ACVoice *v = NULL; 2630 for (int i = 0; i < AUDIO_MAX_VOICES; i++) { 2631 if (audio->voices[i].id == id) { v = &audio->voices[i]; break; } 2632 } 2633 if (v) { 2634 double sr = (double)(audio->actual_rate ? audio->actual_rate 2635 : AUDIO_SAMPLE_RATE); 2636 gun_init_voice(v, preset, sr, force_model); 2637 if (pressure_scale > 0.0 && pressure_scale != 1.0) { 2638 v->gun_pressure *= pressure_scale; 2639 } 2640 } 2641 pthread_mutex_unlock(&audio->lock); 2642 return id; 2643} 2644 2645// Apply a single per-shot param override to a freshly-initialized gun 2646// voice. Called by the JS bindings between audio_synth_gun() and the 2647// audio thread's first read of the voice — lets the inspector's 2648// drag-to-edit cards push live tuning values into the next shot 2649// without rebuilding gun_presets[]. Unknown keys are silently ignored. 2650// 2651// Layer-state fields (envelopes, biquad coefficients, the Friedlander 2652// pulse position) are NOT exposed; we only change the constants the 2653// preset would have set in init. 2654void audio_gun_voice_set_param(ACAudio *audio, uint64_t id, 2655 const char *key, double value) { 2656 if (!audio || !key) return; 2657 pthread_mutex_lock(&audio->lock); 2658 ACVoice *v = NULL; 2659 for (int i = 0; i < AUDIO_MAX_VOICES; i++) { 2660 if (audio->voices[i].id == id && audio->voices[i].type == WAVE_GUN) { 2661 v = &audio->voices[i]; 2662 break; 2663 } 2664 } 2665 if (!v) { pthread_mutex_unlock(&audio->lock); return; } 2666 2667 double sr = (double)(audio->actual_rate ? audio->actual_rate 2668 : AUDIO_SAMPLE_RATE); 2669 if (v->gun_model == GUN_MODEL_CLASSIC) { 2670 if (strcmp(key, "click_amp") == 0) v->gun_click_amp = value; 2671 else if (strcmp(key, "click_decay_ms") == 0) { 2672 double tau = (value > 0.05 ? value : 0.05) * 0.001; 2673 v->gun_click_decay_mult = exp(-1.0 / (tau * sr)); 2674 } 2675 else if (strcmp(key, "crack_amp") == 0) v->gun_body_amp[0] = value; 2676 else if (strcmp(key, "crack_decay_ms") == 0) { 2677 double tau = (value > 0.1 ? value : 0.1) * 0.001; 2678 v->gun_env_decay_mult = exp(-1.0 / (tau * sr)); 2679 } 2680 else if (strcmp(key, "crack_fc") == 0 || strcmp(key, "crack_q") == 0) { 2681 // Both freq and Q feed the same biquad, recompute together. 2682 // For partial updates we just recompute with the latest value 2683 // and keep the other from existing coefs (lossy but adequate). 2684 // Approximate Q from a2 = r² → r = √a2 → tau = -π·f/(Q·sr·ln r). 2685 double a2 = v->gun_body_a2[0]; 2686 double r = a2 > 0 ? sqrt(a2) : 0.95; 2687 double cur_w = acos(v->gun_body_a1[0] / (2.0 * r)); 2688 double cur_f = cur_w * sr / (2.0 * M_PI); 2689 double cur_q = -M_PI * cur_f / (sr * log(r > 0.0001 ? r : 0.0001)); 2690 double f = (strcmp(key, "crack_fc") == 0) ? value : cur_f; 2691 double q = (strcmp(key, "crack_q") == 0) ? value : cur_q; 2692 compute_resonator(f, q, sr, &v->gun_body_a1[0], 2693 &v->gun_body_a2[0], &v->gun_crack_b0); 2694 } 2695 else if (strcmp(key, "boom_amp") == 0) v->gun_body_amp[1] = value; 2696 else if (strcmp(key, "boom_freq_start") == 0) { 2697 v->gun_boom_freq_start = value; 2698 v->gun_boom_freq = value; 2699 } 2700 else if (strcmp(key, "boom_freq_end") == 0) v->gun_boom_freq_end = value; 2701 else if (strcmp(key, "boom_pitch_decay_ms") == 0) { 2702 double tau = (value > 0.1 ? value : 0.1) * 0.001; 2703 v->gun_boom_pitch_mult = exp(-1.0 / (tau * sr)); 2704 } 2705 else if (strcmp(key, "boom_amp_decay_ms") == 0) { 2706 double tau = (value > 0.1 ? value : 0.1) * 0.001; 2707 v->gun_boom_decay_mult = exp(-1.0 / (tau * sr)); 2708 } 2709 else if (strcmp(key, "tail_amp") == 0) v->gun_body_amp[2] = value; 2710 else if (strcmp(key, "tail_decay_ms") == 0) { 2711 double tau = (value > 0.1 ? value : 0.1) * 0.001; 2712 v->gun_tail_decay_mult = exp(-1.0 / (tau * sr)); 2713 } 2714 else if (strcmp(key, "tail_fc") == 0 || strcmp(key, "tail_q") == 0) { 2715 double a2 = v->gun_body_a2[1]; 2716 double r = a2 > 0 ? sqrt(a2) : 0.95; 2717 double cur_w = acos(v->gun_body_a1[1] / (2.0 * r)); 2718 double cur_f = cur_w * sr / (2.0 * M_PI); 2719 double cur_q = -M_PI * cur_f / (sr * log(r > 0.0001 ? r : 0.0001)); 2720 double f = (strcmp(key, "tail_fc") == 0) ? value : cur_f; 2721 double q = (strcmp(key, "tail_q") == 0) ? value : cur_q; 2722 compute_resonator(f, q, sr, &v->gun_body_a1[1], 2723 &v->gun_body_a2[1], &v->gun_tail_b0); 2724 } 2725 } else { 2726 // Physical model overrides. 2727 if (strcmp(key, "pressure") == 0) v->gun_pressure = value; 2728 else if (strcmp(key, "env_rate") == 0) { 2729 v->gun_phys_t_plus = (3.0 / (value > 100 ? value : 100.0)) * sr; 2730 if (v->gun_phys_t_plus < 32.0) v->gun_phys_t_plus = 32.0; 2731 if (v->gun_phys_t_plus > 4096.0) v->gun_phys_t_plus = 4096.0; 2732 } 2733 else if (strcmp(key, "bore_length_s") == 0) { 2734 v->gun_bore_delay = value * sr; 2735 if (v->gun_bore_delay < 4.0) v->gun_bore_delay = 4.0; 2736 if (v->gun_bore_delay > 2040.0) v->gun_bore_delay = 2040.0; 2737 } 2738 else if (strcmp(key, "bore_loss") == 0) v->gun_bore_loss = value; 2739 else if (strcmp(key, "breech_reflect") == 0) v->gun_breech_reflect = value; 2740 else if (strcmp(key, "noise_gain") == 0) v->gun_noise_gain = value; 2741 else if (strcmp(key, "radiation") == 0) v->gun_radiation_a = value; 2742 else if (strncmp(key, "body_freq", 9) == 0 || 2743 strncmp(key, "body_q", 6) == 0) { 2744 int idx = key[strlen(key) - 1] - '0'; 2745 if (idx < 0 || idx > 2) { pthread_mutex_unlock(&audio->lock); return; } 2746 // Recompute the resonator with one swapped param, the other inferred. 2747 double a2 = v->gun_body_a2[idx]; 2748 double r = a2 > 0 ? sqrt(a2) : 0.95; 2749 double cur_w = acos(v->gun_body_a1[idx] / (2.0 * r)); 2750 double cur_f = cur_w * sr / (2.0 * M_PI); 2751 double cur_q = -M_PI * cur_f / (sr * log(r > 0.0001 ? r : 0.0001)); 2752 double f = (strncmp(key, "body_freq", 9) == 0) ? value : cur_f; 2753 double q = (strncmp(key, "body_q", 6) == 0) ? value : cur_q; 2754 double b0_unused; 2755 compute_resonator(f, q, sr, &v->gun_body_a1[idx], 2756 &v->gun_body_a2[idx], &b0_unused); 2757 } 2758 else if (strncmp(key, "body_amp", 8) == 0) { 2759 int idx = key[strlen(key) - 1] - '0'; 2760 if (idx >= 0 && idx <= 2) v->gun_body_amp[idx] = value; 2761 } 2762 } 2763 pthread_mutex_unlock(&audio->lock); 2764} 2765 2766void audio_update(ACAudio *audio, uint64_t id, double freq, 2767 double volume, double pan) { 2768 if (!audio) return; 2769 pthread_mutex_lock(&audio->lock); 2770 for (int i = 0; i < AUDIO_MAX_VOICES; i++) { 2771 if (audio->voices[i].id == id && audio->voices[i].state != VOICE_INACTIVE) { 2772 if (freq > 0) audio->voices[i].target_frequency = freq; 2773 if (volume >= 0) audio->voices[i].volume = volume; 2774 if (pan > -2.0) audio->voices[i].pan = pan; 2775 break; 2776 } 2777 } 2778 pthread_mutex_unlock(&audio->lock); 2779} 2780 2781int audio_beat_check(ACAudio *audio) { 2782 if (!audio) return 0; 2783 int triggered = audio->beat_triggered; 2784 if (triggered) audio->beat_triggered = 0; 2785 return triggered; 2786} 2787 2788void audio_set_bpm(ACAudio *audio, double bpm) { 2789 if (!audio || bpm <= 0) return; 2790 pthread_mutex_lock(&audio->lock); 2791 audio->bpm = bpm; 2792 pthread_mutex_unlock(&audio->lock); 2793} 2794 2795void audio_room_toggle(ACAudio *audio) { 2796 if (!audio) return; 2797 audio->room_enabled = !audio->room_enabled; 2798 fprintf(stderr, "[audio] Room: %s\n", audio->room_enabled ? "ON" : "OFF"); 2799} 2800 2801void audio_glitch_toggle(ACAudio *audio) { 2802 if (!audio) return; 2803 if (audio->glitch_enabled || audio->target_glitch_mix > 0.001f || audio->glitch_mix > 0.001f) { 2804 audio->glitch_enabled = 0; 2805 audio->target_glitch_mix = 0.0f; 2806 } else { 2807 audio->glitch_enabled = 1; 2808 audio->target_glitch_mix = 1.0f; 2809 } 2810 fprintf(stderr, "[audio] Glitch: %s (mix %.2f)\n", 2811 audio->glitch_enabled ? "ON" : "OFF", 2812 audio->target_glitch_mix); 2813} 2814 2815void audio_set_room_mix(ACAudio *audio, float mix) { 2816 if (!audio) return; 2817 if (mix < 0.0f) mix = 0.0f; 2818 if (mix > 1.0f) mix = 1.0f; 2819 audio->target_room_mix = mix; 2820} 2821 2822void audio_set_glitch_mix(ACAudio *audio, float mix) { 2823 if (!audio) return; 2824 if (mix < 0.0f) mix = 0.0f; 2825 if (mix > 1.0f) mix = 1.0f; 2826 audio->target_glitch_mix = mix; 2827 audio->glitch_enabled = mix > 0.001f; 2828 if (!audio->glitch_enabled) audio->glitch_counter = 0; 2829} 2830 2831void audio_set_fx_mix(ACAudio *audio, float mix) { 2832 if (!audio) return; 2833 if (mix < 0.0f) mix = 0.0f; 2834 if (mix > 1.0f) mix = 1.0f; 2835 audio->target_fx_mix = mix; 2836} 2837 2838// --- Hot-mic capture thread --- 2839// Device opens once (on wave-enter), stays running. Always reads to keep 2840// ALSA happy and the level meter live. Only writes to sample_buf when 2841// recording flag is set. Instant recording with zero device-open latency. 2842// 2843// IMPORTANT: HDMI audio must be DISABLED in audio_init and playback buffer 2844// must be 3 periods (not 6) — otherwise the HDA controller runs out of 2845// streams and capture gets EIO. 2846static void *capture_thread_func(void *arg) { 2847 ACAudio *audio = (ACAudio *)arg; 2848 snd_pcm_t *cap = NULL; 2849 2850 const char *devices[] = {"hw:0,0", "hw:1,0", "hw:0,6", "hw:0,7", 2851 "plughw:0,0", "plughw:1,0", "default", NULL}; 2852 for (int i = 0; devices[i]; i++) { 2853 if (snd_pcm_open(&cap, devices[i], SND_PCM_STREAM_CAPTURE, 0) == 0) { 2854 snprintf(audio->mic_device, sizeof(audio->mic_device), "%s", devices[i]); 2855 ac_log("[mic] opened capture device: %s\n", devices[i]); 2856 break; 2857 } 2858 cap = NULL; 2859 } 2860 if (!cap) { 2861 snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 2862 "no capture device found"); 2863 ac_log("[mic] no capture device found\n"); 2864 audio->mic_hot = 0; 2865 return NULL; 2866 } 2867 2868 // Enable capture mixer switches now that capture PCM is open. 2869 // (Safe to do after snd_pcm_open — avoids the pre-open EIO bug on some HDA.) 2870 { 2871 char mixer_card[16]; 2872 snprintf(mixer_card, sizeof(mixer_card), "hw:%d", audio->card_index); 2873 snd_mixer_t *cmix = NULL; 2874 if (snd_mixer_open(&cmix, 0) >= 0) { 2875 snd_mixer_attach(cmix, mixer_card); 2876 snd_mixer_selem_register(cmix, NULL, NULL); 2877 snd_mixer_load(cmix); 2878 snd_mixer_elem_t *el; 2879 for (el = snd_mixer_first_elem(cmix); el; el = snd_mixer_elem_next(el)) { 2880 if (!snd_mixer_selem_is_active(el)) continue; 2881 if (snd_mixer_selem_has_capture_switch(el)) { 2882 snd_mixer_selem_set_capture_switch_all(el, 1); 2883 ac_log("[mic] enabled capture switch: %s\n", snd_mixer_selem_get_name(el)); 2884 } 2885 if (snd_mixer_selem_has_capture_volume(el)) { 2886 long cmin, cmax; 2887 snd_mixer_selem_get_capture_volume_range(el, &cmin, &cmax); 2888 snd_mixer_selem_set_capture_volume_all(el, cmax); 2889 ac_log("[mic] capture volume %s: %ld/%ld\n", snd_mixer_selem_get_name(el), cmax, cmax); 2890 } 2891 } 2892 snd_mixer_close(cmix); 2893 } 2894 } 2895 2896 snd_pcm_hw_params_t *hw; 2897 snd_pcm_hw_params_alloca(&hw); 2898 snd_pcm_hw_params_any(cap, hw); 2899 snd_pcm_hw_params_set_access(cap, hw, SND_PCM_ACCESS_RW_INTERLEAVED); 2900 snd_pcm_hw_params_set_format(cap, hw, SND_PCM_FORMAT_S16_LE); 2901 2902 unsigned int channels = 1; 2903 if (snd_pcm_hw_params_set_channels(cap, hw, 1) < 0) { 2904 channels = 2; 2905 snd_pcm_hw_params_set_channels(cap, hw, 2); 2906 } 2907 2908 unsigned int rate = 48000; 2909 snd_pcm_hw_params_set_rate_near(cap, hw, &rate, NULL); 2910 2911 // Set period=1024 (~21ms) for low-latency capture. This previously 2912 // caused EIO but the root causes were: HDMI audio open (exhausting 2913 // HDA streams), 6-period playback buffer, and capture mixer in 2914 // audio_init. All three are now fixed. 2915 snd_pcm_uframes_t period_frames = 1024; 2916 snd_pcm_hw_params_set_period_size_near(cap, hw, &period_frames, NULL); 2917 snd_pcm_uframes_t buffer_frames = 8192; 2918 snd_pcm_hw_params_set_buffer_size_near(cap, hw, &buffer_frames); 2919 2920 if (snd_pcm_hw_params(cap, hw) < 0) { 2921 snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 2922 "failed to configure capture"); 2923 ac_log("[mic] failed to configure capture\n"); 2924 snd_pcm_close(cap); 2925 audio->mic_hot = 0; 2926 return NULL; 2927 } 2928 2929 // Enable capture mixer (safe here — after PCM open, in capture thread) 2930 { 2931 int cnum = 0; 2932 const char *d = audio->mic_device; 2933 while (*d && (*d < '0' || *d > '9')) d++; 2934 if (*d) cnum = atoi(d); 2935 char ccard[16]; 2936 snprintf(ccard, sizeof(ccard), "hw:%d", cnum); 2937 snd_mixer_t *cmix = NULL; 2938 if (snd_mixer_open(&cmix, 0) >= 0) { 2939 snd_mixer_attach(cmix, ccard); 2940 snd_mixer_selem_register(cmix, NULL, NULL); 2941 snd_mixer_load(cmix); 2942 snd_mixer_elem_t *elem; 2943 for (elem = snd_mixer_first_elem(cmix); elem; elem = snd_mixer_elem_next(elem)) { 2944 if (!snd_mixer_selem_is_active(elem)) continue; 2945 if (snd_mixer_selem_has_capture_switch(elem)) { 2946 snd_mixer_selem_set_capture_switch_all(elem, 1); 2947 ac_log("[mic] capture switch ON: %s\n", snd_mixer_selem_get_name(elem)); 2948 } 2949 if (snd_mixer_selem_has_capture_volume(elem)) { 2950 long cmin, cmax; 2951 snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax); 2952 long cset = cmin + ((cmax - cmin) * 9) / 10; 2953 snd_mixer_selem_set_capture_volume_all(elem, cset); 2954 ac_log("[mic] capture volume %s: %ld/%ld\n", 2955 snd_mixer_selem_get_name(elem), cset, cmax); 2956 } 2957 } 2958 snd_mixer_close(cmix); 2959 } 2960 } 2961 2962 audio->sample_rate = rate; 2963 audio->mic_connected = 1; 2964 ac_log("[mic] hot-mic running at %u Hz, %u ch\n", rate, channels); 2965 2966 int16_t buf[1024 * 2]; 2967 while (audio->mic_hot) { 2968 int n = snd_pcm_readi(cap, buf, 512); 2969 if (n < 0) { 2970 n = snd_pcm_recover(cap, n, 0); 2971 if (n < 0) { 2972 snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 2973 "capture read failed: %s", snd_strerror(n)); 2974 ac_log("[mic] capture read failed: %s\n", snd_strerror(n)); 2975 break; 2976 } 2977 continue; 2978 } 2979 2980 float peak = 0.0f; 2981 // Aggressive compressor + hard limiter to prevent clipping. 2982 // Matches the note compression style in the synth output. 2983 static float env = 0.0f; // envelope follower 2984 static float comp_gain = 1.0f; // current gain 2985 const float threshold = 0.15f; // compress early (mic input is often hot) 2986 const float ratio = 12.0f; // aggressive compression 2987 const float attack = 0.005f; // fast attack 2988 const float release = 0.00005f; // slow release (smooth) 2989 const float limiter = 0.9f; // hard limiter ceiling 2990 2991 for (int s = 0; s < n; s++) { 2992 float sample; 2993 if (channels == 1) { 2994 sample = buf[s] / 32768.0f; 2995 } else { 2996 sample = (buf[s * 2] + buf[s * 2 + 1]) / 65536.0f; 2997 } 2998 2999 // Envelope follower 3000 float abs_s = fabsf(sample); 3001 if (abs_s > env) 3002 env += attack * (abs_s - env); 3003 else 3004 env += release * (abs_s - env); 3005 3006 // Compute gain reduction 3007 if (env > threshold) { 3008 float over = env - threshold; 3009 float reduced = threshold + over / ratio; 3010 comp_gain = reduced / env; 3011 } else { 3012 comp_gain += 0.0002f * (1.0f - comp_gain); 3013 } 3014 3015 sample *= comp_gain; 3016 3017 // Hard limiter — prevent any clipping 3018 if (sample > limiter) sample = limiter; 3019 else if (sample < -limiter) sample = -limiter; 3020 3021 if (abs_s > peak) peak = abs_s; 3022 3023 // Always write to ring buffer 3024 audio->mic_ring[audio->mic_ring_pos % audio->sample_max_len] = sample; 3025 audio->mic_ring_pos++; 3026 3027 // Direct-write when recording 3028 if (audio->recording && audio->sample_write_pos < audio->sample_max_len) { 3029 audio->sample_buf[audio->sample_write_pos++] = sample; 3030 } 3031 } 3032 // If we skipped the first chunk, mark that we've consumed it 3033 // by writing at least 0 (sample_write_pos stays 0, next chunk writes) 3034 audio->mic_level = peak; 3035 3036 if (audio->recording && audio->sample_write_pos >= audio->sample_max_len) { 3037 audio->sample_len = audio->sample_write_pos; 3038 audio->recording = 0; 3039 ac_log("[mic] recording buffer full (%d samples)\n", audio->sample_len); 3040 } 3041 } 3042 3043 ac_log("[mic] hot-mic thread exiting, device=%s\n", audio->mic_device); 3044 snd_pcm_close(cap); 3045 audio->mic_connected = 0; 3046 audio->recording = 0; 3047 return NULL; 3048} 3049 3050int audio_mic_open(ACAudio *audio) { 3051 if (!audio || audio->mic_hot || audio->capture_thread_running) return -1; 3052 audio->mic_hot = 1; 3053 audio->capture_thread_running = 1; 3054 audio->mic_last_error[0] = 0; 3055 ac_log("[mic] opening hot-mic\n"); 3056 if (pthread_create(&audio->capture_thread, NULL, capture_thread_func, audio) != 0) { 3057 audio->mic_hot = 0; 3058 audio->capture_thread_running = 0; 3059 ac_log("[mic] failed to create capture thread\n"); 3060 return -1; 3061 } 3062 return 0; 3063} 3064 3065void audio_mic_close(ACAudio *audio) { 3066 if (!audio) return; 3067 audio->recording = 0; 3068 audio->mic_hot = 0; 3069 if (audio->capture_thread_running) { 3070 pthread_join(audio->capture_thread, NULL); 3071 audio->capture_thread_running = 0; 3072 } 3073 ac_log("[mic] hot-mic closed\n"); 3074} 3075 3076int audio_mic_start(ACAudio *audio) { 3077 if (!audio || audio->recording) return -1; 3078 if (!audio->mic_hot) { 3079 int rc = audio_mic_open(audio); 3080 if (rc != 0) return rc; 3081 } 3082 // Kill any playing sample voices 3083 for (int i = 0; i < AUDIO_MAX_SAMPLE_VOICES; i++) 3084 audio->sample_voices[i].active = 0; 3085 audio->rec_start_ring_pos = audio->mic_ring_pos; 3086 audio->sample_len = 0; 3087 audio->sample_write_pos = 0; 3088 __sync_synchronize(); 3089 audio->recording = 1; 3090 ac_log("[mic] recording started (instant), ring_pos=%d\n", audio->rec_start_ring_pos); 3091 return 0; 3092} 3093 3094int audio_mic_stop(ACAudio *audio) { 3095 if (!audio) return 0; 3096 audio->recording = 0; 3097 __sync_synchronize(); 3098 3099 // Kill all sample voices BEFORE touching sample_buf — 3100 // playback thread reads sample_buf[]/sample_len without locks 3101 for (int i = 0; i < AUDIO_MAX_SAMPLE_VOICES; i++) 3102 audio->sample_voices[i].active = 0; 3103 __sync_synchronize(); 3104 3105 int direct_len = audio->sample_write_pos; 3106 if (direct_len > 0) { 3107 audio->sample_len = direct_len; 3108 ac_log("[mic] recording stopped (direct), sample_len=%d sample_rate=%u\n", 3109 audio->sample_len, audio->sample_rate); 3110 } else { 3111 // Fallback: extract from ring buffer 3112 int start = audio->rec_start_ring_pos; 3113 int end = audio->mic_ring_pos; 3114 int len = end - start; 3115 if (len < 0) len = 0; 3116 if (len > audio->sample_max_len) len = audio->sample_max_len; 3117 for (int i = 0; i < len; i++) { 3118 audio->sample_buf[i] = audio->mic_ring[(start + i) % audio->sample_max_len]; 3119 } 3120 audio->sample_len = len; 3121 ac_log("[mic] recording stopped (ring), sample_len=%d ring_span=%d sample_rate=%u\n", 3122 audio->sample_len, end - start, audio->sample_rate); 3123 } 3124 // Auto-trim silence from start (threshold: ~0.01 = -40dB) 3125 if (audio->sample_len > 0) { 3126 const float trim_threshold = 0.01f; 3127 int trim_start = 0; 3128 while (trim_start < audio->sample_len && 3129 fabsf(audio->sample_buf[trim_start]) < trim_threshold) { 3130 trim_start++; 3131 } 3132 if (trim_start > 0 && trim_start < audio->sample_len) { 3133 int new_len = audio->sample_len - trim_start; 3134 memmove(audio->sample_buf, audio->sample_buf + trim_start, 3135 new_len * sizeof(float)); 3136 audio->sample_len = new_len; 3137 ac_log("[mic] auto-trimmed %d silent samples from start\n", trim_start); 3138 } 3139 } 3140 3141 return audio->sample_len; 3142} 3143 3144// --- Sample bank: get/load data for per-key samples --- 3145int audio_sample_get_data(ACAudio *audio, float *out, int max_len) { 3146 if (!audio || !out || audio->sample_len == 0) return 0; 3147 int len = audio->sample_len < max_len ? audio->sample_len : max_len; 3148 memcpy(out, audio->sample_buf, len * sizeof(float)); 3149 return len; 3150} 3151 3152int audio_output_get_recent(ACAudio *audio, float *out, int max_len, unsigned int *out_rate) { 3153 if (!audio || !out || max_len <= 0 || !audio->output_history_buf || audio->output_history_size <= 0) { 3154 if (out_rate) *out_rate = 0; 3155 return 0; 3156 } 3157 3158 pthread_mutex_lock(&audio->lock); 3159 if (out_rate) *out_rate = audio->output_history_rate; 3160 3161 uint64_t write_pos = audio->output_history_write_pos; 3162 int available = write_pos < (uint64_t)audio->output_history_size 3163 ? (int)write_pos 3164 : audio->output_history_size; 3165 int len = available < max_len ? available : max_len; 3166 uint64_t start = write_pos - (uint64_t)len; 3167 for (int i = 0; i < len; i++) { 3168 out[i] = audio->output_history_buf[(start + (uint64_t)i) % (uint64_t)audio->output_history_size]; 3169 } 3170 3171 pthread_mutex_unlock(&audio->lock); 3172 return len; 3173} 3174 3175void audio_sample_load_data(ACAudio *audio, const float *data, int len, unsigned int rate) { 3176 if (!audio || !data || len <= 0 || !audio->sample_buf_back) return; 3177 if (len > audio->sample_max_len) len = audio->sample_max_len; 3178 // Write to back buffer (only JS thread writes here — safe without lock) 3179 memcpy(audio->sample_buf_back, data, len * sizeof(float)); 3180 if (len < audio->sample_max_len) 3181 memset(audio->sample_buf_back + len, 0, (audio->sample_max_len - len) * sizeof(float)); 3182 // Swap pointers under lock — audio callback checks sample_loading flag 3183 pthread_mutex_lock(&audio->lock); 3184 float *tmp = audio->sample_buf; 3185 audio->sample_buf = audio->sample_buf_back; 3186 audio->sample_buf_back = tmp; 3187 audio->sample_len = len; 3188 if (rate > 0) audio->sample_rate = rate; 3189 __sync_synchronize(); 3190 pthread_mutex_unlock(&audio->lock); 3191 // Log peak value and first few samples for debugging 3192 float peak = 0.0f; 3193 for (int i = 0; i < len; i++) { 3194 float a = fabsf(audio->sample_buf[i]); 3195 if (a > peak) peak = a; 3196 } 3197 ac_log("[sample] loaded %d samples (%d Hz) peak=%.4f first=[%.3f,%.3f,%.3f,%.3f]\n", 3198 len, audio->sample_rate, peak, 3199 len > 0 ? audio->sample_buf[0] : 0, 3200 len > 1 ? audio->sample_buf[1] : 0, 3201 len > 2 ? audio->sample_buf[2] : 0, 3202 len > 3 ? audio->sample_buf[3] : 0); 3203} 3204 3205void audio_replay_load_data(ACAudio *audio, const float *data, int len, unsigned int rate) { 3206 if (!audio || !data || len <= 0 || !audio->replay_buf_back) return; 3207 if (len > audio->replay_max_len) len = audio->replay_max_len; 3208 3209 memcpy(audio->replay_buf_back, data, len * sizeof(float)); 3210 if (len < audio->replay_max_len) 3211 memset(audio->replay_buf_back + len, 0, (audio->replay_max_len - len) * sizeof(float)); 3212 3213 pthread_mutex_lock(&audio->lock); 3214 audio->replay_voice.active = 0; 3215 float *tmp = audio->replay_buf; 3216 audio->replay_buf = audio->replay_buf_back; 3217 audio->replay_buf_back = tmp; 3218 audio->replay_len = len; 3219 if (rate > 0) audio->replay_rate = rate; 3220 __sync_synchronize(); 3221 pthread_mutex_unlock(&audio->lock); 3222} 3223 3224// --- Sample playback --- 3225uint64_t audio_sample_play(ACAudio *audio, double freq, double base_freq, 3226 double volume, double pan, int loop) { 3227 if (!audio || audio->sample_len == 0) return 0; 3228 pthread_mutex_lock(&audio->lock); 3229 3230 // Find free slot (or steal oldest) 3231 int slot = -1; 3232 for (int i = 0; i < AUDIO_MAX_SAMPLE_VOICES; i++) { 3233 if (!audio->sample_voices[i].active) { slot = i; break; } 3234 } 3235 if (slot < 0) slot = 0; // steal first 3236 3237 SampleVoice *sv = &audio->sample_voices[slot]; 3238 sv->active = 1; 3239 sv->loop = loop; 3240 sv->position = 0.0; 3241 // Speed: pitch ratio * rate conversion (capture rate → output rate) 3242 sv->speed = (freq / base_freq) * ((double)audio->sample_rate / (double)audio->actual_rate); 3243 sv->volume = volume; 3244 sv->pan = pan; 3245 sv->fade = 0.0; 3246 sv->fade_target = 1.0; 3247 sv->id = audio->sample_next_id++; 3248 3249 pthread_mutex_unlock(&audio->lock); 3250 ac_log("[sample] play freq=%.1f base=%.1f speed=%.4f rate=%u/%u len=%d id=%lu\n", 3251 freq, base_freq, sv->speed, audio->sample_rate, audio->actual_rate, 3252 audio->sample_len, (unsigned long)sv->id); 3253 return sv->id; 3254} 3255 3256uint64_t audio_replay_play(ACAudio *audio, double freq, double base_freq, 3257 double volume, double pan, int loop) { 3258 if (!audio || audio->replay_len == 0) return 0; 3259 pthread_mutex_lock(&audio->lock); 3260 3261 SampleVoice *sv = &audio->replay_voice; 3262 sv->active = 1; 3263 sv->loop = loop; 3264 sv->position = 0.0; 3265 sv->speed = (freq / base_freq) * ((double)audio->replay_rate / (double)audio->actual_rate); 3266 sv->volume = volume; 3267 sv->pan = pan; 3268 sv->fade = 0.0; 3269 sv->fade_target = 1.0; 3270 sv->id = audio->sample_next_id++; 3271 3272 pthread_mutex_unlock(&audio->lock); 3273 return sv->id; 3274} 3275 3276void audio_sample_kill(ACAudio *audio, uint64_t id, double fade) { 3277 if (!audio) return; 3278 pthread_mutex_lock(&audio->lock); 3279 for (int i = 0; i < AUDIO_MAX_SAMPLE_VOICES; i++) { 3280 if (audio->sample_voices[i].active && audio->sample_voices[i].id == id) { 3281 if (fade <= 0.001) { 3282 audio->sample_voices[i].active = 0; 3283 } else { 3284 audio->sample_voices[i].fade_target = 0.0; 3285 } 3286 break; 3287 } 3288 } 3289 pthread_mutex_unlock(&audio->lock); 3290} 3291 3292void audio_replay_kill(ACAudio *audio, uint64_t id, double fade) { 3293 if (!audio) return; 3294 pthread_mutex_lock(&audio->lock); 3295 SampleVoice *sv = &audio->replay_voice; 3296 if (sv->active && sv->id == id) { 3297 if (fade <= 0.001) sv->active = 0; 3298 else sv->fade_target = 0.0; 3299 } 3300 pthread_mutex_unlock(&audio->lock); 3301} 3302 3303void audio_sample_update(ACAudio *audio, uint64_t id, double freq, 3304 double base_freq, double volume, double pan) { 3305 if (!audio) return; 3306 pthread_mutex_lock(&audio->lock); 3307 for (int i = 0; i < AUDIO_MAX_SAMPLE_VOICES; i++) { 3308 SampleVoice *sv = &audio->sample_voices[i]; 3309 if (sv->active && sv->id == id) { 3310 if (freq > 0 && base_freq > 0) 3311 sv->speed = (freq / base_freq) * ((double)audio->sample_rate / (double)audio->actual_rate); 3312 if (volume >= 0) sv->volume = volume; 3313 if (pan > -2) sv->pan = pan; 3314 break; 3315 } 3316 } 3317 pthread_mutex_unlock(&audio->lock); 3318} 3319 3320void audio_replay_update(ACAudio *audio, uint64_t id, double freq, 3321 double base_freq, double volume, double pan) { 3322 if (!audio) return; 3323 pthread_mutex_lock(&audio->lock); 3324 SampleVoice *sv = &audio->replay_voice; 3325 if (sv->active && sv->id == id) { 3326 if (freq > 0 && base_freq > 0) 3327 sv->speed = (freq / base_freq) * ((double)audio->replay_rate / (double)audio->actual_rate); 3328 if (volume >= 0) sv->volume = volume; 3329 if (pan > -2) sv->pan = pan; 3330 } 3331 pthread_mutex_unlock(&audio->lock); 3332} 3333 3334// Read current Master mixer volume as 0-100 percentage 3335static int read_system_volume_card(int card) { 3336 snd_mixer_t *mixer = NULL; 3337 if (snd_mixer_open(&mixer, 0) < 0) return -1; 3338 char card_name[16]; 3339 snprintf(card_name, sizeof(card_name), "hw:%d", card); 3340 snd_mixer_attach(mixer, card_name); 3341 snd_mixer_selem_register(mixer, NULL, NULL); 3342 snd_mixer_load(mixer); 3343 3344 int pct = -1; 3345 snd_mixer_elem_t *elem; 3346 for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { 3347 if (!snd_mixer_selem_is_active(elem)) continue; 3348 if (strcasecmp(snd_mixer_selem_get_name(elem), "Master") != 0) continue; 3349 if (snd_mixer_selem_has_playback_volume(elem)) { 3350 long min, max, cur; 3351 snd_mixer_selem_get_playback_volume_range(elem, &min, &max); 3352 snd_mixer_selem_get_playback_volume(elem, 0, &cur); 3353 if (max > min) pct = (int)((cur - min) * 100 / (max - min)); 3354 } 3355 break; 3356 } 3357 snd_mixer_close(mixer); 3358 return pct; 3359} 3360 3361static int muted = 0; 3362static long pre_mute_volume = -1; 3363 3364// Unmute all playback switches in the mixer — but skip jack-gated 3365// ones so we don't re-enable the headphone DAPM path (see audio_init 3366// above for the MAX98360A silencing story). 3367static void unmute_all_switches(snd_mixer_t *mixer) { 3368 snd_mixer_elem_t *elem; 3369 const char *jack_gated[] = { 3370 "Headphone Jack", "Headphone Jack Switch", 3371 "HPOL Playback", "HPOR Playback", "Headset", NULL 3372 }; 3373 for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { 3374 if (!snd_mixer_selem_is_active(elem)) continue; 3375 if (!snd_mixer_selem_has_playback_switch(elem)) continue; 3376 const char *name = snd_mixer_selem_get_name(elem); 3377 int skip = 0; 3378 for (int j = 0; jack_gated[j]; j++) { 3379 if (name && strstr(name, jack_gated[j])) { skip = 1; break; } 3380 } 3381 if (!skip) 3382 snd_mixer_selem_set_playback_switch_all(elem, 1); 3383 } 3384} 3385 3386void audio_volume_adjust(ACAudio *audio, int delta) { 3387 if (!audio || !audio->pcm) return; 3388 3389 char card_name[16]; 3390 snprintf(card_name, sizeof(card_name), "hw:%d", audio->card_index); 3391 3392 snd_mixer_t *mixer = NULL; 3393 if (snd_mixer_open(&mixer, 0) < 0) return; 3394 snd_mixer_attach(mixer, card_name); 3395 snd_mixer_selem_register(mixer, NULL, NULL); 3396 snd_mixer_load(mixer); 3397 3398 // Adjust ALL playback volume elements — on Realtek ALC codecs, 3399 // Master controls digital gain, Speaker/Headphone control the amplifier. 3400 // Both need to be set for audible volume change. 3401 /* RT5682 exposes "DAC1" for digital volume; SOF cards expose 3402 * "PGA*.0 * Master" pipeline PGAs. HDA laptops expose Master/PCM. 3403 * Try everything — first match wins but we run through the whole 3404 * list so volume keys work regardless of hardware. */ 3405 const char *try_names[] = {"Master", "Speaker", "Headphone", "PCM", 3406 "DAC1", "DAC2", 3407 "PGA1.0 1 Master", "PGA2.0 2 Master", 3408 "PGA5.0 5 Master", "PGA6.0 6 Master", 3409 "PGA7.0 7 Master", NULL}; 3410 int adjusted = 0; 3411 for (int n = 0; try_names[n]; n++) { 3412 snd_mixer_elem_t *elem = NULL; 3413 for (snd_mixer_elem_t *e = snd_mixer_first_elem(mixer); e; e = snd_mixer_elem_next(e)) { 3414 if (!snd_mixer_selem_is_active(e)) continue; 3415 const char *name = snd_mixer_selem_get_name(e); 3416 if (strcasecmp(name, try_names[n]) == 0 && snd_mixer_selem_has_playback_volume(e)) 3417 { elem = e; break; } 3418 } 3419 if (!elem) continue; 3420 3421 if (delta == 0) { 3422 // Toggle mute 3423 long min, max, cur; 3424 snd_mixer_selem_get_playback_volume_range(elem, &min, &max); 3425 snd_mixer_selem_get_playback_volume(elem, 0, &cur); 3426 if (!muted) { 3427 pre_mute_volume = cur; 3428 snd_mixer_selem_set_playback_volume_all(elem, min); 3429 } else { 3430 long restore = (pre_mute_volume > min) ? pre_mute_volume : max * 80 / 100; 3431 snd_mixer_selem_set_playback_volume_all(elem, restore); 3432 } 3433 ac_log("[audio] volume: mute toggle '%s' on %s\n", try_names[n], card_name); 3434 } else { 3435 long min, max, cur; 3436 snd_mixer_selem_get_playback_volume_range(elem, &min, &max); 3437 snd_mixer_selem_get_playback_volume(elem, 0, &cur); 3438 long step = (max - min) * 5 / 100; 3439 if (step < 1) step = 1; 3440 long newvol = cur + step * delta; 3441 if (newvol < min) newvol = min; 3442 if (newvol > max) newvol = max; 3443 snd_mixer_selem_set_playback_volume_all(elem, newvol); 3444 ac_log("[audio] volume: '%s' %ld→%ld (range %ld-%ld)\n", try_names[n], cur, newvol, min, max); 3445 } 3446 adjusted++; 3447 } 3448 if (delta == 0) { muted = !muted; } 3449 if (adjusted) { 3450 unmute_all_switches(mixer); 3451 if (delta != 0) muted = 0; 3452 } else { 3453 // No elements found — log what's available 3454 ac_log("[audio] volume: no playback elements on %s. Available:\n", card_name); 3455 for (snd_mixer_elem_t *e = snd_mixer_first_elem(mixer); e; e = snd_mixer_elem_next(e)) 3456 ac_log("[audio] %s%s\n", snd_mixer_selem_get_name(e), 3457 snd_mixer_selem_has_playback_volume(e) ? " [vol]" : ""); 3458 } 3459 snd_mixer_close(mixer); 3460 3461 // Update cached system volume. On SOF cards without a "Master" mixer, 3462 // read_system_volume_card returns -1. In that case, use software-only 3463 // volume: start at 100 and step ±5 with volume keys. 3464 if (muted) { 3465 audio->system_volume = 0; 3466 } else { 3467 int hw_vol = read_system_volume_card(audio->card_index); 3468 if (hw_vol >= 0) { 3469 audio->system_volume = hw_vol; 3470 } else { 3471 // No Master mixer — software gain mode 3472 int sv = audio->system_volume; 3473 if (sv < 0) sv = 100; // first call: default 100% 3474 if (delta > 0) sv = (sv + 5 > 100) ? 100 : sv + 5; 3475 else if (delta < 0) sv = (sv - 5 < 0) ? 0 : sv - 5; 3476 audio->system_volume = sv; 3477 ac_log("[audio] Software volume: %d%%\n", sv); 3478 } 3479 } 3480} 3481 3482void audio_boot_beep(ACAudio *audio) { 3483 if (!audio || !audio->pcm) return; 3484 // Two-tone "doo-dah" — distinct from old single ping (OTA test marker) 3485 audio_synth(audio, WAVE_SINE, 660.0, 0.12, 0.8, 0.002, 0.08, -0.15); // E5 3486 usleep(80000); 3487 audio_synth(audio, WAVE_SINE, 990.0, 0.15, 0.9, 0.002, 0.10, 0.15); // B5 3488} 3489 3490// Prewarm: play a near-silent note so ALSA buffers are filled and ready 3491void audio_prewarm(ACAudio *audio) { 3492 if (!audio || !audio->pcm) return; 3493 audio_synth(audio, WAVE_SINE, 440.0, 0.05, 0.001, 0.001, 0.04, 0.0); 3494} 3495 3496void audio_ready_melody(ACAudio *audio) { 3497 if (!audio || !audio->pcm) return; 3498 // Quick ascending 3-note toot: C5 → E5 → G5 (major triad) at full volume 3499 audio_synth(audio, WAVE_TRIANGLE, 523.25, 0.15, 0.7, 0.003, 0.10, -0.2); // C5 3500 usleep(60000); // 60ms gap 3501 audio_synth(audio, WAVE_TRIANGLE, 659.25, 0.15, 0.7, 0.003, 0.10, 0.0); // E5 3502 usleep(60000); 3503 audio_synth(audio, WAVE_TRIANGLE, 783.99, 0.20, 0.8, 0.003, 0.14, 0.2); // G5 3504} 3505 3506void audio_shutdown_sound(ACAudio *audio) { 3507 if (!audio || !audio->pcm) return; 3508 // Descending 3-note chime: G5 → E5 → C5 at full volume 3509 audio_synth(audio, WAVE_TRIANGLE, 783.99, 0.15, 0.7, 0.003, 0.10, 0.2); // G5 3510 usleep(60000); 3511 audio_synth(audio, WAVE_TRIANGLE, 659.25, 0.15, 0.7, 0.003, 0.10, 0.0); // E5 3512 usleep(60000); 3513 audio_synth(audio, WAVE_TRIANGLE, 523.25, 0.20, 0.8, 0.003, 0.14, -0.2); // C5 3514 // Wait for notes to finish playing before shutdown 3515 usleep(250000); 3516} 3517 3518// Save sample buffer to disk as raw floats with a small header 3519// Format: [uint32_t sample_rate] [uint32_t sample_len] [float * sample_len] 3520int audio_sample_save(ACAudio *audio, const char *path) { 3521 if (!audio || !audio->sample_buf || audio->sample_len <= 0) return -1; 3522 FILE *f = fopen(path, "wb"); 3523 if (!f) return -1; 3524 uint32_t rate = (uint32_t)audio->sample_rate; 3525 uint32_t len = (uint32_t)audio->sample_len; 3526 fwrite(&rate, sizeof(rate), 1, f); 3527 fwrite(&len, sizeof(len), 1, f); 3528 fwrite(audio->sample_buf, sizeof(float), len, f); 3529 fclose(f); 3530 sync(); 3531 return (int)len; 3532} 3533 3534// Load sample buffer from disk 3535int audio_sample_load(ACAudio *audio, const char *path) { 3536 if (!audio || !audio->sample_buf) return -1; 3537 FILE *f = fopen(path, "rb"); 3538 if (!f) return -1; 3539 uint32_t rate, len; 3540 if (fread(&rate, sizeof(rate), 1, f) != 1 || 3541 fread(&len, sizeof(len), 1, f) != 1) { 3542 fclose(f); 3543 return -1; 3544 } 3545 if (len > (uint32_t)audio->sample_max_len) len = (uint32_t)audio->sample_max_len; 3546 if (fread(audio->sample_buf, sizeof(float), len, f) != len) { 3547 fclose(f); 3548 return -1; 3549 } 3550 fclose(f); 3551 audio->sample_len = (int)len; 3552 audio->sample_rate = (int)rate; 3553 return (int)len; 3554} 3555 3556// --- DJ deck API --- 3557 3558int audio_deck_load(ACAudio *audio, int deck, const char *path) { 3559 if (!audio || deck < 0 || deck >= AUDIO_MAX_DECKS) return -1; 3560 ACDeck *dk = &audio->decks[deck]; 3561 3562 // Create decoder if needed 3563 if (!dk->decoder) { 3564 dk->decoder = deck_decoder_create(audio->actual_rate); 3565 if (!dk->decoder) return -1; 3566 } 3567 3568 dk->playing = 0; 3569 dk->active = 0; 3570 int ret = deck_decoder_load(dk->decoder, path); 3571 if (ret == 0) { 3572 dk->active = 1; 3573 // Generate waveform peaks for visualization (decoded in background thread) 3574 deck_decoder_generate_peaks(dk->decoder, 1024); 3575 } 3576 return ret; 3577} 3578 3579void audio_deck_play(ACAudio *audio, int deck) { 3580 if (!audio || deck < 0 || deck >= AUDIO_MAX_DECKS) return; 3581 ACDeck *dk = &audio->decks[deck]; 3582 if (!dk->active || !dk->decoder) return; 3583 dk->playing = 1; 3584 deck_decoder_play(dk->decoder); 3585} 3586 3587void audio_deck_pause(ACAudio *audio, int deck) { 3588 if (!audio || deck < 0 || deck >= AUDIO_MAX_DECKS) return; 3589 ACDeck *dk = &audio->decks[deck]; 3590 if (!dk->decoder) return; 3591 dk->playing = 0; 3592 deck_decoder_pause(dk->decoder); 3593} 3594 3595void audio_deck_seek(ACAudio *audio, int deck, double seconds) { 3596 if (!audio || deck < 0 || deck >= AUDIO_MAX_DECKS) return; 3597 ACDeck *dk = &audio->decks[deck]; 3598 if (!dk->active || !dk->decoder) return; 3599 deck_decoder_seek(dk->decoder, seconds); 3600} 3601 3602void audio_deck_set_speed(ACAudio *audio, int deck, double speed) { 3603 if (!audio || deck < 0 || deck >= AUDIO_MAX_DECKS) return; 3604 ACDeck *dk = &audio->decks[deck]; 3605 if (!dk->decoder) return; 3606 deck_decoder_set_speed(dk->decoder, speed); 3607} 3608 3609void audio_deck_set_volume(ACAudio *audio, int deck, float vol) { 3610 if (!audio || deck < 0 || deck >= AUDIO_MAX_DECKS) return; 3611 if (vol < 0.0f) vol = 0.0f; 3612 if (vol > 1.0f) vol = 1.0f; 3613 audio->decks[deck].volume = vol; 3614} 3615 3616void audio_deck_set_crossfader(ACAudio *audio, float value) { 3617 if (!audio) return; 3618 if (value < 0.0f) value = 0.0f; 3619 if (value > 1.0f) value = 1.0f; 3620 audio->crossfader = value; 3621} 3622 3623void audio_deck_set_master_volume(ACAudio *audio, float value) { 3624 if (!audio) return; 3625 if (value < 0.0f) value = 0.0f; 3626 if (value > 1.0f) value = 1.0f; 3627 audio->deck_master_volume = value; 3628} 3629 3630void audio_destroy(ACAudio *audio) { 3631 if (!audio) return; 3632 audio->running = 0; 3633 audio_mic_close(audio); 3634 // Destroy DJ decks 3635 for (int d = 0; d < AUDIO_MAX_DECKS; d++) { 3636 if (audio->decks[d].decoder) { 3637 deck_decoder_destroy(audio->decks[d].decoder); 3638 audio->decks[d].decoder = NULL; 3639 } 3640 } 3641 if (audio->pcm) { 3642 pthread_join(audio->thread, NULL); 3643 snd_pcm_close((snd_pcm_t *)audio->pcm); 3644 } 3645 if (audio->headphone_pcm) snd_pcm_close((snd_pcm_t *)audio->headphone_pcm); 3646 if (audio->hdmi_pcm) snd_pcm_close((snd_pcm_t *)audio->hdmi_pcm); 3647 free(audio->room_buf_l); 3648 free(audio->room_buf_r); 3649 free(audio->sample_buf); 3650 free(audio->sample_buf_back); 3651 free(audio->mic_ring); 3652 free(audio->replay_buf); 3653 free(audio->replay_buf_back); 3654 free(audio->output_history_buf); 3655 free(audio->tts_buf); 3656 pthread_mutex_destroy(&audio->lock); 3657 free(audio); 3658}