ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
mixer.c
Go to the documentation of this file.
1
9#include <ascii-chat/common.h>
10#include <ascii-chat/asciichat_errno.h> // For asciichat_errno system
12#include <ascii-chat/util/time.h> // For timing instrumentation
13#include <ascii-chat/util/bits.h> // For find_first_set_bit
14#include <ascii-chat/util/overflow.h> // For overflow checking
15#include <math.h>
16#include <string.h>
17#include <stdint.h>
20
21#define MIXER_LISTENER_SAMPLES 8192
27
28// Called with the source write lock. Drain each input once and fan it out to
29// independent listener queues so one recipient cannot consume another's audio.
30static void mixer_distribute_samples(mixer_t *mixer) {
31 float samples[960];
32 for (int source = 0; source < mixer->max_sources; source++) {
33 if (!mixer->source_ids[source] || !mixer->source_buffers[source])
34 continue;
35 while (audio_ring_buffer_available_read(mixer->source_buffers[source]) > 0) {
36 size_t count = audio_ring_buffer_read(mixer->source_buffers[source], samples, 960);
37 if (!count)
38 break;
39 for (int listener = 0; listener < mixer->max_sources; listener++) {
40 if (listener == source || !mixer->source_ids[listener])
41 continue;
42 mixer_listener_buffer_t *queue = mixer->listener_buffers[listener][source];
43 if (!queue) {
44 queue = SAFE_CALLOC(1, sizeof(*queue), mixer_listener_buffer_t *);
45 if (!queue)
46 continue;
47 mixer->listener_buffers[listener][source] = queue;
48 }
49 if (queue->count + count > MIXER_LISTENER_SAMPLES) {
50 size_t discard = queue->count + count - MIXER_LISTENER_SAMPLES;
51 queue->read = (queue->read + discard) % MIXER_LISTENER_SAMPLES;
52 queue->count -= discard;
53 }
54 for (size_t i = 0; i < count; i++)
55 queue->samples[(queue->read + queue->count + i) % MIXER_LISTENER_SAMPLES] = samples[i];
56 queue->count += count;
57 }
58 }
59 }
60}
61
62#ifndef M_PI
63#define M_PI 3.14159265358979323846
64#endif
65
66// Utility functions
67float db_to_linear(float db) {
68 return powf(10.0f, db / 20.0f);
69}
70
71float linear_to_db(float linear) {
72 return 20.0f * log10f(fmaxf(linear, 1e-12f));
73}
74
75float clamp_float(float value, float min, float max) {
76 if (value < min) {
77 return min;
78 }
79 if (value > max) {
80 return max;
81 }
82 return value;
83}
84
85// Compressor implementation
86void compressor_init(compressor_t *comp, float sample_rate) {
87 comp->sample_rate = sample_rate;
88 comp->envelope = 0.0f;
89 comp->gain_lin = 1.0f;
90
91 // Set default parameters with 0dB makeup gain (unity)
92 // Soft clip at 0.7 provides 3dB headroom to prevent hard clipping
93 // Ducking and crowd scaling naturally reduce volume, no compensation needed
94 compressor_set_params(comp, -10.0f, 4.0f, 10 * NS_PER_MS_INT, 100 * NS_PER_MS_INT, 0.0f);
95}
96
97void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, uint64_t attack_ns, uint64_t release_ns,
98 float makeup_dB) {
99 comp->threshold_dB = threshold_dB;
100 comp->ratio = ratio;
101 comp->attack_ns = attack_ns;
102 comp->release_ns = release_ns;
103 comp->makeup_dB = makeup_dB;
104 comp->knee_dB = 2.0f; // Fixed soft knee
105
106 // Calculate time constants
107 float attack_tau = (float)attack_ns / NS_PER_SEC;
108 float release_tau = (float)release_ns / NS_PER_SEC;
109 comp->attack_coeff = expf(-1.0f / (attack_tau * comp->sample_rate + 1e-12f));
110 comp->release_coeff = expf(-1.0f / (release_tau * comp->sample_rate + 1e-12f));
111}
112
113static float compressor_gain_reduction_db(const compressor_t *comp, float level_dB) {
114 float over = level_dB - comp->threshold_dB;
115 float knee = comp->knee_dB;
116
117 if (knee > 0.0f) {
118 if (over <= -knee * 0.5f)
119 return 0.0f;
120 if (over >= knee * 0.5f)
121 return (1.0f / comp->ratio - 1.0f) * over;
122 float x = over + knee * 0.5f;
123 return (1.0f / comp->ratio - 1.0f) * (x * x) / (2.0f * knee);
124 }
125 if (over <= 0.0f) {
126 return 0.0f;
127 }
128 return (1.0f / comp->ratio - 1.0f) * over;
129}
130
131float compressor_process_sample(compressor_t *comp, float sidechain) {
132 float x = fabsf(sidechain);
133
134 // Update envelope with attack/release
135 if (x > comp->envelope)
136 comp->envelope = comp->attack_coeff * comp->envelope + (1.0f - comp->attack_coeff) * x;
137 else
138 comp->envelope = comp->release_coeff * comp->envelope + (1.0f - comp->release_coeff) * x;
139
140 // Calculate gain reduction
141 float level_dB = linear_to_db(comp->envelope);
142 float gr_dB = compressor_gain_reduction_db(comp, level_dB);
143 float target_lin = db_to_linear(gr_dB + comp->makeup_dB);
144
145 // Smooth gain changes
146 if (target_lin < comp->gain_lin)
147 comp->gain_lin = comp->attack_coeff * comp->gain_lin + (1.0f - comp->attack_coeff) * target_lin;
148 else
149 comp->gain_lin = comp->release_coeff * comp->gain_lin + (1.0f - comp->release_coeff) * target_lin;
150
151 return comp->gain_lin;
152}
153
154// Ducking implementation
155int ducking_init(ducking_t *duck, int num_sources, float sample_rate) {
156 if (!duck) {
157 return SET_ERRNO(ERROR_INVALID_PARAM, "ducking_init: duck is NULL");
158 }
159
160 // Set default parameters
161 // threshold_dB: sources below this aren't considered "speaking"
162 // leader_margin_dB: sources within this margin of loudest are all "leaders"
163 // atten_dB: how much to attenuate non-leaders (was -12dB, too aggressive)
164 duck->threshold_dB = -45.0f; // More lenient threshold
165 duck->leader_margin_dB = 6.0f; // Wider margin = more sources treated as leaders
166 duck->atten_dB = -6.0f; // Only -6dB attenuation (was -12dB)
167 duck->attack_ns = 10 * NS_PER_MS_INT; // Slower attack (10ms)
168 duck->release_ns = 200 * NS_PER_MS_INT; // Slower release (200ms)
169 duck->envelope = NULL;
170 duck->gain = NULL;
171
172 // Calculate time constants
173 float attack_tau = (float)duck->attack_ns / NS_PER_SEC;
174 float release_tau = (float)duck->release_ns / NS_PER_SEC;
175 duck->attack_coeff = expf(-1.0f / (attack_tau * sample_rate + 1e-12f));
176 duck->release_coeff = expf(-1.0f / (release_tau * sample_rate + 1e-12f));
177
178 // Allocate arrays with overflow checking
179 size_t envelope_size = 0;
180 if (checked_size_mul((size_t)num_sources, sizeof(float), &envelope_size) != ASCIICHAT_OK) {
181 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Ducking envelope array size overflow: %d sources", num_sources);
182 }
183 duck->envelope = SAFE_MALLOC(envelope_size, float *);
184 if (!duck->envelope) {
185 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ducking envelope array");
186 }
187
188 size_t gain_size = 0;
189 if (checked_size_mul((size_t)num_sources, sizeof(float), &gain_size) != ASCIICHAT_OK) {
190 SAFE_FREE(duck->envelope);
191 duck->envelope = NULL;
192 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Ducking gain array size overflow: %d sources", num_sources);
193 }
194 duck->gain = SAFE_MALLOC(gain_size, float *);
195 if (!duck->gain) {
196 SAFE_FREE(duck->envelope);
197 duck->envelope = NULL;
198 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ducking gain array");
199 }
200
201 // Initialize
202 SAFE_MEMSET(duck->envelope, (size_t)num_sources * sizeof(float), 0, (size_t)num_sources * sizeof(float));
203 for (int i = 0; i < num_sources; i++) {
204 duck->gain[i] = 1.0f;
205 }
206
207 return ASCIICHAT_OK;
208}
209
211 if (duck->envelope) {
212 SAFE_FREE(duck->envelope);
213 }
214 if (duck->gain) {
215 SAFE_FREE(duck->gain);
216 }
217}
218
219void ducking_set_params(ducking_t *duck, float threshold_dB, float leader_margin_dB, float atten_dB, uint64_t attack_ns,
220 uint64_t release_ns) {
221 duck->threshold_dB = threshold_dB;
222 duck->leader_margin_dB = leader_margin_dB;
223 duck->atten_dB = atten_dB;
224 duck->attack_ns = attack_ns;
225 duck->release_ns = release_ns;
226}
227
228void ducking_process_frame(ducking_t *duck, float *envelopes, float *gains, int num_sources) {
229 // Find the loudest source
230 float max_dB = -120.0f;
231 float env_dB[MIXER_MAX_SOURCES];
232
233 for (int i = 0; i < num_sources; i++) {
234 env_dB[i] = linear_to_db(envelopes[i]);
235 if (env_dB[i] > max_dB)
236 max_dB = env_dB[i];
237 }
238
239 // Calculate ducking gain for each source
240 float leader_cut = db_to_linear(duck->atten_dB);
241
242 for (int i = 0; i < num_sources; i++) {
243 bool is_speaking = env_dB[i] > duck->threshold_dB;
244 bool is_leader = is_speaking && (env_dB[i] >= max_dB - duck->leader_margin_dB);
245
246 float target;
247 if (is_speaking && !is_leader) {
248 target = leader_cut;
249 } else {
250 target = 1.0f;
251 }
252
253 // Smooth gain transitions
254 if (target < gains[i])
255 gains[i] = duck->attack_coeff * gains[i] + (1.0f - duck->attack_coeff) * target;
256 else
257 gains[i] = duck->release_coeff * gains[i] + (1.0f - duck->release_coeff) * target;
258 }
259}
260
261// Mixer implementation
262mixer_t *mixer_create(int max_sources, int sample_rate) {
263 // Validate parameters
264 if (max_sources <= 0 || max_sources > MIXER_MAX_SOURCES) {
265 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid max_sources: %d (must be 1-%d)", max_sources, MIXER_MAX_SOURCES);
266 return NULL;
267 }
268
269 if (sample_rate <= 0 || sample_rate > 192000) {
270 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid sample_rate: %d (must be 1-192000)", sample_rate);
271 return NULL;
272 }
273
274 mixer_t *mixer;
275 mixer = SAFE_MALLOC(sizeof(mixer_t), mixer_t *);
276 if (!mixer) {
277 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mixer structure");
278 return NULL;
279 }
280
281 mixer->num_sources = 0;
282 mixer->max_sources = max_sources;
283 mixer->sample_rate = sample_rate;
284 memset(mixer->listener_buffers, 0, sizeof(mixer->listener_buffers));
285
286 // Allocate source management arrays with overflow checking
287 size_t buffers_size = 0;
288 if (checked_size_mul((size_t)max_sources, sizeof(audio_ring_buffer_t *), &buffers_size) != ASCIICHAT_OK) {
289 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer source buffers array overflow: %d sources", max_sources);
290 SAFE_FREE(mixer);
291 return NULL;
292 }
293 mixer->source_buffers = SAFE_MALLOC(buffers_size, audio_ring_buffer_t **);
294 if (!mixer->source_buffers) {
295 SAFE_FREE(mixer);
296 return NULL;
297 }
298
299 size_t ids_size = 0;
300 if (checked_size_mul((size_t)max_sources, sizeof(const char *), &ids_size) != ASCIICHAT_OK) {
301 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer source IDs array overflow: %d sources", max_sources);
303 SAFE_FREE(mixer);
304 return NULL;
305 }
306 mixer->source_ids = SAFE_MALLOC(ids_size, const char **);
307 if (!mixer->source_ids) {
309 SAFE_FREE(mixer);
310 return NULL;
311 }
312
313 size_t active_size = 0;
314 if (checked_size_mul((size_t)max_sources, sizeof(bool), &active_size) != ASCIICHAT_OK) {
315 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer source active array overflow: %d sources", max_sources);
317 SAFE_FREE(mixer->source_ids);
318 SAFE_FREE(mixer);
319 return NULL;
320 }
321 mixer->source_active = SAFE_MALLOC(active_size, bool *);
322 if (!mixer->source_active) {
324 SAFE_FREE(mixer->source_ids);
325 SAFE_FREE(mixer);
326 return NULL;
327 }
328
329 // Initialize arrays
330 SAFE_MEMSET((void *)mixer->source_buffers, buffers_size, 0, buffers_size);
331 SAFE_MEMSET(mixer->source_ids, ids_size, 0, ids_size);
332 SAFE_MEMSET(mixer->source_active, active_size, 0, active_size);
333
334 // OPTIMIZATION 1: Initialize bitset optimization structures
335 mixer->active_sources_mask = 0ULL; // No sources active initially
336 for (int i = 0; i < 256; i++) {
338 }
339
340 // Allocate mix buffer BEFORE rwlock_init so cleanup path is correct
341 size_t mix_buffer_size = 0;
342 if (checked_size_mul(MIXER_FRAME_SIZE, sizeof(float), &mix_buffer_size) != ASCIICHAT_OK) {
343 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer mix buffer size overflow: %zu samples", (size_t)MIXER_FRAME_SIZE);
345 SAFE_FREE(mixer->source_ids);
346 SAFE_FREE(mixer->source_active);
347 SAFE_FREE(mixer);
348 return NULL;
349 }
350 mixer->mix_buffer = SAFE_MALLOC(mix_buffer_size, float *);
351 if (!mixer->mix_buffer) {
352 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mix buffer");
354 SAFE_FREE(mixer->source_ids);
355 SAFE_FREE(mixer->source_active);
356 SAFE_FREE(mixer);
357 return NULL;
358 }
359
360 // OPTIMIZATION 2: Initialize reader-writer lock
361 if (rwlock_init(&mixer->source_lock, "audio_mixer_sources") != 0) {
362 SET_ERRNO(ERROR_THREAD, "Failed to initialize mixer source lock");
364 SAFE_FREE(mixer->source_ids);
365 SAFE_FREE(mixer->source_active);
366 SAFE_FREE(mixer->mix_buffer);
367 SAFE_FREE(mixer);
368 return NULL;
369 }
370
371 // Set crowd scaling parameters
372 mixer->crowd_alpha = 0.5f; // Square root scaling
373 mixer->base_gain = 1.0f; // Unity gain - let compressor handle loudness, avoid clipping
374
375 // Initialize processing
376 if (ducking_init(&mixer->ducking, max_sources, (float)sample_rate) != ASCIICHAT_OK) {
379 SAFE_FREE(mixer->source_ids);
380 SAFE_FREE(mixer->source_active);
381 SAFE_FREE(mixer->mix_buffer);
382 SAFE_FREE(mixer);
383 return NULL;
384 }
385 compressor_init(&mixer->compressor, (float)sample_rate);
386
387 log_debug("Audio mixer created: max_sources=%d, sample_rate=%d", max_sources, sample_rate);
388
389 // Register mixer and its sync primitives for debugging
390 NAMED_REGISTER_MIXER(mixer, "audio_mixer", NULL);
391 NAMED_REGISTER_RWLOCK(&mixer->source_lock, "source_lock", (uintptr_t)(const void *)(mixer));
392
393 return mixer;
394}
395
396void mixer_destroy(mixer_t *mixer) {
397 if (!mixer)
398 return;
399
400 NAMED_UNREGISTER(mixer);
401
402 // OPTIMIZATION 2: Destroy reader-writer lock
404
405 ducking_destroy(&mixer->ducking);
406
407 // Free all allocated client_id strings before freeing the array itself
408 for (int i = 0; i < mixer->max_sources; i++) {
409 for (int j = 0; j < mixer->max_sources; j++)
410 SAFE_FREE(mixer->listener_buffers[i][j]);
411 if (mixer->source_ids[i] != NULL) {
412 SAFE_FREE(mixer->source_ids[i]);
413 }
414 }
415
417 SAFE_FREE(mixer->source_ids);
418 SAFE_FREE(mixer->source_active);
419 SAFE_FREE(mixer->mix_buffer);
420 SAFE_FREE(mixer);
421 log_debug("Audio mixer destroyed");
422}
423
424int mixer_add_source(mixer_t *mixer, const char *client_id, audio_ring_buffer_t *buffer) {
425 if (!mixer || !buffer)
426 return -1;
427
428 // OPTIMIZATION 2: Acquire write lock for source modification
429 rwlock_wrlock(&mixer->source_lock);
430
431 // Find an empty slot
432 int slot = -1;
433 for (int i = 0; i < mixer->max_sources; i++) {
434 if (mixer->source_ids[i] == NULL) {
435 slot = i;
436 break;
437 }
438 }
439
440 if (slot == -1) {
442 log_warn("Mixer: No available slots for client %s", client_id);
443 return -1;
444 }
445
446 mixer->source_buffers[slot] = buffer;
447 // Duplicate the client_id string - we own this memory now
448 size_t client_id_len = strlen(client_id) + 1;
449 mixer->source_ids[slot] = SAFE_MALLOC(client_id_len, char *);
450 if (!mixer->source_ids[slot]) {
451 log_warn("Mixer: Failed to allocate memory for client_id %s", client_id);
452 mixer->source_buffers[slot] = NULL;
454 return -1;
455 }
456 asciichat_error_t strcpy_result = SAFE_STRCPY((char *)mixer->source_ids[slot], client_id_len, client_id);
457 if (strcpy_result != ASCIICHAT_OK) {
458 log_error("Mixer: Failed to copy client_id: %s", asciichat_error_string(strcpy_result));
459 SAFE_FREE(mixer->source_ids[slot]);
460 mixer->source_buffers[slot] = NULL;
462 return -1;
463 }
464 mixer->source_active[slot] = true;
465 mixer->num_sources++;
466
467 // OPTIMIZATION 1: Update bitset optimization structures
468 mixer->active_sources_mask |= (1ULL << slot); // Set bit for this slot
469 // Hash table optimization disabled for string-based client_ids (linear search is acceptable for small mixer sizes)
470
472
473 log_info("Mixer: Added source for client %s at slot %d", client_id, slot);
474 return slot;
475}
476
477void mixer_remove_source(mixer_t *mixer, const char *client_id) {
478 if (!mixer)
479 return;
480
481 // OPTIMIZATION 2: Acquire write lock for source modification
482 rwlock_wrlock(&mixer->source_lock);
483
484 for (int i = 0; i < mixer->max_sources; i++) {
485 if (mixer->source_ids[i] != NULL && strcmp(mixer->source_ids[i], client_id) == 0) {
486 for (int j = 0; j < mixer->max_sources; j++) {
487 SAFE_FREE(mixer->listener_buffers[i][j]);
488 if (j != i)
489 SAFE_FREE(mixer->listener_buffers[j][i]);
490 }
491 mixer->source_buffers[i] = NULL;
492 // Free the allocated client_id string
493 SAFE_FREE(mixer->source_ids[i]);
494 mixer->source_active[i] = false;
495 mixer->num_sources--;
496
497 // OPTIMIZATION 1: Update bitset optimization structures
498 mixer->active_sources_mask &= ~(1ULL << i); // Clear bit for this slot
499 // Hash table optimization disabled for string-based client_ids
500
501 // Reset ducking state for this source
502 mixer->ducking.envelope[i] = 0.0f;
503 mixer->ducking.gain[i] = 1.0f;
504
506
507 log_info("Mixer: Removed source for client %s from slot %d", client_id, i);
508 return;
509 }
510 }
511
513}
514
515void mixer_set_source_active(mixer_t *mixer, const char *client_id, bool active) {
516 if (!mixer)
517 return;
518
519 // OPTIMIZATION 2: Acquire write lock for source modification
520 rwlock_wrlock(&mixer->source_lock);
521
522 for (int i = 0; i < mixer->max_sources; i++) {
523 if (mixer->source_ids[i] != NULL && strcmp(mixer->source_ids[i], client_id) == 0) {
524 mixer->source_active[i] = active;
525
526 // OPTIMIZATION 1: Update bitset for active state change
527 if (active) {
528 mixer->active_sources_mask |= (1ULL << i); // Set bit
529 } else {
530 mixer->active_sources_mask &= ~(1ULL << i); // Clear bit
531 }
532
534 log_debug("Mixer: Set source %s active=%d", client_id, active);
535 return;
536 }
537 }
538
540}
541
542int mixer_process(mixer_t *mixer, float *output, int num_samples) {
543 if (!mixer || !output || num_samples <= 0)
544 return -1;
545
546 // THREAD SAFETY: Acquire read lock to protect against concurrent source add/remove
547 // This prevents race conditions where source_buffers[i] could be set to NULL while we read it
548 rwlock_rdlock(&mixer->source_lock);
549
550 // Clear output buffer
551 SAFE_MEMSET(output, num_samples * sizeof(float), 0, num_samples * sizeof(float));
552
553 // Count active sources
554 int active_count = 0;
555 for (int i = 0; i < mixer->max_sources; i++) {
556 if (mixer->source_ids[i] != 0 && mixer->source_active[i] && mixer->source_buffers[i]) {
557 active_count++;
558 }
559 }
560
561 if (active_count == 0) {
562 // No active sources, output silence
563 return 0;
564 }
565
566 // Process in frames for efficiency
567 for (int frame_start = 0; frame_start < num_samples; frame_start += MIXER_FRAME_SIZE) {
568 int frame_size = (frame_start + MIXER_FRAME_SIZE > num_samples) ? (num_samples - frame_start) : MIXER_FRAME_SIZE;
569
570 // Clear mix buffer
571 SAFE_MEMSET(mixer->mix_buffer, frame_size * sizeof(float), 0, frame_size * sizeof(float));
572
573 // Temporary buffers for source audio
574 float source_samples[MIXER_MAX_SOURCES][MIXER_FRAME_SIZE];
575 int source_count = 0;
576 int source_map[MIXER_MAX_SOURCES]; // Maps source index to slot
577
578 // Read from each active source
579 for (int i = 0; i < mixer->max_sources; i++) {
580 if (mixer->source_ids[i] != 0 && mixer->source_active[i] && mixer->source_buffers[i]) {
581 // Read samples from this source's ring buffer
582 size_t samples_read_size =
583 audio_ring_buffer_read(mixer->source_buffers[i], source_samples[source_count], frame_size);
584 int samples_read = (int)samples_read_size;
585
586 // Accept partial frames - pad with silence if needed
587 // This prevents audio dropouts when ring buffers are temporarily under-filled
588 if (samples_read > 0) {
589 // Pad remaining samples with silence if we got a partial frame
590 if (samples_read < frame_size) {
591 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) * sizeof(float), 0,
592 (frame_size - samples_read) * sizeof(float));
593 }
594
595 source_map[source_count] = i;
596 source_count++;
597
598 if (source_count >= MIXER_MAX_SOURCES)
599 break;
600 }
601 }
602 }
603
604 // OPTIMIZATION: Batch envelope calculation per-frame instead of per-sample
605 // Calculate peak amplitude for each source over the entire frame
606 int speaking_count = 0;
607
608 for (int i = 0; i < source_count; i++) {
609 int slot = source_map[i];
610 float peak = 0.0f;
611
612 // Find peak amplitude in frame (much faster than per-sample envelope)
613 for (int s = 0; s < frame_size; s++) {
614 float abs_sample = fabsf(source_samples[i][s]);
615 if (abs_sample > peak)
616 peak = abs_sample;
617 }
618
619 // Update envelope using frame peak (one update per frame instead of per-sample)
620 if (peak > mixer->ducking.envelope[slot]) {
621 mixer->ducking.envelope[slot] =
622 mixer->ducking.attack_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.attack_coeff) * peak;
623 } else {
624 mixer->ducking.envelope[slot] =
625 mixer->ducking.release_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.release_coeff) * peak;
626 }
627
628 // Count speaking sources
629 if (mixer->ducking.envelope[slot] > db_to_linear(-60.0f))
630 speaking_count++;
631 }
632
633 // Apply ducking ONCE per frame (not per-sample)
634 ducking_process_frame(&mixer->ducking, mixer->ducking.envelope, mixer->ducking.gain, mixer->max_sources);
635
636 // Calculate crowd scaling ONCE per frame
637 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((float)speaking_count, mixer->crowd_alpha)) : 1.0f;
638 float pre_bus = mixer->base_gain * crowd_gain;
639
640 // Pre-calculate combined gains for each source (ducking * pre_bus)
641 float combined_gains[MIXER_MAX_SOURCES];
642 for (int i = 0; i < source_count; i++) {
643 int slot = source_map[i];
644 combined_gains[i] = mixer->ducking.gain[slot] * pre_bus;
645 }
646
647 // OPTIMIZATION: Fast mixing loop - simple multiply-add with pre-calculated gains
648 // NO per-sample compressor to avoid expensive log/pow calls (480x per iteration)
649 for (int s = 0; s < frame_size; s++) {
650 float mix = 0.0f;
651 for (int i = 0; i < source_count; i++) {
652 mix += source_samples[i][s] * combined_gains[i];
653 }
654
655 // Store in mix buffer for frame-level compression below
656 mixer->mix_buffer[s] = mix;
657 }
658
659 // OPTIMIZATION: Apply compression ONCE per frame instead of per-sample
660 // This reduces expensive log10f/powf calls from 480x to 1x per iteration
661 // Calculate frame peak for compressor sidechain
662 float frame_peak = 0.0f;
663 for (int s = 0; s < frame_size; s++) {
664 float abs_val = fabsf(mixer->mix_buffer[s]);
665 if (abs_val > frame_peak)
666 frame_peak = abs_val;
667 }
668
669 // Process compressor with frame peak (1 call instead of 480)
670 float comp_gain = compressor_process_sample(&mixer->compressor, frame_peak);
671
672 // Apply compression gain and soft clipping to all samples
673 for (int s = 0; s < frame_size; s++) {
674 float mix = mixer->mix_buffer[s] * comp_gain;
675
676 // Soft clip at 0.7 to provide 3dB headroom and prevent hard clipping
677 // With +6dB makeup gain, peaks can exceed 1.0, so we need headroom
678 // threshold=0.7 maps asymptotically toward 1.0, preventing harsh distortion
679 output[frame_start + s] = soft_clip(mix, 0.7f, 3.0f);
680 }
681 }
682
684 return num_samples;
685}
686
687int mixer_process_excluding_source(mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id) {
688 if (!mixer || !output || num_samples <= 0) {
689 return -1;
690 }
691
692 // Only use timing in debug builds - snprintf + hashtable ops are expensive in hot path
693#ifndef NDEBUG
694 START_TIMER("mixer_total");
695#endif
696
697 // Serialize sample distribution and mutable ducking/compressor state.
698 rwlock_wrlock(&mixer->source_lock);
699
700 // Clear output buffer
701 SAFE_MEMSET(output, num_samples * sizeof(float), 0, num_samples * sizeof(float));
702
703 // Source IDs are strings; match the complete ID hash used by the caller.
704 uint8_t exclude_index = MIXER_HASH_INVALID;
705 for (int i = 0; i < mixer->max_sources; i++) {
706 if (mixer->source_ids[i] && fnv1a_hash_string(mixer->source_ids[i]) == exclude_client_id) {
707 exclude_index = (uint8_t)i;
708 break;
709 }
710 }
711 if (exclude_index != MIXER_HASH_INVALID)
712 mixer_distribute_samples(mixer);
713 uint64_t active_mask = mixer->active_sources_mask;
714
715 // Validate exclude_index before using in bitshift
716 // - exclude_index == MIXER_HASH_INVALID means "not found" (sentinel value from initialization)
717 // - exclude_index >= MIXER_MAX_SOURCES would cause undefined behavior in bitshift
718 bool valid_exclude = (exclude_index < MIXER_MAX_SOURCES && exclude_index != MIXER_HASH_INVALID);
719
720 if (valid_exclude) {
721 // Apply exclusion to prevent echo feedback
722 uint64_t mask_without_excluded = active_mask & ~(1ULL << exclude_index);
723 active_mask = mask_without_excluded;
724
725 // DIAGNOSTIC: Log which client was excluded and which remain active
727 4500 * US_PER_MS_INT,
728 "MIXER EXCLUSION: exclude_client=%u, exclude_index=%u, active_mask_before=0x%llx, active_mask_after=0x%llx",
729 exclude_client_id, exclude_index, (unsigned long long)(active_mask | (1ULL << exclude_index)),
730 (unsigned long long)active_mask);
731 } else {
732 // DIAGNOSTIC: Failed to exclude - log why
734 "MIXER EXCLUSION FAILED: exclude_client=%u, exclude_index=%u (valid=%d), lookup_id=%u",
735 exclude_client_id, exclude_index, valid_exclude,
736 (exclude_index < MIXER_MAX_SOURCES && exclude_index != 0xFF) ? mixer->source_ids[exclude_index] : 0);
737 }
738
739 // Fast check: any sources to mix?
740 if (active_mask == 0) {
742#ifndef NDEBUG
743 STOP_TIMER("mixer_total");
744#endif
745 return 0; // No sources to mix (excluding the specified client), output silence
746 }
747
748 // Process in frames for efficiency
749 for (int frame_start = 0; frame_start < num_samples; frame_start += MIXER_FRAME_SIZE) {
750 int frame_size = (frame_start + MIXER_FRAME_SIZE > num_samples) ? (num_samples - frame_start) : MIXER_FRAME_SIZE;
751
752 uint64_t read_start_ns = time_get_ns();
753
754 // Clear mix buffer
755 SAFE_MEMSET(mixer->mix_buffer, frame_size * sizeof(float), 0, frame_size * sizeof(float));
756
757 // Temporary buffers for source audio
758 float source_samples[MIXER_MAX_SOURCES][MIXER_FRAME_SIZE];
759 int source_count = 0;
760 int source_map[MIXER_MAX_SOURCES]; // Maps source index to slot
761
762 // OPTIMIZATION 1: Iterate only over active sources using bitset
763 uint64_t current_mask = active_mask;
764 while (current_mask && source_count < MIXER_MAX_SOURCES) {
765 int i = find_first_set_bit(current_mask); // Portable: find first set bit
766 current_mask &= current_mask - 1; // Clear lowest set bit
767
768 // Verify source is valid (defensive programming)
769 if (i < mixer->max_sources && mixer->source_ids[i] != 0 && mixer->source_buffers[i]) {
770 // Read samples from this source's ring buffer
771 size_t samples_read_size = 0;
772 if (valid_exclude) {
773 mixer_listener_buffer_t *queue = mixer->listener_buffers[exclude_index][i];
774 if (queue && queue->count >= (size_t)frame_size) {
775 samples_read_size = (size_t)frame_size;
776 for (int s = 0; s < frame_size; s++)
777 source_samples[source_count][s] = queue->samples[(queue->read + s) % MIXER_LISTENER_SAMPLES];
778 queue->read = (queue->read + samples_read_size) % MIXER_LISTENER_SAMPLES;
779 queue->count -= samples_read_size;
780 }
781 } else {
782 samples_read_size =
783 audio_ring_buffer_read(mixer->source_buffers[i], source_samples[source_count], frame_size);
784 }
785 int samples_read = (int)samples_read_size;
786
787 // Accept partial frames - pad with silence if needed
788 // This prevents audio dropouts when ring buffers are temporarily under-filled
789 if (samples_read > 0) {
790 // Pad remaining samples with silence if we got a partial frame
791 if (samples_read < frame_size) {
792 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) * sizeof(float), 0,
793 (frame_size - samples_read) * sizeof(float));
794 }
795
796 // DIAGNOSTIC: Calculate RMS of this source's audio
797 float source_rms = 0.0f;
798 if (frame_start == 0) { // Only log for first frame to reduce spam
799 float sum_squares = 0.0f;
800 for (int s = 0; s < samples_read; s++) {
801 sum_squares += source_samples[source_count][s] * source_samples[source_count][s];
802 }
803 source_rms = sqrtf(sum_squares / (float)samples_read);
804 log_dev_every(NS_PER_MS_INT, "MIXER SOURCE READ: client_id=%u, slot=%d, samples_read=%d, RMS=%.6f",
805 mixer->source_ids[i], i, samples_read, source_rms);
806 }
807
808 source_map[source_count] = i;
809 source_count++;
810 }
811 }
812 }
813
814 uint64_t read_end_ns = time_get_ns();
815 uint64_t read_time_ns = time_elapsed_ns(read_start_ns, read_end_ns);
816
817 if (read_time_ns > 10 * NS_PER_MS_INT) {
818 char read_time_str[32];
819 time_pretty(read_time_ns, -1, read_time_str, sizeof(read_time_str));
820 log_warn_every(LOG_RATE_DEFAULT, "Mixer: Slow source reading took %s for %d sources", read_time_str,
821 source_count);
822 }
823
824 if (GET_OPTION(no_audio_mixer)) {
825 for (int sample = 0; sample < frame_size; sample++) {
826 for (int source = 0; source < source_count; source++)
827 output[frame_start + sample] += source_samples[source][sample];
828 }
829 continue;
830 }
831
832 // OPTIMIZATION: Batch envelope calculation per-frame instead of per-sample
833 // Calculate peak amplitude for each source over the entire frame
834 int speaking_count = 0;
835
836 for (int i = 0; i < source_count; i++) {
837 int slot = source_map[i];
838 float peak = 0.0f;
839
840 // Find peak amplitude in frame (much faster than per-sample envelope)
841 for (int s = 0; s < frame_size; s++) {
842 float abs_sample = fabsf(source_samples[i][s]);
843 if (abs_sample > peak)
844 peak = abs_sample;
845 }
846
847 // Update envelope using frame peak (one update per frame instead of per-sample)
848 if (peak > mixer->ducking.envelope[slot]) {
849 mixer->ducking.envelope[slot] =
850 mixer->ducking.attack_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.attack_coeff) * peak;
851 } else {
852 mixer->ducking.envelope[slot] =
853 mixer->ducking.release_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.release_coeff) * peak;
854 }
855
856 // Count speaking sources
857 if (mixer->ducking.envelope[slot] > db_to_linear(-60.0f))
858 speaking_count++;
859 }
860
861 // Apply ducking ONCE per frame (not per-sample)
862 ducking_process_frame(&mixer->ducking, mixer->ducking.envelope, mixer->ducking.gain, mixer->max_sources);
863
864 // Calculate crowd scaling ONCE per frame
865 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((float)speaking_count, mixer->crowd_alpha)) : 1.0f;
866 float pre_bus = mixer->base_gain * crowd_gain;
867
868 // Pre-calculate combined gains for each source (ducking * pre_bus)
869 float combined_gains[MIXER_MAX_SOURCES];
870 for (int i = 0; i < source_count; i++) {
871 int slot = source_map[i];
872 combined_gains[i] = mixer->ducking.gain[slot] * pre_bus;
873 }
874
875 // OPTIMIZATION: Fast mixing loop - simple multiply-add with pre-calculated gains
876 // NO per-sample compressor to avoid expensive log/pow calls (480x per iteration)
877 for (int s = 0; s < frame_size; s++) {
878 float mix = 0.0f;
879 for (int i = 0; i < source_count; i++) {
880 mix += source_samples[i][s] * combined_gains[i];
881 }
882
883 // Store in mix buffer for frame-level compression below
884 mixer->mix_buffer[s] = mix;
885 }
886
887 // OPTIMIZATION: Apply compression ONCE per frame instead of per-sample
888 // This reduces expensive log10f/powf calls from 480x to 1x per iteration
889 // Calculate frame peak for compressor sidechain
890 float frame_peak = 0.0f;
891 for (int s = 0; s < frame_size; s++) {
892 float abs_val = fabsf(mixer->mix_buffer[s]);
893 if (abs_val > frame_peak)
894 frame_peak = abs_val;
895 }
896
897 // Process compressor with frame peak (1 call instead of 480)
898 float comp_gain = compressor_process_sample(&mixer->compressor, frame_peak);
899
900 // Apply compression gain and soft clipping to all samples
901 for (int s = 0; s < frame_size; s++) {
902 float mix = mixer->mix_buffer[s] * comp_gain;
903
904 // Soft clip at 0.7 to provide 3dB headroom and prevent hard clipping
905 // With +6dB makeup gain, peaks can exceed 1.0, so we need headroom
906 // threshold=0.7 maps asymptotically toward 1.0, preventing harsh distortion
907 output[frame_start + s] = soft_clip(mix, 0.7f, 3.0f);
908 }
909 }
910
912
913#ifndef NDEBUG
914 STOP_TIMER_AND_LOG_EVERY(warn, NS_PER_SEC_INT, 2 * NS_PER_MS_INT, "mixer_total", "Mixer took");
915#endif
916
917 return num_samples;
918}
919
920/* ============================================================================
921 * Noise Gate Implementation
922 * ============================================================================
923 */
924
925void noise_gate_init(noise_gate_t *gate, float sample_rate) {
926 if (!gate)
927 return;
928
929 gate->sample_rate = sample_rate;
930 gate->envelope = 0.0f;
931 gate->gate_open = false;
932
933 // Default parameters
934 // Slower attack (10ms) prevents clicking artifacts during gate transitions
935 // threshold=0.01, attack=10ms, release=50ms, hysteresis=0.9
936 noise_gate_set_params(gate, 0.01f, 10 * NS_PER_MS_INT, 50 * NS_PER_MS_INT, 0.9f);
937}
938
939void noise_gate_set_params(noise_gate_t *gate, float threshold, uint64_t attack_ns, uint64_t release_ns,
940 float hysteresis) {
941 if (!gate)
942 return;
943
944 gate->threshold = threshold;
945 gate->attack_ns = attack_ns;
946 gate->release_ns = release_ns;
947 gate->hysteresis = hysteresis;
948
949 // Calculate coefficients for envelope follower
950 // Using exponential moving average: coeff = 1 - exp(-1 / (time_s * sample_rate))
951 float attack_s = (float)attack_ns / NS_PER_SEC;
952 float release_s = (float)release_ns / NS_PER_SEC;
953 gate->attack_coeff = 1.0f - expf(-1.0f / (attack_s * gate->sample_rate + 1e-12f));
954 gate->release_coeff = 1.0f - expf(-1.0f / (release_s * gate->sample_rate + 1e-12f));
955}
956
957float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude) {
958 if (!gate)
959 return input;
960
961 // Determine target state with hysteresis
962 float target;
963 if (gate->gate_open) {
964 // Gate is open - use lower threshold (hysteresis) to close
965 target = (peak_amplitude > gate->threshold * gate->hysteresis) ? 1.0f : 0.0f;
966 } else {
967 // Gate is closed - use normal threshold to open
968 target = (peak_amplitude > gate->threshold) ? 1.0f : 0.0f;
969 }
970
971 // Update gate state
972 gate->gate_open = (target > 0.5f);
973
974 // Update envelope with appropriate coefficient
975 float coeff = (target > gate->envelope) ? gate->attack_coeff : gate->release_coeff;
976 gate->envelope += coeff * (target - gate->envelope);
977
978 // Apply gate
979 return input * gate->envelope;
980}
981
982void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples) {
983 if (!gate || !buffer || num_samples <= 0)
984 return;
985
986 // First pass: find peak amplitude
987 float peak = 0.0f;
988 for (int i = 0; i < num_samples; i++) {
989 float abs_sample = fabsf(buffer[i]);
990 if (abs_sample > peak) {
991 peak = abs_sample;
992 }
993 }
994
995 // Second pass: apply gate
996 for (int i = 0; i < num_samples; i++) {
997 buffer[i] = noise_gate_process_sample(gate, buffer[i], peak);
998 }
999}
1000
1002 return gate ? gate->gate_open : false;
1003}
1004
1005/* ============================================================================
1006 * High-Pass Filter Implementation
1007 * ============================================================================
1008 */
1009
1010void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate) {
1011 if (!filter)
1012 return;
1013
1014 filter->cutoff_hz = cutoff_hz;
1015 filter->sample_rate = sample_rate;
1016
1017 // Calculate filter coefficient
1018 // alpha = 1 / (1 + 2*pi*fc/fs)
1019 filter->alpha = 1.0f / (1.0f + 2.0f * M_PI * cutoff_hz / sample_rate);
1020
1021 highpass_filter_reset(filter);
1022}
1023
1025 if (!filter)
1026 return;
1027
1028 filter->prev_input = 0.0f;
1029 filter->prev_output = 0.0f;
1030}
1031
1033 if (!filter)
1034 return input;
1035
1036 // First-order high-pass filter
1037 // y[n] = alpha * (y[n-1] + x[n] - x[n-1])
1038 float output = filter->alpha * (filter->prev_output + input - filter->prev_input);
1039
1040 filter->prev_input = input;
1041 filter->prev_output = output;
1042
1043 return output;
1044}
1045
1046void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples) {
1047 if (!filter || !buffer || num_samples <= 0)
1048 return;
1049
1050 for (int i = 0; i < num_samples; i++) {
1051 buffer[i] = highpass_filter_process_sample(filter, buffer[i]);
1052 }
1053}
1054
1055/* ============================================================================
1056 * Low-Pass Filter Implementation
1057 * ============================================================================
1058 */
1059
1060void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate) {
1061 if (!filter)
1062 return;
1063
1064 filter->cutoff_hz = cutoff_hz;
1065 filter->sample_rate = sample_rate;
1066
1067 // Calculate filter coefficient using RC time constant formula
1068 // alpha = dt / (RC + dt) where RC = 1 / (2 * pi * fc)
1069 float dt = 1.0f / sample_rate;
1070 float rc = 1.0f / (2.0f * (float)M_PI * cutoff_hz);
1071 filter->alpha = dt / (rc + dt);
1072
1073 lowpass_filter_reset(filter);
1074}
1075
1077 if (!filter)
1078 return;
1079
1080 filter->prev_output = 0.0f;
1081}
1082
1084 if (!filter)
1085 return input;
1086
1087 // First-order IIR low-pass filter: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
1088 float output = filter->alpha * input + (1.0f - filter->alpha) * filter->prev_output;
1089
1090 filter->prev_output = output;
1091
1092 return output;
1093}
1094
1095void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples) {
1096 if (!filter || !buffer || num_samples <= 0)
1097 return;
1098
1099 for (int i = 0; i < num_samples; i++) {
1100 buffer[i] = lowpass_filter_process_sample(filter, buffer[i]);
1101 }
1102}
1103
1104/* ============================================================================
1105 * Soft Clipping Implementation
1106 * ============================================================================
1107 */
1108
1109float soft_clip(float sample, float threshold, float steepness) {
1110 if (sample > threshold) {
1111 // Soft clip positive values using tanh curve
1112 // Maps samples above threshold asymptotically toward 1.0
1113 return threshold + (1.0f - threshold) * tanhf((sample - threshold) * steepness);
1114 }
1115 if (sample < -threshold) {
1116 // Soft clip negative values symmetrically
1117 return -threshold + (-1.0f + threshold) * tanhf((sample + threshold) * steepness);
1118 }
1119 return sample;
1120}
1121
1122void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness) {
1123 if (!buffer || num_samples <= 0)
1124 return;
1125
1126 for (int i = 0; i < num_samples; i++) {
1127 buffer[i] = soft_clip(buffer[i], threshold, steepness);
1128 }
1129}
1130
1131/* ============================================================================
1132 * Buffer Utility Functions
1133 * ============================================================================
1134 */
1135
1136float smoothstep(float t) {
1137 if (t <= 0.0f)
1138 return 0.0f;
1139 if (t >= 1.0f)
1140 return 1.0f;
1141 return t * t * (3.0f - 2.0f * t);
1142}
1143
1144int16_t float_to_int16(float sample) {
1145 // Clamp to [-1, 1] then scale to int16 range
1146 if (sample > 1.0f)
1147 sample = 1.0f;
1148 if (sample < -1.0f)
1149 sample = -1.0f;
1150 return (int16_t)(sample * 32767.0f);
1151}
1152
1153float int16_to_float(int16_t sample) {
1154 return (float)sample / 32768.0f;
1155}
1156
1157void buffer_float_to_int16(const float *src, int16_t *dst, int count) {
1158 if (!src || !dst || count <= 0)
1159 return;
1160 for (int i = 0; i < count; i++) {
1161 dst[i] = float_to_int16(src[i]);
1162 }
1163}
1164
1165void buffer_int16_to_float(const int16_t *src, float *dst, int count) {
1166 if (!src || !dst || count <= 0)
1167 return;
1168 for (int i = 0; i < count; i++) {
1169 dst[i] = int16_to_float(src[i]);
1170 }
1171}
1172
1173float buffer_peak(const float *buffer, int count) {
1174 if (!buffer || count <= 0)
1175 return 0.0f;
1176
1177 float peak = 0.0f;
1178 for (int i = 0; i < count; i++) {
1179 float abs_sample = fabsf(buffer[i]);
1180 if (abs_sample > peak)
1181 peak = abs_sample;
1182 }
1183 return peak;
1184}
1185
1186void apply_gain_buffer(float *buffer, int count, float gain) {
1187 if (!buffer || count <= 0)
1188 return;
1189
1190 for (int i = 0; i < count; i++) {
1191 buffer[i] *= gain;
1192 }
1193}
1194
1195void fade_buffer(float *buffer, int count, float start_gain, float end_gain) {
1196 if (!buffer || count <= 0)
1197 return;
1198
1199 float step = (end_gain - start_gain) / (float)count;
1200 float gain = start_gain;
1201 for (int i = 0; i < count; i++) {
1202 buffer[i] *= gain;
1203 gain += step;
1204 }
1205}
1206
1207void fade_buffer_smooth(float *buffer, int count, bool fade_in) {
1208 if (!buffer || count <= 0)
1209 return;
1210
1211 for (int i = 0; i < count; i++) {
1212 float t = (float)i / (float)(count - 1);
1213 float gain = smoothstep(fade_in ? t : (1.0f - t));
1214 buffer[i] *= gain;
1215 }
1216}
1217
1218void copy_buffer_with_gain(const float *src, float *dst, int count, float gain) {
1219 if (!src || !dst || count <= 0)
1220 return;
1221
1222 for (int i = 0; i < count; i++) {
1223 dst[i] = src[i] * gain;
1224 }
1225}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
🔢 Bit Manipulation Utilities
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
#️⃣ FNV-1a Hash Function Implementation
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:1001
uint8_t source_id_to_index[256]
Hash table mapping client_id → mixer source index (uses hash function for strings)
Definition mixer.h:345
float base_gain
Base gain before crowd scaling is applied.
Definition mixer.h:355
void copy_buffer_with_gain(const float *src, float *dst, int count, float gain)
Copy buffer with gain scaling.
Definition mixer.c:1218
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:982
void buffer_float_to_int16(const float *src, int16_t *dst, int count)
Convert float buffer to int16 buffer.
Definition mixer.c:1157
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:75
const char ** source_ids
Array of client ID strings (one per source slot)
Definition mixer.h:338
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:1024
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:1122
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:1032
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:340
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:1186
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:350
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
uint64_t release_ns
Ducking release time in nanoseconds.
Definition mixer.h:281
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:228
void noise_gate_init(noise_gate_t *gate, float sample_rate)
Initialize a noise gate.
Definition mixer.c:925
float crowd_alpha
Crowd scaling exponent (typically 0.5 for sqrt scaling)
Definition mixer.h:353
void ducking_destroy(ducking_t *duck)
Free ducking system resources.
Definition mixer.c:210
float buffer_peak(const float *buffer, int count)
Find peak absolute value in buffer.
Definition mixer.c:1173
ducking_t ducking
Ducking system (active speaker detection and attenuation)
Definition mixer.h:358
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:1109
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
void mixer_remove_source(mixer_t *mixer, const char *client_id)
Remove an audio source from the mixer.
Definition mixer.c:477
void fade_buffer(float *buffer, int count, float start_gain, float end_gain)
Apply linear fade to buffer in-place.
Definition mixer.c:1195
void compressor_init(compressor_t *comp, float sample_rate)
Initialize a compressor.
Definition mixer.c:86
float alpha
Filter coefficient alpha (calculated from cutoff_hz)
Definition mixer.h:248
void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, uint64_t attack_ns, uint64_t release_ns, float makeup_dB)
Set compressor parameters.
Definition mixer.c:97
uint64_t attack_ns
Attack time in nanoseconds (how fast gate opens)
Definition mixer.h:184
int ducking_init(ducking_t *duck, int num_sources, float sample_rate)
Initialize ducking system.
Definition mixer.c:155
#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:1010
uint64_t active_sources_mask
Bitset of active sources (bit i = source i is active, O(1) iteration)
Definition mixer.h:343
float int16_to_float(int16_t sample)
Convert int16 sample to float.
Definition mixer.c:1153
uint64_t attack_ns
Ducking attack time in nanoseconds.
Definition mixer.h:279
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
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:363
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:1136
void fade_buffer_smooth(float *buffer, int count, bool fade_in)
Apply smoothstep fade to buffer in-place.
Definition mixer.c:1207
int16_t float_to_int16(float sample)
Convert float sample to int16 (WebRTC format)
Definition mixer.c:1144
compressor_t compressor
Compressor (dynamic range compression)
Definition mixer.h:360
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:1060
mixer_t * mixer_create(int max_sources, int sample_rate)
Create a new audio mixer.
Definition mixer.c:262
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:687
float db_to_linear(float db)
Convert decibels to linear gain.
Definition mixer.c:67
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:1095
uint64_t release_ns
Release time in nanoseconds (how fast gate closes)
Definition mixer.h:186
#define MIXER_HASH_INVALID
Invalid index sentinel value for mixer hash table.
Definition mixer.h:464
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:1046
void ducking_set_params(ducking_t *duck, float threshold_dB, float leader_margin_dB, float atten_dB, uint64_t attack_ns, uint64_t release_ns)
Set ducking parameters.
Definition mixer.c:219
void mixer_set_source_active(mixer_t *mixer, const char *client_id, bool active)
Set whether a source is active (receiving audio)
Definition mixer.c:515
audio_ring_buffer_t ** source_buffers
Array of pointers to client audio ring buffers.
Definition mixer.h:334
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:131
void mixer_destroy(mixer_t *mixer)
Destroy a mixer and free all resources.
Definition mixer.c:396
int sample_rate
Sample rate in Hz (e.g., 44100)
Definition mixer.h:331
uint64_t attack_ns
Attack time in nanoseconds (how fast compression kicks in)
Definition mixer.h:145
float lowpass_filter_process_sample(lowpass_filter_t *filter, float input)
Process a single sample through low-pass filter.
Definition mixer.c:1083
void noise_gate_set_params(noise_gate_t *gate, float threshold, uint64_t attack_ns, uint64_t release_ns, float hysteresis)
Set noise gate parameters.
Definition mixer.c:939
float linear_to_db(float linear)
Convert linear gain to decibels.
Definition mixer.c:71
int mixer_add_source(mixer_t *mixer, const char *client_id, audio_ring_buffer_t *buffer)
Add an audio source to the mixer.
Definition mixer.c:424
uint64_t release_ns
Release time in nanoseconds (how fast compression releases)
Definition mixer.h:147
struct mixer_listener_buffer * listener_buffers[32][32]
Definition mixer.h:336
void lowpass_filter_reset(lowpass_filter_t *filter)
Reset low-pass filter state.
Definition mixer.c:1076
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:542
void buffer_int16_to_float(const int16_t *src, float *dst, int count)
Convert int16 buffer to float buffer.
Definition mixer.c:1165
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:957
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:469
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:471
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define NAMED_REGISTER_RWLOCK(lock, name, parent_ptr)
Register an rwlock with automatic format specifier.
#define NAMED_REGISTER_MIXER(mixer, name, parent_ptr)
Register an audio mixer with automatic format specifier.
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
@ ERROR_THREAD
#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.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define STOP_TIMER_AND_LOG_EVERY(log_level, interval_ns, threshold_ns, timer_name, msg_fmt,...)
Stop a timer and log the result with rate limiting.
Definition time.h:647
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
Definition time.h:333
#define NS_PER_MS_INT
Definition time.h:156
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
#define US_PER_MS_INT
Definition time.h:160
#define STOP_TIMER(name_fmt,...)
Stop a timer with formatted name and return elapsed time.
Definition time.h:403
#define NS_PER_SEC
Definition time.h:148
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:349
int rwlock_init(rwlock_t *rwlock, const char *name)
Initialize a read-write lock with a name.
Definition threading.c:65
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:294
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:313
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:331
🔊 Audio Capture and Playback Interface for ascii-chat
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:708
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:699
🔢 Mathematical Utility Functions
struct mixer_listener_buffer mixer_listener_buffer_t
#define MIXER_LISTENER_SAMPLES
Definition mixer.c:21
#define M_PI
Definition mixer.c:63
Multi-Source Audio Mixing and Processing System.
✅ Safe Integer Arithmetic and Overflow Detection
Audio ring buffer for real-time audio streaming.
Definition ringbuffer.h:205
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
float samples[8192]
Definition mixer.c:23
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