ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
mixer.c
Go to the documentation of this file.
1
7#include "audio/audio.h"
8#include "audio/mixer.h"
9#include "common.h"
10#include "asciichat_errno.h" // For asciichat_errno system
11#include "util/time.h" // For timing instrumentation
12#include "util/bits.h" // For find_first_set_bit
13#include "util/overflow.h" // For overflow checking
14#include <math.h>
15#include <string.h>
16#include <stdint.h>
17
18#ifndef M_PI
19#define M_PI 3.14159265358979323846
20#endif
21
22// Utility functions
23float db_to_linear(float db) {
24 return powf(10.0f, db / 20.0f);
25}
26
27float linear_to_db(float linear) {
28 return 20.0f * log10f(fmaxf(linear, 1e-12f));
29}
30
31float clamp_float(float value, float min, float max) {
32 if (value < min) {
33 return min;
34 }
35 if (value > max) {
36 return max;
37 }
38 return value;
39}
40
41// Compressor implementation
42void compressor_init(compressor_t *comp, float sample_rate) {
43 comp->sample_rate = sample_rate;
44 comp->envelope = 0.0f;
45 comp->gain_lin = 1.0f;
46
47 // Set default parameters with +6dB makeup gain
48 // The client playback path now has proper soft clipping to handle any peaks
49 // Server ducking (-6dB) + crowd scaling (-3dB) needs compensation
50 compressor_set_params(comp, -10.0f, 4.0f, 10.0f, 100.0f, 6.0f);
51}
52
53void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, float attack_ms, float release_ms,
54 float makeup_dB) {
55 comp->threshold_dB = threshold_dB;
56 comp->ratio = ratio;
57 comp->attack_ms = attack_ms;
58 comp->release_ms = release_ms;
59 comp->makeup_dB = makeup_dB;
60 comp->knee_dB = 2.0f; // Fixed soft knee
61
62 // Calculate time constants
63 float attack_tau = attack_ms / 1000.0f;
64 float release_tau = release_ms / 1000.0f;
65 comp->attack_coeff = expf(-1.0f / (attack_tau * comp->sample_rate + 1e-12f));
66 comp->release_coeff = expf(-1.0f / (release_tau * comp->sample_rate + 1e-12f));
67}
68
69static float compressor_gain_reduction_db(const compressor_t *comp, float level_dB) {
70 float over = level_dB - comp->threshold_dB;
71 float knee = comp->knee_dB;
72
73 if (knee > 0.0f) {
74 if (over <= -knee * 0.5f)
75 return 0.0f;
76 if (over >= knee * 0.5f)
77 return (1.0f / comp->ratio - 1.0f) * over;
78 float x = over + knee * 0.5f;
79 return (1.0f / comp->ratio - 1.0f) * (x * x) / (2.0f * knee);
80 }
81 if (over <= 0.0f) {
82 return 0.0f;
83 }
84 return (1.0f / comp->ratio - 1.0f) * over;
85}
86
87float compressor_process_sample(compressor_t *comp, float sidechain) {
88 float x = fabsf(sidechain);
89
90 // Update envelope with attack/release
91 if (x > comp->envelope)
92 comp->envelope = comp->attack_coeff * comp->envelope + (1.0f - comp->attack_coeff) * x;
93 else
94 comp->envelope = comp->release_coeff * comp->envelope + (1.0f - comp->release_coeff) * x;
95
96 // Calculate gain reduction
97 float level_dB = linear_to_db(comp->envelope);
98 float gr_dB = compressor_gain_reduction_db(comp, level_dB);
99 float target_lin = db_to_linear(gr_dB + comp->makeup_dB);
100
101 // Smooth gain changes
102 if (target_lin < comp->gain_lin)
103 comp->gain_lin = comp->attack_coeff * comp->gain_lin + (1.0f - comp->attack_coeff) * target_lin;
104 else
105 comp->gain_lin = comp->release_coeff * comp->gain_lin + (1.0f - comp->release_coeff) * target_lin;
106
107 return comp->gain_lin;
108}
109
110// Ducking implementation
111int ducking_init(ducking_t *duck, int num_sources, float sample_rate) {
112 if (!duck) {
113 return SET_ERRNO(ERROR_INVALID_PARAM, "ducking_init: duck is NULL");
114 }
115
116 // Set default parameters
117 // threshold_dB: sources below this aren't considered "speaking"
118 // leader_margin_dB: sources within this margin of loudest are all "leaders"
119 // atten_dB: how much to attenuate non-leaders (was -12dB, too aggressive)
120 duck->threshold_dB = -45.0f; // More lenient threshold
121 duck->leader_margin_dB = 6.0f; // Wider margin = more sources treated as leaders
122 duck->atten_dB = -6.0f; // Only -6dB attenuation (was -12dB)
123 duck->attack_ms = 10.0f; // Slower attack (was 5ms)
124 duck->release_ms = 200.0f; // Slower release (was 100ms)
125 duck->envelope = NULL;
126 duck->gain = NULL;
127
128 // Calculate time constants
129 float attack_tau = duck->attack_ms / 1000.0f;
130 float release_tau = duck->release_ms / 1000.0f;
131 duck->attack_coeff = expf(-1.0f / (attack_tau * sample_rate + 1e-12f));
132 duck->release_coeff = expf(-1.0f / (release_tau * sample_rate + 1e-12f));
133
134 // Allocate arrays with overflow checking
135 size_t envelope_size = 0;
136 if (checked_size_mul((size_t)num_sources, sizeof(float), &envelope_size) != ASCIICHAT_OK) {
137 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Ducking envelope array size overflow: %d sources", num_sources);
138 }
139 duck->envelope = SAFE_MALLOC(envelope_size, float *);
140 if (!duck->envelope) {
141 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ducking envelope array");
142 }
143
144 size_t gain_size = 0;
145 if (checked_size_mul((size_t)num_sources, sizeof(float), &gain_size) != ASCIICHAT_OK) {
146 SAFE_FREE(duck->envelope);
147 duck->envelope = NULL;
148 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Ducking gain array size overflow: %d sources", num_sources);
149 }
150 duck->gain = SAFE_MALLOC(gain_size, float *);
151 if (!duck->gain) {
152 SAFE_FREE(duck->envelope);
153 duck->envelope = NULL;
154 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ducking gain array");
155 }
156
157 // Initialize
158 SAFE_MEMSET(duck->envelope, (size_t)num_sources * sizeof(float), 0, (size_t)num_sources * sizeof(float));
159 for (int i = 0; i < num_sources; i++) {
160 duck->gain[i] = 1.0f;
161 }
162
163 return ASCIICHAT_OK;
164}
165
167 if (duck->envelope) {
168 SAFE_FREE(duck->envelope);
169 }
170 if (duck->gain) {
171 SAFE_FREE(duck->gain);
172 }
173}
174
175void ducking_set_params(ducking_t *duck, float threshold_dB, float leader_margin_dB, float atten_dB, float attack_ms,
176 float release_ms) {
177 duck->threshold_dB = threshold_dB;
178 duck->leader_margin_dB = leader_margin_dB;
179 duck->atten_dB = atten_dB;
180 duck->attack_ms = attack_ms;
181 duck->release_ms = release_ms;
182}
183
184void ducking_process_frame(ducking_t *duck, float *envelopes, float *gains, int num_sources) {
185 // Find the loudest source
186 float max_dB = -120.0f;
187 float env_dB[MIXER_MAX_SOURCES];
188
189 for (int i = 0; i < num_sources; i++) {
190 env_dB[i] = linear_to_db(envelopes[i]);
191 if (env_dB[i] > max_dB)
192 max_dB = env_dB[i];
193 }
194
195 // Calculate ducking gain for each source
196 float leader_cut = db_to_linear(duck->atten_dB);
197
198 for (int i = 0; i < num_sources; i++) {
199 bool is_speaking = env_dB[i] > duck->threshold_dB;
200 bool is_leader = is_speaking && (env_dB[i] >= max_dB - duck->leader_margin_dB);
201
202 float target;
203 if (is_speaking && !is_leader) {
204 target = leader_cut;
205 } else {
206 target = 1.0f;
207 }
208
209 // Smooth gain transitions
210 if (target < gains[i])
211 gains[i] = duck->attack_coeff * gains[i] + (1.0f - duck->attack_coeff) * target;
212 else
213 gains[i] = duck->release_coeff * gains[i] + (1.0f - duck->release_coeff) * target;
214 }
215}
216
217// Mixer implementation
218mixer_t *mixer_create(int max_sources, int sample_rate) {
219 // Validate parameters
220 if (max_sources <= 0 || max_sources > MIXER_MAX_SOURCES) {
221 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid max_sources: %d (must be 1-%d)", max_sources, MIXER_MAX_SOURCES);
222 return NULL;
223 }
224
225 if (sample_rate <= 0 || sample_rate > 192000) {
226 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid sample_rate: %d (must be 1-192000)", sample_rate);
227 return NULL;
228 }
229
230 mixer_t *mixer;
231 mixer = SAFE_MALLOC(sizeof(mixer_t), mixer_t *);
232 if (!mixer) {
233 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mixer structure");
234 return NULL;
235 }
236
237 mixer->num_sources = 0;
238 mixer->max_sources = max_sources;
239 mixer->sample_rate = sample_rate;
240
241 // Allocate source management arrays with overflow checking
242 size_t buffers_size = 0;
243 if (checked_size_mul((size_t)max_sources, sizeof(audio_ring_buffer_t *), &buffers_size) != ASCIICHAT_OK) {
244 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer source buffers array overflow: %d sources", max_sources);
245 SAFE_FREE(mixer);
246 return NULL;
247 }
248 mixer->source_buffers = SAFE_MALLOC(buffers_size, audio_ring_buffer_t **);
249 if (!mixer->source_buffers) {
250 SAFE_FREE(mixer);
251 return NULL;
252 }
253
254 size_t ids_size = 0;
255 if (checked_size_mul((size_t)max_sources, sizeof(uint32_t), &ids_size) != ASCIICHAT_OK) {
256 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer source IDs array overflow: %d sources", max_sources);
258 SAFE_FREE(mixer);
259 return NULL;
260 }
261 mixer->source_ids = SAFE_MALLOC(ids_size, uint32_t *);
262 if (!mixer->source_ids) {
264 SAFE_FREE(mixer);
265 return NULL;
266 }
267
268 size_t active_size = 0;
269 if (checked_size_mul((size_t)max_sources, sizeof(bool), &active_size) != ASCIICHAT_OK) {
270 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer source active array overflow: %d sources", max_sources);
272 SAFE_FREE(mixer->source_ids);
273 SAFE_FREE(mixer);
274 return NULL;
275 }
276 mixer->source_active = SAFE_MALLOC(active_size, bool *);
277 if (!mixer->source_active) {
279 SAFE_FREE(mixer->source_ids);
280 SAFE_FREE(mixer);
281 return NULL;
282 }
283
284 // Initialize arrays
285 SAFE_MEMSET((void *)mixer->source_buffers, buffers_size, 0, buffers_size);
286 SAFE_MEMSET(mixer->source_ids, ids_size, 0, ids_size);
287 SAFE_MEMSET(mixer->source_active, active_size, 0, active_size);
288
289 // OPTIMIZATION 1: Initialize bitset optimization structures
290 mixer->active_sources_mask = 0ULL; // No sources active initially
291 SAFE_MEMSET(mixer->source_id_to_index, sizeof(mixer->source_id_to_index), 0xFF,
292 sizeof(mixer->source_id_to_index)); // 0xFF = invalid index
293
294 // Allocate mix buffer BEFORE rwlock_init so cleanup path is correct
295 size_t mix_buffer_size = 0;
296 if (checked_size_mul(MIXER_FRAME_SIZE, sizeof(float), &mix_buffer_size) != ASCIICHAT_OK) {
297 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer mix buffer size overflow: %zu samples", (size_t)MIXER_FRAME_SIZE);
299 SAFE_FREE(mixer->source_ids);
300 SAFE_FREE(mixer->source_active);
301 SAFE_FREE(mixer);
302 return NULL;
303 }
304 mixer->mix_buffer = SAFE_MALLOC(mix_buffer_size, float *);
305 if (!mixer->mix_buffer) {
306 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mix buffer");
308 SAFE_FREE(mixer->source_ids);
309 SAFE_FREE(mixer->source_active);
310 SAFE_FREE(mixer);
311 return NULL;
312 }
313
314 // OPTIMIZATION 2: Initialize reader-writer lock
315 if (rwlock_init(&mixer->source_lock) != 0) {
316 SET_ERRNO(ERROR_THREAD, "Failed to initialize mixer source lock");
318 SAFE_FREE(mixer->source_ids);
319 SAFE_FREE(mixer->source_active);
320 SAFE_FREE(mixer->mix_buffer);
321 SAFE_FREE(mixer);
322 return NULL;
323 }
324
325 // Set crowd scaling parameters
326 mixer->crowd_alpha = 0.5f; // Square root scaling
327 mixer->base_gain = 1.0f; // Unity gain - let compressor handle loudness, avoid clipping
328
329 // Initialize processing
330 if (ducking_init(&mixer->ducking, max_sources, (float)sample_rate) != ASCIICHAT_OK) {
333 SAFE_FREE(mixer->source_ids);
334 SAFE_FREE(mixer->source_active);
335 SAFE_FREE(mixer->mix_buffer);
336 SAFE_FREE(mixer);
337 return NULL;
338 }
339 compressor_init(&mixer->compressor, (float)sample_rate);
340
341 log_info("Audio mixer created: max_sources=%d, sample_rate=%d", max_sources, sample_rate);
342
343 return mixer;
344}
345
346void mixer_destroy(mixer_t *mixer) {
347 if (!mixer)
348 return;
349
350 // OPTIMIZATION 2: Destroy reader-writer lock
352
353 ducking_free(&mixer->ducking);
354
356 SAFE_FREE(mixer->source_ids);
357 SAFE_FREE(mixer->source_active);
358 SAFE_FREE(mixer->mix_buffer);
359 SAFE_FREE(mixer);
360 log_info("Audio mixer destroyed");
361}
362
363int mixer_add_source(mixer_t *mixer, uint32_t client_id, audio_ring_buffer_t *buffer) {
364 if (!mixer || !buffer)
365 return -1;
366
367 // OPTIMIZATION 2: Acquire write lock for source modification
368 rwlock_wrlock(&mixer->source_lock);
369
370 // Find an empty slot
371 int slot = -1;
372 for (int i = 0; i < mixer->max_sources; i++) {
373 if (mixer->source_ids[i] == 0) {
374 slot = i;
375 break;
376 }
377 }
378
379 if (slot == -1) {
381 log_warn("Mixer: No available slots for client %u", client_id);
382 return -1;
383 }
384
385 mixer->source_buffers[slot] = buffer;
386 mixer->source_ids[slot] = client_id;
387 mixer->source_active[slot] = true;
388 mixer->num_sources++;
389
390 // OPTIMIZATION 1: Update bitset optimization structures
391 mixer->active_sources_mask |= (1ULL << slot); // Set bit for this slot
392 mixer->source_id_to_index[client_id & 0xFF] = (uint8_t)slot; // Hash table: client_id → slot
393
395
396 log_info("Mixer: Added source for client %u at slot %d", client_id, slot);
397 return slot;
398}
399
400void mixer_remove_source(mixer_t *mixer, uint32_t client_id) {
401 if (!mixer)
402 return;
403
404 // OPTIMIZATION 2: Acquire write lock for source modification
405 rwlock_wrlock(&mixer->source_lock);
406
407 for (int i = 0; i < mixer->max_sources; i++) {
408 if (mixer->source_ids[i] == client_id) {
409 mixer->source_buffers[i] = NULL;
410 mixer->source_ids[i] = 0;
411 mixer->source_active[i] = false;
412 mixer->num_sources--;
413
414 // OPTIMIZATION 1: Update bitset optimization structures
415 mixer->active_sources_mask &= ~(1ULL << i); // Clear bit for this slot
416 mixer->source_id_to_index[client_id & 0xFF] = 0xFF; // Mark as invalid in hash table
417
418 // Reset ducking state for this source
419 mixer->ducking.envelope[i] = 0.0f;
420 mixer->ducking.gain[i] = 1.0f;
421
423
424 log_info("Mixer: Removed source for client %u from slot %d", client_id, i);
425 return;
426 }
427 }
428
430}
431
432void mixer_set_source_active(mixer_t *mixer, uint32_t client_id, bool active) {
433 if (!mixer)
434 return;
435
436 // OPTIMIZATION 2: Acquire write lock for source modification
437 rwlock_wrlock(&mixer->source_lock);
438
439 for (int i = 0; i < mixer->max_sources; i++) {
440 if (mixer->source_ids[i] == client_id) {
441 mixer->source_active[i] = active;
442
443 // OPTIMIZATION 1: Update bitset for active state change
444 if (active) {
445 mixer->active_sources_mask |= (1ULL << i); // Set bit
446 } else {
447 mixer->active_sources_mask &= ~(1ULL << i); // Clear bit
448 }
449
451 log_debug("Mixer: Set source %u active=%d", client_id, active);
452 return;
453 }
454 }
455
457}
458
459int mixer_process(mixer_t *mixer, float *output, int num_samples) {
460 if (!mixer || !output || num_samples <= 0)
461 return -1;
462
463 // THREAD SAFETY: Acquire read lock to protect against concurrent source add/remove
464 // This prevents race conditions where source_buffers[i] could be set to NULL while we read it
465 rwlock_rdlock(&mixer->source_lock);
466
467 // Clear output buffer
468 SAFE_MEMSET(output, num_samples * sizeof(float), 0, num_samples * sizeof(float));
469
470 // Count active sources
471 int active_count = 0;
472 for (int i = 0; i < mixer->max_sources; i++) {
473 if (mixer->source_ids[i] != 0 && mixer->source_active[i] && mixer->source_buffers[i]) {
474 active_count++;
475 }
476 }
477
478 if (active_count == 0) {
479 // No active sources, output silence
480 return 0;
481 }
482
483 // Process in frames for efficiency
484 for (int frame_start = 0; frame_start < num_samples; frame_start += MIXER_FRAME_SIZE) {
485 int frame_size = (frame_start + MIXER_FRAME_SIZE > num_samples) ? (num_samples - frame_start) : MIXER_FRAME_SIZE;
486
487 // Clear mix buffer
488 SAFE_MEMSET(mixer->mix_buffer, frame_size * sizeof(float), 0, frame_size * sizeof(float));
489
490 // Temporary buffers for source audio
491 float source_samples[MIXER_MAX_SOURCES][MIXER_FRAME_SIZE];
492 int source_count = 0;
493 int source_map[MIXER_MAX_SOURCES]; // Maps source index to slot
494
495 // Read from each active source
496 for (int i = 0; i < mixer->max_sources; i++) {
497 if (mixer->source_ids[i] != 0 && mixer->source_active[i] && mixer->source_buffers[i]) {
498 // Read samples from this source's ring buffer
499 size_t samples_read_size =
500 audio_ring_buffer_read(mixer->source_buffers[i], source_samples[source_count], frame_size);
501 int samples_read = (int)samples_read_size;
502
503 // Accept partial frames - pad with silence if needed
504 // This prevents audio dropouts when ring buffers are temporarily under-filled
505 if (samples_read > 0) {
506 // Pad remaining samples with silence if we got a partial frame
507 if (samples_read < frame_size) {
508 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) * sizeof(float), 0,
509 (frame_size - samples_read) * sizeof(float));
510 }
511
512 source_map[source_count] = i;
513 source_count++;
514
515 if (source_count >= MIXER_MAX_SOURCES)
516 break;
517 }
518 }
519 }
520
521 // OPTIMIZATION: Batch envelope calculation per-frame instead of per-sample
522 // Calculate peak amplitude for each source over the entire frame
523 int speaking_count = 0;
524
525 for (int i = 0; i < source_count; i++) {
526 int slot = source_map[i];
527 float peak = 0.0f;
528
529 // Find peak amplitude in frame (much faster than per-sample envelope)
530 for (int s = 0; s < frame_size; s++) {
531 float abs_sample = fabsf(source_samples[i][s]);
532 if (abs_sample > peak)
533 peak = abs_sample;
534 }
535
536 // Update envelope using frame peak (one update per frame instead of per-sample)
537 if (peak > mixer->ducking.envelope[slot]) {
538 mixer->ducking.envelope[slot] =
539 mixer->ducking.attack_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.attack_coeff) * peak;
540 } else {
541 mixer->ducking.envelope[slot] =
542 mixer->ducking.release_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.release_coeff) * peak;
543 }
544
545 // Count speaking sources
546 if (mixer->ducking.envelope[slot] > db_to_linear(-60.0f))
547 speaking_count++;
548 }
549
550 // Apply ducking ONCE per frame (not per-sample)
551 ducking_process_frame(&mixer->ducking, mixer->ducking.envelope, mixer->ducking.gain, mixer->max_sources);
552
553 // Calculate crowd scaling ONCE per frame
554 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((float)speaking_count, mixer->crowd_alpha)) : 1.0f;
555 float pre_bus = mixer->base_gain * crowd_gain;
556
557 // Pre-calculate combined gains for each source (ducking * pre_bus)
558 float combined_gains[MIXER_MAX_SOURCES];
559 for (int i = 0; i < source_count; i++) {
560 int slot = source_map[i];
561 combined_gains[i] = mixer->ducking.gain[slot] * pre_bus;
562 }
563
564 // OPTIMIZATION: Fast mixing loop - simple multiply-add with pre-calculated gains
565 // NO per-sample compressor to avoid expensive log/pow calls (480x per iteration)
566 for (int s = 0; s < frame_size; s++) {
567 float mix = 0.0f;
568 for (int i = 0; i < source_count; i++) {
569 mix += source_samples[i][s] * combined_gains[i];
570 }
571
572 // Store in mix buffer for frame-level compression below
573 mixer->mix_buffer[s] = mix;
574 }
575
576 // OPTIMIZATION: Apply compression ONCE per frame instead of per-sample
577 // This reduces expensive log10f/powf calls from 480x to 1x per iteration
578 // Calculate frame peak for compressor sidechain
579 float frame_peak = 0.0f;
580 for (int s = 0; s < frame_size; s++) {
581 float abs_val = fabsf(mixer->mix_buffer[s]);
582 if (abs_val > frame_peak)
583 frame_peak = abs_val;
584 }
585
586 // Process compressor with frame peak (1 call instead of 480)
587 float comp_gain = compressor_process_sample(&mixer->compressor, frame_peak);
588
589 // Apply compression gain and soft clipping to all samples
590 for (int s = 0; s < frame_size; s++) {
591 float mix = mixer->mix_buffer[s] * comp_gain;
592
593 // Compressor provides +6dB makeup gain for better audibility
594 // Soft clip threshold 1.0f allows full range without premature clipping
595 // Steepness 3.0 for smoother clipping behavior
596 output[frame_start + s] = soft_clip(mix, 1.0f, 3.0f);
597 }
598 }
599
601 return num_samples;
602}
603
604int mixer_process_excluding_source(mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id) {
605 if (!mixer || !output || num_samples <= 0)
606 return -1;
607
608 // Only use timing in debug builds - snprintf + hashtable ops are expensive in hot path
609#ifndef NDEBUG
610 START_TIMER("mixer_total");
611#endif
612
613 // THREAD SAFETY: Acquire read lock to protect against concurrent source add/remove
614 // This prevents race conditions where source_buffers[i] could be set to NULL while we read it
615 rwlock_rdlock(&mixer->source_lock);
616
617 // Clear output buffer
618 SAFE_MEMSET(output, num_samples * sizeof(float), 0, num_samples * sizeof(float));
619
620 // OPTIMIZATION 1: O(1) exclusion using bitset and hash table
621 uint8_t exclude_index = mixer->source_id_to_index[exclude_client_id & 0xFF];
622 uint64_t active_mask = mixer->active_sources_mask;
623
624 // Validate exclude_index before using in bitshift
625 // - exclude_index == 0xFF means "not found" (sentinel value from initialization)
626 // - exclude_index >= MIXER_MAX_SOURCES would cause undefined behavior in bitshift
627 // - Also verify hash table lookup actually matched the client_id (collision detection)
628 bool valid_exclude = (exclude_index < MIXER_MAX_SOURCES && exclude_index != 0xFF &&
629 mixer->source_ids[exclude_index] == exclude_client_id);
630
631 if (valid_exclude) {
632 // Check if this is the ONLY active source - if so, drain it to prevent buffer overflow
633 // With N>1 clients, other threads drain this buffer. With N=1, no one does.
634 uint64_t mask_without_excluded = active_mask & ~(1ULL << exclude_index);
635 if (mask_without_excluded == 0 && mixer->source_buffers[exclude_index]) {
636 // Solo client: drain their buffer to prevent overflow (discard samples)
637 // LOCK-FREE: Skip samples directly using atomic operations
638 audio_ring_buffer_t *rb = mixer->source_buffers[exclude_index];
639 if (rb) {
640 size_t available = audio_ring_buffer_available_read(rb);
641 size_t to_skip = ((size_t)num_samples < available) ? (size_t)num_samples : available;
642
643 // LOCK-FREE: Atomically advance read_index with release ordering
644 unsigned int old_read_idx = atomic_load_explicit(&rb->read_index, memory_order_relaxed);
645 unsigned int new_read_idx = (old_read_idx + to_skip) % AUDIO_RING_BUFFER_SIZE;
646 atomic_store_explicit(&rb->read_index, new_read_idx, memory_order_release);
647
648 log_debug_every(LOG_RATE_DEFAULT, "Mixer: Drained %zu samples for solo client %u (lock-free skip)", to_skip,
649 exclude_client_id);
650 }
651 }
652 active_mask = mask_without_excluded;
653
654 // DIAGNOSTIC: Log which client was excluded and which remain active
656 1000000,
657 "MIXER EXCLUSION: exclude_client=%u, exclude_index=%u, active_mask_before=0x%llx, active_mask_after=0x%llx",
658 exclude_client_id, exclude_index, (unsigned long long)(active_mask | (1ULL << exclude_index)),
659 (unsigned long long)active_mask);
660 } else {
661 // DIAGNOSTIC: Failed to exclude - log why
662 log_warn_every(1000000, "MIXER EXCLUSION FAILED: exclude_client=%u, exclude_index=%u (valid=%d), lookup_id=%u",
663 exclude_client_id, exclude_index, valid_exclude,
664 (exclude_index < MIXER_MAX_SOURCES && exclude_index != 0xFF) ? mixer->source_ids[exclude_index] : 0);
665 }
666
667 // Fast check: any sources to mix?
668 if (active_mask == 0) {
670#ifndef NDEBUG
671 STOP_TIMER("mixer_total");
672#endif
673 return 0; // No sources to mix (excluding the specified client), output silence
674 }
675
676 // Process in frames for efficiency
677 for (int frame_start = 0; frame_start < num_samples; frame_start += MIXER_FRAME_SIZE) {
678 int frame_size = (frame_start + MIXER_FRAME_SIZE > num_samples) ? (num_samples - frame_start) : MIXER_FRAME_SIZE;
679
680 struct timespec read_start, read_end;
681 (void)clock_gettime(CLOCK_MONOTONIC, &read_start);
682
683 // Clear mix buffer
684 SAFE_MEMSET(mixer->mix_buffer, frame_size * sizeof(float), 0, frame_size * sizeof(float));
685
686 // Temporary buffers for source audio
687 float source_samples[MIXER_MAX_SOURCES][MIXER_FRAME_SIZE];
688 int source_count = 0;
689 int source_map[MIXER_MAX_SOURCES]; // Maps source index to slot
690
691 // OPTIMIZATION 1: Iterate only over active sources using bitset
692 uint64_t current_mask = active_mask;
693 while (current_mask && source_count < MIXER_MAX_SOURCES) {
694 int i = find_first_set_bit(current_mask); // Portable: find first set bit
695 current_mask &= current_mask - 1; // Clear lowest set bit
696
697 // Verify source is valid (defensive programming)
698 if (i < mixer->max_sources && mixer->source_ids[i] != 0 && mixer->source_buffers[i]) {
699 // Read samples from this source's ring buffer
700 size_t samples_read_size =
701 audio_ring_buffer_read(mixer->source_buffers[i], source_samples[source_count], frame_size);
702 int samples_read = (int)samples_read_size;
703
704 // Accept partial frames - pad with silence if needed
705 // This prevents audio dropouts when ring buffers are temporarily under-filled
706 if (samples_read > 0) {
707 // Pad remaining samples with silence if we got a partial frame
708 if (samples_read < frame_size) {
709 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) * sizeof(float), 0,
710 (frame_size - samples_read) * sizeof(float));
711 }
712
713 // DIAGNOSTIC: Calculate RMS of this source's audio
714 float source_rms = 0.0f;
715 if (frame_start == 0) { // Only log for first frame to reduce spam
716 float sum_squares = 0.0f;
717 for (int s = 0; s < samples_read; s++) {
718 sum_squares += source_samples[source_count][s] * source_samples[source_count][s];
719 }
720 source_rms = sqrtf(sum_squares / (float)samples_read);
721 log_info_every(1000000, "MIXER SOURCE READ: client_id=%u, slot=%d, samples_read=%d, RMS=%.6f",
722 mixer->source_ids[i], i, samples_read, source_rms);
723 }
724
725 source_map[source_count] = i;
726 source_count++;
727 }
728 }
729 }
730
731 (void)clock_gettime(CLOCK_MONOTONIC, &read_end);
732 uint64_t read_time_us = ((uint64_t)read_end.tv_sec * 1000000 + (uint64_t)read_end.tv_nsec / 1000) -
733 ((uint64_t)read_start.tv_sec * 1000000 + (uint64_t)read_start.tv_nsec / 1000);
734
735 if (read_time_us > 10000) { // Log if reading sources takes > 10ms
736 log_warn_every(LOG_RATE_DEFAULT, "Mixer: Slow source reading took %lluus (%.2fms) for %d sources", read_time_us,
737 (float)read_time_us / 1000.0f, source_count);
738 }
739
740 // OPTIMIZATION: Batch envelope calculation per-frame instead of per-sample
741 // Calculate peak amplitude for each source over the entire frame
742 int speaking_count = 0;
743
744 for (int i = 0; i < source_count; i++) {
745 int slot = source_map[i];
746 float peak = 0.0f;
747
748 // Find peak amplitude in frame (much faster than per-sample envelope)
749 for (int s = 0; s < frame_size; s++) {
750 float abs_sample = fabsf(source_samples[i][s]);
751 if (abs_sample > peak)
752 peak = abs_sample;
753 }
754
755 // Update envelope using frame peak (one update per frame instead of per-sample)
756 if (peak > mixer->ducking.envelope[slot]) {
757 mixer->ducking.envelope[slot] =
758 mixer->ducking.attack_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.attack_coeff) * peak;
759 } else {
760 mixer->ducking.envelope[slot] =
761 mixer->ducking.release_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.release_coeff) * peak;
762 }
763
764 // Count speaking sources
765 if (mixer->ducking.envelope[slot] > db_to_linear(-60.0f))
766 speaking_count++;
767 }
768
769 // Apply ducking ONCE per frame (not per-sample)
770 ducking_process_frame(&mixer->ducking, mixer->ducking.envelope, mixer->ducking.gain, mixer->max_sources);
771
772 // Calculate crowd scaling ONCE per frame
773 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((float)speaking_count, mixer->crowd_alpha)) : 1.0f;
774 float pre_bus = mixer->base_gain * crowd_gain;
775
776 // Pre-calculate combined gains for each source (ducking * pre_bus)
777 float combined_gains[MIXER_MAX_SOURCES];
778 for (int i = 0; i < source_count; i++) {
779 int slot = source_map[i];
780 combined_gains[i] = mixer->ducking.gain[slot] * pre_bus;
781 }
782
783 // OPTIMIZATION: Fast mixing loop - simple multiply-add with pre-calculated gains
784 // NO per-sample compressor to avoid expensive log/pow calls (480x per iteration)
785 for (int s = 0; s < frame_size; s++) {
786 float mix = 0.0f;
787 for (int i = 0; i < source_count; i++) {
788 mix += source_samples[i][s] * combined_gains[i];
789 }
790
791 // Store in mix buffer for frame-level compression below
792 mixer->mix_buffer[s] = mix;
793 }
794
795 // OPTIMIZATION: Apply compression ONCE per frame instead of per-sample
796 // This reduces expensive log10f/powf calls from 480x to 1x per iteration
797 // Calculate frame peak for compressor sidechain
798 float frame_peak = 0.0f;
799 for (int s = 0; s < frame_size; s++) {
800 float abs_val = fabsf(mixer->mix_buffer[s]);
801 if (abs_val > frame_peak)
802 frame_peak = abs_val;
803 }
804
805 // Process compressor with frame peak (1 call instead of 480)
806 float comp_gain = compressor_process_sample(&mixer->compressor, frame_peak);
807
808 // Apply compression gain and soft clipping to all samples
809 for (int s = 0; s < frame_size; s++) {
810 float mix = mixer->mix_buffer[s] * comp_gain;
811
812 // Compressor provides +6dB makeup gain for better audibility
813 // Soft clip threshold 1.0f allows full range without premature clipping
814 // Steepness 3.0 for smoother clipping behavior
815 output[frame_start + s] = soft_clip(mix, 1.0f, 3.0f);
816 }
817 }
818
820
821#ifndef NDEBUG
822 double total_ns = STOP_TIMER("mixer_total");
823 if (total_ns > 2000000) { // > 2ms
824 char duration_str[32];
825 format_duration_ns(total_ns, duration_str, sizeof(duration_str));
826 log_warn("Slow mixer: total=%s, num_samples=%d", duration_str, num_samples);
827 }
828#endif
829
830 return num_samples;
831}
832
833/* ============================================================================
834 * Noise Gate Implementation
835 * ============================================================================
836 */
837
838void noise_gate_init(noise_gate_t *gate, float sample_rate) {
839 if (!gate)
840 return;
841
842 gate->sample_rate = sample_rate;
843 gate->envelope = 0.0f;
844 gate->gate_open = false;
845
846 // Default parameters
847 // Slower attack (10ms) prevents clicking artifacts during gate transitions
848 // threshold=0.01, attack=10ms, release=50ms, hysteresis=0.9
849 noise_gate_set_params(gate, 0.01f, 10.0f, 50.0f, 0.9f);
850}
851
852void noise_gate_set_params(noise_gate_t *gate, float threshold, float attack_ms, float release_ms, float hysteresis) {
853 if (!gate)
854 return;
855
856 gate->threshold = threshold;
857 gate->attack_ms = attack_ms;
858 gate->release_ms = release_ms;
859 gate->hysteresis = hysteresis;
860
861 // Calculate coefficients for envelope follower
862 // Using exponential moving average: coeff = 1 - exp(-1 / (time_ms * sample_rate / 1000))
863 gate->attack_coeff = 1.0f - expf(-1.0f / (attack_ms * gate->sample_rate / 1000.0f));
864 gate->release_coeff = 1.0f - expf(-1.0f / (release_ms * gate->sample_rate / 1000.0f));
865}
866
867float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude) {
868 if (!gate)
869 return input;
870
871 // Determine target state with hysteresis
872 float target;
873 if (gate->gate_open) {
874 // Gate is open - use lower threshold (hysteresis) to close
875 target = (peak_amplitude > gate->threshold * gate->hysteresis) ? 1.0f : 0.0f;
876 } else {
877 // Gate is closed - use normal threshold to open
878 target = (peak_amplitude > gate->threshold) ? 1.0f : 0.0f;
879 }
880
881 // Update gate state
882 gate->gate_open = (target > 0.5f);
883
884 // Update envelope with appropriate coefficient
885 float coeff = (target > gate->envelope) ? gate->attack_coeff : gate->release_coeff;
886 gate->envelope += coeff * (target - gate->envelope);
887
888 // Apply gate
889 return input * gate->envelope;
890}
891
892void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples) {
893 if (!gate || !buffer || num_samples <= 0)
894 return;
895
896 // First pass: find peak amplitude
897 float peak = 0.0f;
898 for (int i = 0; i < num_samples; i++) {
899 float abs_sample = fabsf(buffer[i]);
900 if (abs_sample > peak) {
901 peak = abs_sample;
902 }
903 }
904
905 // Second pass: apply gate
906 for (int i = 0; i < num_samples; i++) {
907 buffer[i] = noise_gate_process_sample(gate, buffer[i], peak);
908 }
909}
910
912 return gate ? gate->gate_open : false;
913}
914
915/* ============================================================================
916 * High-Pass Filter Implementation
917 * ============================================================================
918 */
919
920void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate) {
921 if (!filter)
922 return;
923
924 filter->cutoff_hz = cutoff_hz;
925 filter->sample_rate = sample_rate;
926
927 // Calculate filter coefficient
928 // alpha = 1 / (1 + 2*pi*fc/fs)
929 filter->alpha = 1.0f / (1.0f + 2.0f * M_PI * cutoff_hz / sample_rate);
930
931 highpass_filter_reset(filter);
932}
933
935 if (!filter)
936 return;
937
938 filter->prev_input = 0.0f;
939 filter->prev_output = 0.0f;
940}
941
943 if (!filter)
944 return input;
945
946 // First-order high-pass filter
947 // y[n] = alpha * (y[n-1] + x[n] - x[n-1])
948 float output = filter->alpha * (filter->prev_output + input - filter->prev_input);
949
950 filter->prev_input = input;
951 filter->prev_output = output;
952
953 return output;
954}
955
956void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples) {
957 if (!filter || !buffer || num_samples <= 0)
958 return;
959
960 for (int i = 0; i < num_samples; i++) {
961 buffer[i] = highpass_filter_process_sample(filter, buffer[i]);
962 }
963}
964
965/* ============================================================================
966 * Low-Pass Filter Implementation
967 * ============================================================================
968 */
969
970void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate) {
971 if (!filter)
972 return;
973
974 filter->cutoff_hz = cutoff_hz;
975 filter->sample_rate = sample_rate;
976
977 // Calculate filter coefficient using RC time constant formula
978 // alpha = dt / (RC + dt) where RC = 1 / (2 * pi * fc)
979 float dt = 1.0f / sample_rate;
980 float rc = 1.0f / (2.0f * (float)M_PI * cutoff_hz);
981 filter->alpha = dt / (rc + dt);
982
983 lowpass_filter_reset(filter);
984}
985
987 if (!filter)
988 return;
989
990 filter->prev_output = 0.0f;
991}
992
994 if (!filter)
995 return input;
996
997 // First-order IIR low-pass filter: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
998 float output = filter->alpha * input + (1.0f - filter->alpha) * filter->prev_output;
999
1000 filter->prev_output = output;
1001
1002 return output;
1003}
1004
1005void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples) {
1006 if (!filter || !buffer || num_samples <= 0)
1007 return;
1008
1009 for (int i = 0; i < num_samples; i++) {
1010 buffer[i] = lowpass_filter_process_sample(filter, buffer[i]);
1011 }
1012}
1013
1014/* ============================================================================
1015 * Soft Clipping Implementation
1016 * ============================================================================
1017 */
1018
1019float soft_clip(float sample, float threshold, float steepness) {
1020 if (sample > threshold) {
1021 // Soft clip positive values using tanh curve
1022 // Maps samples above threshold asymptotically toward 1.0
1023 return threshold + (1.0f - threshold) * tanhf((sample - threshold) * steepness);
1024 }
1025 if (sample < -threshold) {
1026 // Soft clip negative values symmetrically
1027 return -threshold + (-1.0f + threshold) * tanhf((sample + threshold) * steepness);
1028 }
1029 return sample;
1030}
1031
1032void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness) {
1033 if (!buffer || num_samples <= 0)
1034 return;
1035
1036 for (int i = 0; i < num_samples; i++) {
1037 buffer[i] = soft_clip(buffer[i], threshold, steepness);
1038 }
1039}
1040
1041/* ============================================================================
1042 * Buffer Utility Functions
1043 * ============================================================================
1044 */
1045
1046float smoothstep(float t) {
1047 if (t <= 0.0f)
1048 return 0.0f;
1049 if (t >= 1.0f)
1050 return 1.0f;
1051 return t * t * (3.0f - 2.0f * t);
1052}
1053
1054int16_t float_to_int16(float sample) {
1055 // Clamp to [-1, 1] then scale to int16 range
1056 if (sample > 1.0f)
1057 sample = 1.0f;
1058 if (sample < -1.0f)
1059 sample = -1.0f;
1060 return (int16_t)(sample * 32767.0f);
1061}
1062
1063float int16_to_float(int16_t sample) {
1064 return (float)sample / 32768.0f;
1065}
1066
1067void buffer_float_to_int16(const float *src, int16_t *dst, int count) {
1068 if (!src || !dst || count <= 0)
1069 return;
1070 for (int i = 0; i < count; i++) {
1071 dst[i] = float_to_int16(src[i]);
1072 }
1073}
1074
1075void buffer_int16_to_float(const int16_t *src, float *dst, int count) {
1076 if (!src || !dst || count <= 0)
1077 return;
1078 for (int i = 0; i < count; i++) {
1079 dst[i] = int16_to_float(src[i]);
1080 }
1081}
1082
1083float buffer_peak(const float *buffer, int count) {
1084 if (!buffer || count <= 0)
1085 return 0.0f;
1086
1087 float peak = 0.0f;
1088 for (int i = 0; i < count; i++) {
1089 float abs_sample = fabsf(buffer[i]);
1090 if (abs_sample > peak)
1091 peak = abs_sample;
1092 }
1093 return peak;
1094}
1095
1096void apply_gain_buffer(float *buffer, int count, float gain) {
1097 if (!buffer || count <= 0)
1098 return;
1099
1100 for (int i = 0; i < count; i++) {
1101 buffer[i] *= gain;
1102 }
1103}
1104
1105void fade_buffer(float *buffer, int count, float start_gain, float end_gain) {
1106 if (!buffer || count <= 0)
1107 return;
1108
1109 float step = (end_gain - start_gain) / (float)count;
1110 float gain = start_gain;
1111 for (int i = 0; i < count; i++) {
1112 buffer[i] *= gain;
1113 gain += step;
1114 }
1115}
1116
1117void fade_buffer_smooth(float *buffer, int count, bool fade_in) {
1118 if (!buffer || count <= 0)
1119 return;
1120
1121 for (int i = 0; i < count; i++) {
1122 float t = (float)i / (float)(count - 1);
1123 float gain = smoothstep(fade_in ? t : (1.0f - t));
1124 buffer[i] *= gain;
1125 }
1126}
1127
1128void copy_buffer_with_gain(const float *src, float *dst, int count, float gain) {
1129 if (!src || !dst || count <= 0)
1130 return;
1131
1132 for (int i = 0; i < count; i++) {
1133 dst[i] = src[i] * gain;
1134 }
1135}
⚠️‼️ Error and/or exit() when things go bad.
🔢 Bit Manipulation Utilities
float hysteresis
Hysteresis factor (0-1, prevents gate chatter)
Definition mixer.h:188
float release_coeff
Release coefficient (converted from release_ms)
Definition mixer.h:197
bool noise_gate_is_open(const noise_gate_t *gate)
Check if noise gate is currently open.
Definition mixer.c:911
uint8_t source_id_to_index[256]
Hash table mapping client_id → mixer source index (uses hash function for 32-bit IDs)
Definition mixer.h:343
float base_gain
Base gain before crowd scaling is applied.
Definition mixer.h:353
void copy_buffer_with_gain(const float *src, float *dst, int count, float gain)
Copy buffer with gain scaling.
Definition mixer.c:1128
float leader_margin_dB
Leader margin in dB (sources within this of loudest are leaders)
Definition mixer.h:275
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
Read audio samples from ring buffer.
void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples)
Process a buffer of samples through noise gate.
Definition mixer.c:892
void buffer_float_to_int16(const float *src, int16_t *dst, int count)
Convert float buffer to int16 buffer.
Definition mixer.c:1067
float alpha
Filter coefficient alpha (calculated from cutoff_hz)
Definition mixer.h:225
float release_coeff
Release coefficient (converted from release_ms)
Definition mixer.h:286
float clamp_float(float value, float min, float max)
Clamp a float value to a range.
Definition mixer.c:31
float ratio
Compression ratio (e.g., 4.0 for 4:1 compression)
Definition mixer.h:143
void highpass_filter_reset(highpass_filter_t *filter)
Reset high-pass filter state.
Definition mixer.c:934
float sample_rate
Sample rate in Hz (set during initialization)
Definition mixer.h:222
void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness)
Apply soft clipping to a buffer.
Definition mixer.c:1032
float prev_input
Previous input sample (filter state)
Definition mixer.h:227
float prev_output
Previous output sample (filter state)
Definition mixer.h:250
#define MIXER_MAX_SOURCES
Maximum number of simultaneous audio sources.
Definition mixer.h:104
float highpass_filter_process_sample(highpass_filter_t *filter, float input)
Process a single sample through high-pass filter.
Definition mixer.c:942
float envelope
Current envelope follower state (linear, 0-1)
Definition mixer.h:154
bool * source_active
Array of active flags (true if source is active)
Definition mixer.h:338
float cutoff_hz
Cutoff frequency in Hz (frequencies above this are attenuated)
Definition mixer.h:243
void apply_gain_buffer(float *buffer, int count, float gain)
Apply gain to buffer in-place.
Definition mixer.c:1096
float atten_dB
Attenuation in dB for non-leader sources.
Definition mixer.h:277
rwlock_t source_lock
Reader-writer lock protecting source arrays and bitset.
Definition mixer.h:348
void mixer_set_source_active(mixer_t *mixer, uint32_t client_id, bool active)
Set whether a source is active (receiving audio)
Definition mixer.c:432
float * gain
Per-source ducking gain (linear, calculated from envelope)
Definition mixer.h:290
float attack_coeff
Attack coefficient (converted from attack_ms)
Definition mixer.h:284
void ducking_set_params(ducking_t *duck, float threshold_dB, float leader_margin_dB, float atten_dB, float attack_ms, float release_ms)
Set ducking parameters.
Definition mixer.c:175
float release_ms
Release time in milliseconds (how fast gate closes)
Definition mixer.h:186
void ducking_free(ducking_t *duck)
Free ducking system resources.
Definition mixer.c:166
float threshold_dB
Speaking threshold in dB (sources below this are not "speaking")
Definition mixer.h:273
size_t audio_ring_buffer_available_read(audio_ring_buffer_t *rb)
Get number of samples available for reading.
void ducking_process_frame(ducking_t *duck, float *envelopes, float *gains, int num_sources)
Process a frame of audio through ducking system.
Definition mixer.c:184
void noise_gate_init(noise_gate_t *gate, float sample_rate)
Initialize a noise gate.
Definition mixer.c:838
float crowd_alpha
Crowd scaling exponent (typically 0.5 for sqrt scaling)
Definition mixer.h:351
float buffer_peak(const float *buffer, int count)
Find peak absolute value in buffer.
Definition mixer.c:1083
ducking_t ducking
Ducking system (active speaker detection and attenuation)
Definition mixer.h:356
float cutoff_hz
Cutoff frequency in Hz (frequencies below this are attenuated)
Definition mixer.h:220
int num_sources
Current number of active audio sources.
Definition mixer.h:327
float soft_clip(float sample, float threshold, float steepness)
Apply soft clipping to a sample.
Definition mixer.c:1019
float sample_rate
Sample rate in Hz (set during initialization)
Definition mixer.h:191
float knee_dB
Knee width in dB for soft knee (e.g., 2.0)
Definition mixer.h:141
float attack_ms
Attack time in milliseconds (how fast compression kicks in)
Definition mixer.h:145
void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, float attack_ms, float release_ms, float makeup_dB)
Set compressor parameters.
Definition mixer.c:53
void fade_buffer(float *buffer, int count, float start_gain, float end_gain)
Apply linear fade to buffer in-place.
Definition mixer.c:1105
void compressor_init(compressor_t *comp, float sample_rate)
Initialize a compressor.
Definition mixer.c:42
float alpha
Filter coefficient alpha (calculated from cutoff_hz)
Definition mixer.h:248
int ducking_init(ducking_t *duck, int num_sources, float sample_rate)
Initialize ducking system.
Definition mixer.c:111
#define MIXER_FRAME_SIZE
Number of samples processed per audio frame.
Definition mixer.h:114
void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a high-pass filter.
Definition mixer.c:920
uint32_t * source_ids
Array of client IDs (one per source slot)
Definition mixer.h:336
uint64_t active_sources_mask
Bitset of active sources (bit i = source i is active, O(1) iteration)
Definition mixer.h:341
float int16_to_float(int16_t sample)
Convert int16 sample to float.
Definition mixer.c:1063
float * envelope
Per-source envelope follower state (linear, allocated per source)
Definition mixer.h:288
float threshold_dB
Compression threshold in dB (e.g., -10.0)
Definition mixer.h:139
float release_ms
Release time in milliseconds (how fast compression releases)
Definition mixer.h:147
int max_sources
Maximum number of sources (allocated array sizes)
Definition mixer.h:329
float * mix_buffer
Temporary buffer for mixing operations (pre-allocated)
Definition mixer.h:361
float attack_ms
Attack time in milliseconds (how fast gate opens)
Definition mixer.h:184
float envelope
Current envelope follower state (linear, 0-1)
Definition mixer.h:193
float sample_rate
Sample rate in Hz (set during initialization)
Definition mixer.h:152
bool gate_open
True if gate is currently open (allowing audio through)
Definition mixer.h:199
float smoothstep(float t)
Compute smoothstep interpolation.
Definition mixer.c:1046
void fade_buffer_smooth(float *buffer, int count, bool fade_in)
Apply smoothstep fade to buffer in-place.
Definition mixer.c:1117
float attack_ms
Ducking attack time in milliseconds.
Definition mixer.h:279
int mixer_add_source(mixer_t *mixer, uint32_t client_id, audio_ring_buffer_t *buffer)
Add an audio source to the mixer.
Definition mixer.c:363
int16_t float_to_int16(float sample)
Convert float sample to int16 (WebRTC format)
Definition mixer.c:1054
compressor_t compressor
Compressor (dynamic range compression)
Definition mixer.h:358
float sample_rate
Sample rate in Hz (set during initialization)
Definition mixer.h:245
void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a low-pass filter.
Definition mixer.c:970
mixer_t * mixer_create(int max_sources, int sample_rate)
Create a new audio mixer.
Definition mixer.c:218
int mixer_process_excluding_source(mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id)
Process audio from all sources except one (for per-client output)
Definition mixer.c:604
float db_to_linear(float db)
Convert decibels to linear gain.
Definition mixer.c:23
void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through low-pass filter.
Definition mixer.c:1005
void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through high-pass filter.
Definition mixer.c:956
float release_ms
Ducking release time in milliseconds.
Definition mixer.h:281
audio_ring_buffer_t ** source_buffers
Array of pointers to client audio ring buffers.
Definition mixer.h:334
void noise_gate_set_params(noise_gate_t *gate, float threshold, float attack_ms, float release_ms, float hysteresis)
Set noise gate parameters.
Definition mixer.c:852
float makeup_dB
Makeup gain in dB (compensates for gain reduction)
Definition mixer.h:149
float compressor_process_sample(compressor_t *comp, float sidechain)
Process a single sample through compressor.
Definition mixer.c:87
void mixer_destroy(mixer_t *mixer)
Destroy a mixer and free all resources.
Definition mixer.c:346
int sample_rate
Sample rate in Hz (e.g., 44100)
Definition mixer.h:331
float lowpass_filter_process_sample(lowpass_filter_t *filter, float input)
Process a single sample through low-pass filter.
Definition mixer.c:993
float linear_to_db(float linear)
Convert linear gain to decibels.
Definition mixer.c:27
void lowpass_filter_reset(lowpass_filter_t *filter)
Reset low-pass filter state.
Definition mixer.c:986
float attack_coeff
Attack coefficient (converted from attack_ms)
Definition mixer.h:158
int mixer_process(mixer_t *mixer, float *output, int num_samples)
Process audio from all active sources.
Definition mixer.c:459
void mixer_remove_source(mixer_t *mixer, uint32_t client_id)
Remove an audio source from the mixer.
Definition mixer.c:400
void buffer_int16_to_float(const int16_t *src, float *dst, int count)
Convert int16 buffer to float buffer.
Definition mixer.c:1075
float threshold
Gate threshold in linear units (e.g., 0.01f for -40dB)
Definition mixer.h:182
float prev_output
Previous output sample (filter state)
Definition mixer.h:229
float release_coeff
Release coefficient (converted from release_ms)
Definition mixer.h:160
float gain_lin
Current gain multiplier (linear, calculated from envelope)
Definition mixer.h:156
float attack_coeff
Attack coefficient (converted from attack_ms)
Definition mixer.h:195
float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude)
Process a single sample through noise gate.
Definition mixer.c:867
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:389
#define SAFE_MALLOC(size, cast)
Definition common.h:208
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
Definition error_codes.h:98
@ ERROR_THREAD
Definition error_codes.h:95
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
Definition log_rates.h:32
#define log_warn(...)
Log a WARN message.
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
Format nanoseconds as human-readable duration string.
Definition time.c:187
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
Definition time.h:141
#define STOP_TIMER(name_fmt,...)
Stop a timer with formatted name and return elapsed time.
Definition time.h:165
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:249
int rwlock_destroy(rwlock_t *lock)
Destroy a read-write lock.
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:194
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:213
int rwlock_init(rwlock_t *lock)
Initialize a read-write lock.
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:231
#define AUDIO_RING_BUFFER_SIZE
Audio ring buffer size in samples (192000 samples = 4 seconds @ 48kHz)
Definition ringbuffer.h:135
🔊 Audio Capture and Playback Interface for ascii-chat
🔢 Mathematical Utility Functions
#define M_PI
Definition mixer.c:19
Multi-Source Audio Mixing and Processing System.
✅ Safe Integer Arithmetic and Overflow Detection
Audio ring buffer for real-time audio streaming.
Definition ringbuffer.h:208
atomic_uint read_index
Read index (consumer position) - LOCK-FREE with atomic operations.
Definition ringbuffer.h:214
Dynamic range compressor settings and state.
Definition mixer.h:137
Ducking system settings and state.
Definition mixer.h:271
High-pass filter settings and state.
Definition mixer.h:218
Low-pass filter state.
Definition mixer.h:241
Main mixer structure for multi-source audio processing.
Definition mixer.h:325
Noise gate settings and state.
Definition mixer.h:180
⏱️ High-precision timing utilities using sokol_time.h and uthash