ascii-chat 0.8.38
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 <ascii-chat/audio/audio.h>
8#include <ascii-chat/audio/mixer.h>
9#include <ascii-chat/common.h>
10#include <ascii-chat/asciichat_errno.h> // For asciichat_errno system
11#include <ascii-chat/util/time.h> // For timing instrumentation
12#include <ascii-chat/util/bits.h> // For find_first_set_bit
13#include <ascii-chat/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 0dB makeup gain (unity)
48 // Soft clip at 0.7 provides 3dB headroom to prevent hard clipping
49 // Ducking and crowd scaling naturally reduce volume, no compensation needed
50 compressor_set_params(comp, -10.0f, 4.0f, 10 * NS_PER_MS_INT, 100 * NS_PER_MS_INT, 0.0f);
51}
52
53void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, uint64_t attack_ns, uint64_t release_ns,
54 float makeup_dB) {
55 comp->threshold_dB = threshold_dB;
56 comp->ratio = ratio;
57 comp->attack_ns = attack_ns;
58 comp->release_ns = release_ns;
59 comp->makeup_dB = makeup_dB;
60 comp->knee_dB = 2.0f; // Fixed soft knee
61
62 // Calculate time constants
63 float attack_tau = (float)attack_ns / NS_PER_SEC;
64 float release_tau = (float)release_ns / NS_PER_SEC;
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_ns = 10 * NS_PER_MS_INT; // Slower attack (10ms)
124 duck->release_ns = 200 * NS_PER_MS_INT; // Slower release (200ms)
125 duck->envelope = NULL;
126 duck->gain = NULL;
127
128 // Calculate time constants
129 float attack_tau = (float)duck->attack_ns / NS_PER_SEC;
130 float release_tau = (float)duck->release_ns / NS_PER_SEC;
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
166void ducking_destroy(ducking_t *duck) {
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, uint64_t attack_ns,
176 uint64_t release_ns) {
177 duck->threshold_dB = threshold_dB;
178 duck->leader_margin_dB = leader_margin_dB;
179 duck->atten_dB = atten_dB;
180 duck->attack_ns = attack_ns;
181 duck->release_ns = release_ns;
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);
257 SAFE_FREE(mixer->source_buffers);
258 SAFE_FREE(mixer);
259 return NULL;
260 }
261 mixer->source_ids = SAFE_MALLOC(ids_size, uint32_t *);
262 if (!mixer->source_ids) {
263 SAFE_FREE(mixer->source_buffers);
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);
271 SAFE_FREE(mixer->source_buffers);
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) {
278 SAFE_FREE(mixer->source_buffers);
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 for (int i = 0; i < 256; i++) {
292 mixer->source_id_to_index[i] = MIXER_HASH_INVALID;
293 }
294
295 // Allocate mix buffer BEFORE rwlock_init so cleanup path is correct
296 size_t mix_buffer_size = 0;
297 if (checked_size_mul(MIXER_FRAME_SIZE, sizeof(float), &mix_buffer_size) != ASCIICHAT_OK) {
298 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Mixer mix buffer size overflow: %zu samples", (size_t)MIXER_FRAME_SIZE);
299 SAFE_FREE(mixer->source_buffers);
300 SAFE_FREE(mixer->source_ids);
301 SAFE_FREE(mixer->source_active);
302 SAFE_FREE(mixer);
303 return NULL;
304 }
305 mixer->mix_buffer = SAFE_MALLOC(mix_buffer_size, float *);
306 if (!mixer->mix_buffer) {
307 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mix buffer");
308 SAFE_FREE(mixer->source_buffers);
309 SAFE_FREE(mixer->source_ids);
310 SAFE_FREE(mixer->source_active);
311 SAFE_FREE(mixer);
312 return NULL;
313 }
314
315 // OPTIMIZATION 2: Initialize reader-writer lock
316 if (rwlock_init(&mixer->source_lock) != 0) {
317 SET_ERRNO(ERROR_THREAD, "Failed to initialize mixer source lock");
318 SAFE_FREE(mixer->source_buffers);
319 SAFE_FREE(mixer->source_ids);
320 SAFE_FREE(mixer->source_active);
321 SAFE_FREE(mixer->mix_buffer);
322 SAFE_FREE(mixer);
323 return NULL;
324 }
325
326 // Set crowd scaling parameters
327 mixer->crowd_alpha = 0.5f; // Square root scaling
328 mixer->base_gain = 1.0f; // Unity gain - let compressor handle loudness, avoid clipping
329
330 // Initialize processing
331 if (ducking_init(&mixer->ducking, max_sources, (float)sample_rate) != ASCIICHAT_OK) {
332 rwlock_destroy(&mixer->source_lock);
333 SAFE_FREE(mixer->source_buffers);
334 SAFE_FREE(mixer->source_ids);
335 SAFE_FREE(mixer->source_active);
336 SAFE_FREE(mixer->mix_buffer);
337 SAFE_FREE(mixer);
338 return NULL;
339 }
340 compressor_init(&mixer->compressor, (float)sample_rate);
341
342 log_debug("Audio mixer created: max_sources=%d, sample_rate=%d", max_sources, sample_rate);
343
344 return mixer;
345}
346
347void mixer_destroy(mixer_t *mixer) {
348 if (!mixer)
349 return;
350
351 // OPTIMIZATION 2: Destroy reader-writer lock
352 rwlock_destroy(&mixer->source_lock);
353
354 ducking_destroy(&mixer->ducking);
355
356 SAFE_FREE(mixer->source_buffers);
357 SAFE_FREE(mixer->source_ids);
358 SAFE_FREE(mixer->source_active);
359 SAFE_FREE(mixer->mix_buffer);
360 SAFE_FREE(mixer);
361 log_debug("Audio mixer destroyed");
362}
363
364int mixer_add_source(mixer_t *mixer, uint32_t client_id, audio_ring_buffer_t *buffer) {
365 if (!mixer || !buffer)
366 return -1;
367
368 // OPTIMIZATION 2: Acquire write lock for source modification
369 rwlock_wrlock(&mixer->source_lock);
370
371 // Find an empty slot
372 int slot = -1;
373 for (int i = 0; i < mixer->max_sources; i++) {
374 if (mixer->source_ids[i] == 0) {
375 slot = i;
376 break;
377 }
378 }
379
380 if (slot == -1) {
381 rwlock_wrunlock(&mixer->source_lock);
382 log_warn("Mixer: No available slots for client %u", client_id);
383 return -1;
384 }
385
386 mixer->source_buffers[slot] = buffer;
387 mixer->source_ids[slot] = client_id;
388 mixer->source_active[slot] = true;
389 mixer->num_sources++;
390
391 // OPTIMIZATION 1: Update bitset optimization structures
392 mixer->active_sources_mask |= (1ULL << slot); // Set bit for this slot
393 mixer_hash_set_slot(mixer, client_id, (uint8_t)slot); // Hash table: client_id → slot
394
395 rwlock_wrunlock(&mixer->source_lock);
396
397 log_info("Mixer: Added source for client %u at slot %d", client_id, slot);
398 return slot;
399}
400
401void mixer_remove_source(mixer_t *mixer, uint32_t client_id) {
402 if (!mixer)
403 return;
404
405 // OPTIMIZATION 2: Acquire write lock for source modification
406 rwlock_wrlock(&mixer->source_lock);
407
408 for (int i = 0; i < mixer->max_sources; i++) {
409 if (mixer->source_ids[i] == client_id) {
410 mixer->source_buffers[i] = NULL;
411 mixer->source_ids[i] = 0;
412 mixer->source_active[i] = false;
413 mixer->num_sources--;
414
415 // OPTIMIZATION 1: Update bitset optimization structures
416 mixer->active_sources_mask &= ~(1ULL << i); // Clear bit for this slot
417 mixer_hash_mark_invalid(mixer, client_id); // Mark as invalid in hash table
418
419 // Reset ducking state for this source
420 mixer->ducking.envelope[i] = 0.0f;
421 mixer->ducking.gain[i] = 1.0f;
422
423 rwlock_wrunlock(&mixer->source_lock);
424
425 log_info("Mixer: Removed source for client %u from slot %d", client_id, i);
426 return;
427 }
428 }
429
430 rwlock_wrunlock(&mixer->source_lock);
431}
432
433void mixer_set_source_active(mixer_t *mixer, uint32_t client_id, bool active) {
434 if (!mixer)
435 return;
436
437 // OPTIMIZATION 2: Acquire write lock for source modification
438 rwlock_wrlock(&mixer->source_lock);
439
440 for (int i = 0; i < mixer->max_sources; i++) {
441 if (mixer->source_ids[i] == client_id) {
442 mixer->source_active[i] = active;
443
444 // OPTIMIZATION 1: Update bitset for active state change
445 if (active) {
446 mixer->active_sources_mask |= (1ULL << i); // Set bit
447 } else {
448 mixer->active_sources_mask &= ~(1ULL << i); // Clear bit
449 }
450
451 rwlock_wrunlock(&mixer->source_lock);
452 log_debug("Mixer: Set source %u active=%d", client_id, active);
453 return;
454 }
455 }
456
457 rwlock_wrunlock(&mixer->source_lock);
458}
459
460int mixer_process(mixer_t *mixer, float *output, int num_samples) {
461 if (!mixer || !output || num_samples <= 0)
462 return -1;
463
464 // THREAD SAFETY: Acquire read lock to protect against concurrent source add/remove
465 // This prevents race conditions where source_buffers[i] could be set to NULL while we read it
466 rwlock_rdlock(&mixer->source_lock);
467
468 // Clear output buffer
469 SAFE_MEMSET(output, num_samples * sizeof(float), 0, num_samples * sizeof(float));
470
471 // Count active sources
472 int active_count = 0;
473 for (int i = 0; i < mixer->max_sources; i++) {
474 if (mixer->source_ids[i] != 0 && mixer->source_active[i] && mixer->source_buffers[i]) {
475 active_count++;
476 }
477 }
478
479 if (active_count == 0) {
480 // No active sources, output silence
481 return 0;
482 }
483
484 // Process in frames for efficiency
485 for (int frame_start = 0; frame_start < num_samples; frame_start += MIXER_FRAME_SIZE) {
486 int frame_size = (frame_start + MIXER_FRAME_SIZE > num_samples) ? (num_samples - frame_start) : MIXER_FRAME_SIZE;
487
488 // Clear mix buffer
489 SAFE_MEMSET(mixer->mix_buffer, frame_size * sizeof(float), 0, frame_size * sizeof(float));
490
491 // Temporary buffers for source audio
492 float source_samples[MIXER_MAX_SOURCES][MIXER_FRAME_SIZE];
493 int source_count = 0;
494 int source_map[MIXER_MAX_SOURCES]; // Maps source index to slot
495
496 // Read from each active source
497 for (int i = 0; i < mixer->max_sources; i++) {
498 if (mixer->source_ids[i] != 0 && mixer->source_active[i] && mixer->source_buffers[i]) {
499 // Read samples from this source's ring buffer
500 size_t samples_read_size =
501 audio_ring_buffer_read(mixer->source_buffers[i], source_samples[source_count], frame_size);
502 int samples_read = (int)samples_read_size;
503
504 // Accept partial frames - pad with silence if needed
505 // This prevents audio dropouts when ring buffers are temporarily under-filled
506 if (samples_read > 0) {
507 // Pad remaining samples with silence if we got a partial frame
508 if (samples_read < frame_size) {
509 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) * sizeof(float), 0,
510 (frame_size - samples_read) * sizeof(float));
511 }
512
513 source_map[source_count] = i;
514 source_count++;
515
516 if (source_count >= MIXER_MAX_SOURCES)
517 break;
518 }
519 }
520 }
521
522 // OPTIMIZATION: Batch envelope calculation per-frame instead of per-sample
523 // Calculate peak amplitude for each source over the entire frame
524 int speaking_count = 0;
525
526 for (int i = 0; i < source_count; i++) {
527 int slot = source_map[i];
528 float peak = 0.0f;
529
530 // Find peak amplitude in frame (much faster than per-sample envelope)
531 for (int s = 0; s < frame_size; s++) {
532 float abs_sample = fabsf(source_samples[i][s]);
533 if (abs_sample > peak)
534 peak = abs_sample;
535 }
536
537 // Update envelope using frame peak (one update per frame instead of per-sample)
538 if (peak > mixer->ducking.envelope[slot]) {
539 mixer->ducking.envelope[slot] =
540 mixer->ducking.attack_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.attack_coeff) * peak;
541 } else {
542 mixer->ducking.envelope[slot] =
543 mixer->ducking.release_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.release_coeff) * peak;
544 }
545
546 // Count speaking sources
547 if (mixer->ducking.envelope[slot] > db_to_linear(-60.0f))
548 speaking_count++;
549 }
550
551 // Apply ducking ONCE per frame (not per-sample)
552 ducking_process_frame(&mixer->ducking, mixer->ducking.envelope, mixer->ducking.gain, mixer->max_sources);
553
554 // Calculate crowd scaling ONCE per frame
555 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((float)speaking_count, mixer->crowd_alpha)) : 1.0f;
556 float pre_bus = mixer->base_gain * crowd_gain;
557
558 // Pre-calculate combined gains for each source (ducking * pre_bus)
559 float combined_gains[MIXER_MAX_SOURCES];
560 for (int i = 0; i < source_count; i++) {
561 int slot = source_map[i];
562 combined_gains[i] = mixer->ducking.gain[slot] * pre_bus;
563 }
564
565 // OPTIMIZATION: Fast mixing loop - simple multiply-add with pre-calculated gains
566 // NO per-sample compressor to avoid expensive log/pow calls (480x per iteration)
567 for (int s = 0; s < frame_size; s++) {
568 float mix = 0.0f;
569 for (int i = 0; i < source_count; i++) {
570 mix += source_samples[i][s] * combined_gains[i];
571 }
572
573 // Store in mix buffer for frame-level compression below
574 mixer->mix_buffer[s] = mix;
575 }
576
577 // OPTIMIZATION: Apply compression ONCE per frame instead of per-sample
578 // This reduces expensive log10f/powf calls from 480x to 1x per iteration
579 // Calculate frame peak for compressor sidechain
580 float frame_peak = 0.0f;
581 for (int s = 0; s < frame_size; s++) {
582 float abs_val = fabsf(mixer->mix_buffer[s]);
583 if (abs_val > frame_peak)
584 frame_peak = abs_val;
585 }
586
587 // Process compressor with frame peak (1 call instead of 480)
588 float comp_gain = compressor_process_sample(&mixer->compressor, frame_peak);
589
590 // Apply compression gain and soft clipping to all samples
591 for (int s = 0; s < frame_size; s++) {
592 float mix = mixer->mix_buffer[s] * comp_gain;
593
594 // Soft clip at 0.7 to provide 3dB headroom and prevent hard clipping
595 // With +6dB makeup gain, peaks can exceed 1.0, so we need headroom
596 // threshold=0.7 maps asymptotically toward 1.0, preventing harsh distortion
597 output[frame_start + s] = soft_clip(mix, 0.7f, 3.0f);
598 }
599 }
600
601 rwlock_rdunlock(&mixer->source_lock);
602 return num_samples;
603}
604
605int mixer_process_excluding_source(mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id) {
606 if (!mixer || !output || num_samples <= 0)
607 return -1;
608
609 // Only use timing in debug builds - snprintf + hashtable ops are expensive in hot path
610#ifndef NDEBUG
611 START_TIMER("mixer_total");
612#endif
613
614 // THREAD SAFETY: Acquire read lock to protect against concurrent source add/remove
615 // This prevents race conditions where source_buffers[i] could be set to NULL while we read it
616 rwlock_rdlock(&mixer->source_lock);
617
618 // Clear output buffer
619 SAFE_MEMSET(output, num_samples * sizeof(float), 0, num_samples * sizeof(float));
620
621 // OPTIMIZATION 1: O(1) exclusion using bitset and hash table
622 uint8_t exclude_index = mixer_hash_get_slot(mixer, exclude_client_id);
623 uint64_t active_mask = mixer->active_sources_mask;
624
625 // Validate exclude_index before using in bitshift
626 // - exclude_index == MIXER_HASH_INVALID means "not found" (sentinel value from initialization)
627 // - exclude_index >= MIXER_MAX_SOURCES would cause undefined behavior in bitshift
628 // - Also verify hash table lookup actually matched the client_id (collision detection)
629 bool valid_exclude = (exclude_index < MIXER_MAX_SOURCES && exclude_index != MIXER_HASH_INVALID &&
630 mixer->source_ids[exclude_index] == exclude_client_id);
631
632 if (valid_exclude) {
633 // Apply exclusion to prevent echo feedback
634 uint64_t mask_without_excluded = active_mask & ~(1ULL << exclude_index);
635 active_mask = mask_without_excluded;
636
637 // DIAGNOSTIC: Log which client was excluded and which remain active
638 log_dev_every(
639 4500 * US_PER_MS_INT,
640 "MIXER EXCLUSION: exclude_client=%u, exclude_index=%u, active_mask_before=0x%llx, active_mask_after=0x%llx",
641 exclude_client_id, exclude_index, (unsigned long long)(active_mask | (1ULL << exclude_index)),
642 (unsigned long long)active_mask);
643 } else {
644 // DIAGNOSTIC: Failed to exclude - log why
645 log_dev_every(4500 * US_PER_MS_INT,
646 "MIXER EXCLUSION FAILED: exclude_client=%u, exclude_index=%u (valid=%d), lookup_id=%u",
647 exclude_client_id, exclude_index, valid_exclude,
648 (exclude_index < MIXER_MAX_SOURCES && exclude_index != 0xFF) ? mixer->source_ids[exclude_index] : 0);
649 }
650
651 // Fast check: any sources to mix?
652 if (active_mask == 0) {
653 rwlock_rdunlock(&mixer->source_lock);
654#ifndef NDEBUG
655 STOP_TIMER("mixer_total");
656#endif
657 return 0; // No sources to mix (excluding the specified client), output silence
658 }
659
660 // Process in frames for efficiency
661 for (int frame_start = 0; frame_start < num_samples; frame_start += MIXER_FRAME_SIZE) {
662 int frame_size = (frame_start + MIXER_FRAME_SIZE > num_samples) ? (num_samples - frame_start) : MIXER_FRAME_SIZE;
663
664 uint64_t read_start_ns = time_get_ns();
665
666 // Clear mix buffer
667 SAFE_MEMSET(mixer->mix_buffer, frame_size * sizeof(float), 0, frame_size * sizeof(float));
668
669 // Temporary buffers for source audio
670 float source_samples[MIXER_MAX_SOURCES][MIXER_FRAME_SIZE];
671 int source_count = 0;
672 int source_map[MIXER_MAX_SOURCES]; // Maps source index to slot
673
674 // OPTIMIZATION 1: Iterate only over active sources using bitset
675 uint64_t current_mask = active_mask;
676 while (current_mask && source_count < MIXER_MAX_SOURCES) {
677 int i = find_first_set_bit(current_mask); // Portable: find first set bit
678 current_mask &= current_mask - 1; // Clear lowest set bit
679
680 // Verify source is valid (defensive programming)
681 if (i < mixer->max_sources && mixer->source_ids[i] != 0 && mixer->source_buffers[i]) {
682 // Read samples from this source's ring buffer
683 size_t samples_read_size =
684 audio_ring_buffer_read(mixer->source_buffers[i], source_samples[source_count], frame_size);
685 int samples_read = (int)samples_read_size;
686
687 // Accept partial frames - pad with silence if needed
688 // This prevents audio dropouts when ring buffers are temporarily under-filled
689 if (samples_read > 0) {
690 // Pad remaining samples with silence if we got a partial frame
691 if (samples_read < frame_size) {
692 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) * sizeof(float), 0,
693 (frame_size - samples_read) * sizeof(float));
694 }
695
696 // DIAGNOSTIC: Calculate RMS of this source's audio
697 float source_rms = 0.0f;
698 if (frame_start == 0) { // Only log for first frame to reduce spam
699 float sum_squares = 0.0f;
700 for (int s = 0; s < samples_read; s++) {
701 sum_squares += source_samples[source_count][s] * source_samples[source_count][s];
702 }
703 source_rms = sqrtf(sum_squares / (float)samples_read);
704 log_info_every(NS_PER_MS_INT, "MIXER SOURCE READ: client_id=%u, slot=%d, samples_read=%d, RMS=%.6f",
705 mixer->source_ids[i], i, samples_read, source_rms);
706 }
707
708 source_map[source_count] = i;
709 source_count++;
710 }
711 }
712 }
713
714 uint64_t read_end_ns = time_get_ns();
715 uint64_t read_time_ns = time_elapsed_ns(read_start_ns, read_end_ns);
716 uint64_t read_time_us = time_ns_to_us(read_time_ns);
717
718 if (read_time_ns > 10 * NS_PER_MS_INT) {
719 log_warn_every(LOG_RATE_DEFAULT, "Mixer: Slow source reading took %lluus (%.2fms) for %d sources", read_time_us,
720 (float)read_time_us / 1000.0f, source_count);
721 }
722
723 // OPTIMIZATION: Batch envelope calculation per-frame instead of per-sample
724 // Calculate peak amplitude for each source over the entire frame
725 int speaking_count = 0;
726
727 for (int i = 0; i < source_count; i++) {
728 int slot = source_map[i];
729 float peak = 0.0f;
730
731 // Find peak amplitude in frame (much faster than per-sample envelope)
732 for (int s = 0; s < frame_size; s++) {
733 float abs_sample = fabsf(source_samples[i][s]);
734 if (abs_sample > peak)
735 peak = abs_sample;
736 }
737
738 // Update envelope using frame peak (one update per frame instead of per-sample)
739 if (peak > mixer->ducking.envelope[slot]) {
740 mixer->ducking.envelope[slot] =
741 mixer->ducking.attack_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.attack_coeff) * peak;
742 } else {
743 mixer->ducking.envelope[slot] =
744 mixer->ducking.release_coeff * mixer->ducking.envelope[slot] + (1.0f - mixer->ducking.release_coeff) * peak;
745 }
746
747 // Count speaking sources
748 if (mixer->ducking.envelope[slot] > db_to_linear(-60.0f))
749 speaking_count++;
750 }
751
752 // Apply ducking ONCE per frame (not per-sample)
753 ducking_process_frame(&mixer->ducking, mixer->ducking.envelope, mixer->ducking.gain, mixer->max_sources);
754
755 // Calculate crowd scaling ONCE per frame
756 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((float)speaking_count, mixer->crowd_alpha)) : 1.0f;
757 float pre_bus = mixer->base_gain * crowd_gain;
758
759 // Pre-calculate combined gains for each source (ducking * pre_bus)
760 float combined_gains[MIXER_MAX_SOURCES];
761 for (int i = 0; i < source_count; i++) {
762 int slot = source_map[i];
763 combined_gains[i] = mixer->ducking.gain[slot] * pre_bus;
764 }
765
766 // OPTIMIZATION: Fast mixing loop - simple multiply-add with pre-calculated gains
767 // NO per-sample compressor to avoid expensive log/pow calls (480x per iteration)
768 for (int s = 0; s < frame_size; s++) {
769 float mix = 0.0f;
770 for (int i = 0; i < source_count; i++) {
771 mix += source_samples[i][s] * combined_gains[i];
772 }
773
774 // Store in mix buffer for frame-level compression below
775 mixer->mix_buffer[s] = mix;
776 }
777
778 // OPTIMIZATION: Apply compression ONCE per frame instead of per-sample
779 // This reduces expensive log10f/powf calls from 480x to 1x per iteration
780 // Calculate frame peak for compressor sidechain
781 float frame_peak = 0.0f;
782 for (int s = 0; s < frame_size; s++) {
783 float abs_val = fabsf(mixer->mix_buffer[s]);
784 if (abs_val > frame_peak)
785 frame_peak = abs_val;
786 }
787
788 // Process compressor with frame peak (1 call instead of 480)
789 float comp_gain = compressor_process_sample(&mixer->compressor, frame_peak);
790
791 // Apply compression gain and soft clipping to all samples
792 for (int s = 0; s < frame_size; s++) {
793 float mix = mixer->mix_buffer[s] * comp_gain;
794
795 // Soft clip at 0.7 to provide 3dB headroom and prevent hard clipping
796 // With +6dB makeup gain, peaks can exceed 1.0, so we need headroom
797 // threshold=0.7 maps asymptotically toward 1.0, preventing harsh distortion
798 output[frame_start + s] = soft_clip(mix, 0.7f, 3.0f);
799 }
800 }
801
802 rwlock_rdunlock(&mixer->source_lock);
803
804#ifndef NDEBUG
805 STOP_TIMER_AND_LOG_EVERY(warn, NS_PER_SEC_INT, 2 * NS_PER_MS_INT, "mixer_total", "Mixer took");
806#endif
807
808 return num_samples;
809}
810
811/* ============================================================================
812 * Noise Gate Implementation
813 * ============================================================================
814 */
815
816void noise_gate_init(noise_gate_t *gate, float sample_rate) {
817 if (!gate)
818 return;
819
820 gate->sample_rate = sample_rate;
821 gate->envelope = 0.0f;
822 gate->gate_open = false;
823
824 // Default parameters
825 // Slower attack (10ms) prevents clicking artifacts during gate transitions
826 // threshold=0.01, attack=10ms, release=50ms, hysteresis=0.9
827 noise_gate_set_params(gate, 0.01f, 10 * NS_PER_MS_INT, 50 * NS_PER_MS_INT, 0.9f);
828}
829
830void noise_gate_set_params(noise_gate_t *gate, float threshold, uint64_t attack_ns, uint64_t release_ns,
831 float hysteresis) {
832 if (!gate)
833 return;
834
835 gate->threshold = threshold;
836 gate->attack_ns = attack_ns;
837 gate->release_ns = release_ns;
838 gate->hysteresis = hysteresis;
839
840 // Calculate coefficients for envelope follower
841 // Using exponential moving average: coeff = 1 - exp(-1 / (time_s * sample_rate))
842 float attack_s = (float)attack_ns / NS_PER_SEC;
843 float release_s = (float)release_ns / NS_PER_SEC;
844 gate->attack_coeff = 1.0f - expf(-1.0f / (attack_s * gate->sample_rate + 1e-12f));
845 gate->release_coeff = 1.0f - expf(-1.0f / (release_s * gate->sample_rate + 1e-12f));
846}
847
848float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude) {
849 if (!gate)
850 return input;
851
852 // Determine target state with hysteresis
853 float target;
854 if (gate->gate_open) {
855 // Gate is open - use lower threshold (hysteresis) to close
856 target = (peak_amplitude > gate->threshold * gate->hysteresis) ? 1.0f : 0.0f;
857 } else {
858 // Gate is closed - use normal threshold to open
859 target = (peak_amplitude > gate->threshold) ? 1.0f : 0.0f;
860 }
861
862 // Update gate state
863 gate->gate_open = (target > 0.5f);
864
865 // Update envelope with appropriate coefficient
866 float coeff = (target > gate->envelope) ? gate->attack_coeff : gate->release_coeff;
867 gate->envelope += coeff * (target - gate->envelope);
868
869 // Apply gate
870 return input * gate->envelope;
871}
872
873void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples) {
874 if (!gate || !buffer || num_samples <= 0)
875 return;
876
877 // First pass: find peak amplitude
878 float peak = 0.0f;
879 for (int i = 0; i < num_samples; i++) {
880 float abs_sample = fabsf(buffer[i]);
881 if (abs_sample > peak) {
882 peak = abs_sample;
883 }
884 }
885
886 // Second pass: apply gate
887 for (int i = 0; i < num_samples; i++) {
888 buffer[i] = noise_gate_process_sample(gate, buffer[i], peak);
889 }
890}
891
892bool noise_gate_is_open(const noise_gate_t *gate) {
893 return gate ? gate->gate_open : false;
894}
895
896/* ============================================================================
897 * High-Pass Filter Implementation
898 * ============================================================================
899 */
900
901void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate) {
902 if (!filter)
903 return;
904
905 filter->cutoff_hz = cutoff_hz;
906 filter->sample_rate = sample_rate;
907
908 // Calculate filter coefficient
909 // alpha = 1 / (1 + 2*pi*fc/fs)
910 filter->alpha = 1.0f / (1.0f + 2.0f * M_PI * cutoff_hz / sample_rate);
911
912 highpass_filter_reset(filter);
913}
914
915void highpass_filter_reset(highpass_filter_t *filter) {
916 if (!filter)
917 return;
918
919 filter->prev_input = 0.0f;
920 filter->prev_output = 0.0f;
921}
922
923float highpass_filter_process_sample(highpass_filter_t *filter, float input) {
924 if (!filter)
925 return input;
926
927 // First-order high-pass filter
928 // y[n] = alpha * (y[n-1] + x[n] - x[n-1])
929 float output = filter->alpha * (filter->prev_output + input - filter->prev_input);
930
931 filter->prev_input = input;
932 filter->prev_output = output;
933
934 return output;
935}
936
937void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples) {
938 if (!filter || !buffer || num_samples <= 0)
939 return;
940
941 for (int i = 0; i < num_samples; i++) {
942 buffer[i] = highpass_filter_process_sample(filter, buffer[i]);
943 }
944}
945
946/* ============================================================================
947 * Low-Pass Filter Implementation
948 * ============================================================================
949 */
950
951void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate) {
952 if (!filter)
953 return;
954
955 filter->cutoff_hz = cutoff_hz;
956 filter->sample_rate = sample_rate;
957
958 // Calculate filter coefficient using RC time constant formula
959 // alpha = dt / (RC + dt) where RC = 1 / (2 * pi * fc)
960 float dt = 1.0f / sample_rate;
961 float rc = 1.0f / (2.0f * (float)M_PI * cutoff_hz);
962 filter->alpha = dt / (rc + dt);
963
964 lowpass_filter_reset(filter);
965}
966
967void lowpass_filter_reset(lowpass_filter_t *filter) {
968 if (!filter)
969 return;
970
971 filter->prev_output = 0.0f;
972}
973
974float lowpass_filter_process_sample(lowpass_filter_t *filter, float input) {
975 if (!filter)
976 return input;
977
978 // First-order IIR low-pass filter: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
979 float output = filter->alpha * input + (1.0f - filter->alpha) * filter->prev_output;
980
981 filter->prev_output = output;
982
983 return output;
984}
985
986void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples) {
987 if (!filter || !buffer || num_samples <= 0)
988 return;
989
990 for (int i = 0; i < num_samples; i++) {
991 buffer[i] = lowpass_filter_process_sample(filter, buffer[i]);
992 }
993}
994
995/* ============================================================================
996 * Soft Clipping Implementation
997 * ============================================================================
998 */
999
1000float soft_clip(float sample, float threshold, float steepness) {
1001 if (sample > threshold) {
1002 // Soft clip positive values using tanh curve
1003 // Maps samples above threshold asymptotically toward 1.0
1004 return threshold + (1.0f - threshold) * tanhf((sample - threshold) * steepness);
1005 }
1006 if (sample < -threshold) {
1007 // Soft clip negative values symmetrically
1008 return -threshold + (-1.0f + threshold) * tanhf((sample + threshold) * steepness);
1009 }
1010 return sample;
1011}
1012
1013void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness) {
1014 if (!buffer || num_samples <= 0)
1015 return;
1016
1017 for (int i = 0; i < num_samples; i++) {
1018 buffer[i] = soft_clip(buffer[i], threshold, steepness);
1019 }
1020}
1021
1022/* ============================================================================
1023 * Buffer Utility Functions
1024 * ============================================================================
1025 */
1026
1027float smoothstep(float t) {
1028 if (t <= 0.0f)
1029 return 0.0f;
1030 if (t >= 1.0f)
1031 return 1.0f;
1032 return t * t * (3.0f - 2.0f * t);
1033}
1034
1035int16_t float_to_int16(float sample) {
1036 // Clamp to [-1, 1] then scale to int16 range
1037 if (sample > 1.0f)
1038 sample = 1.0f;
1039 if (sample < -1.0f)
1040 sample = -1.0f;
1041 return (int16_t)(sample * 32767.0f);
1042}
1043
1044float int16_to_float(int16_t sample) {
1045 return (float)sample / 32768.0f;
1046}
1047
1048void buffer_float_to_int16(const float *src, int16_t *dst, int count) {
1049 if (!src || !dst || count <= 0)
1050 return;
1051 for (int i = 0; i < count; i++) {
1052 dst[i] = float_to_int16(src[i]);
1053 }
1054}
1055
1056void buffer_int16_to_float(const int16_t *src, float *dst, int count) {
1057 if (!src || !dst || count <= 0)
1058 return;
1059 for (int i = 0; i < count; i++) {
1060 dst[i] = int16_to_float(src[i]);
1061 }
1062}
1063
1064float buffer_peak(const float *buffer, int count) {
1065 if (!buffer || count <= 0)
1066 return 0.0f;
1067
1068 float peak = 0.0f;
1069 for (int i = 0; i < count; i++) {
1070 float abs_sample = fabsf(buffer[i]);
1071 if (abs_sample > peak)
1072 peak = abs_sample;
1073 }
1074 return peak;
1075}
1076
1077void apply_gain_buffer(float *buffer, int count, float gain) {
1078 if (!buffer || count <= 0)
1079 return;
1080
1081 for (int i = 0; i < count; i++) {
1082 buffer[i] *= gain;
1083 }
1084}
1085
1086void fade_buffer(float *buffer, int count, float start_gain, float end_gain) {
1087 if (!buffer || count <= 0)
1088 return;
1089
1090 float step = (end_gain - start_gain) / (float)count;
1091 float gain = start_gain;
1092 for (int i = 0; i < count; i++) {
1093 buffer[i] *= gain;
1094 gain += step;
1095 }
1096}
1097
1098void fade_buffer_smooth(float *buffer, int count, bool fade_in) {
1099 if (!buffer || count <= 0)
1100 return;
1101
1102 for (int i = 0; i < count; i++) {
1103 float t = (float)i / (float)(count - 1);
1104 float gain = smoothstep(fade_in ? t : (1.0f - t));
1105 buffer[i] *= gain;
1106 }
1107}
1108
1109void copy_buffer_with_gain(const float *src, float *dst, int count, float gain) {
1110 if (!src || !dst || count <= 0)
1111 return;
1112
1113 for (int i = 0; i < count; i++) {
1114 dst[i] = src[i] * gain;
1115 }
1116}
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
bool noise_gate_is_open(const noise_gate_t *gate)
Definition mixer.c:892
void copy_buffer_with_gain(const float *src, float *dst, int count, float gain)
Definition mixer.c:1109
void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples)
Definition mixer.c:873
void buffer_float_to_int16(const float *src, int16_t *dst, int count)
Definition mixer.c:1048
float clamp_float(float value, float min, float max)
Definition mixer.c:31
void highpass_filter_reset(highpass_filter_t *filter)
Definition mixer.c:915
void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness)
Definition mixer.c:1013
float highpass_filter_process_sample(highpass_filter_t *filter, float input)
Definition mixer.c:923
void apply_gain_buffer(float *buffer, int count, float gain)
Definition mixer.c:1077
void mixer_set_source_active(mixer_t *mixer, uint32_t client_id, bool active)
Definition mixer.c:433
void ducking_process_frame(ducking_t *duck, float *envelopes, float *gains, int num_sources)
Definition mixer.c:184
void noise_gate_init(noise_gate_t *gate, float sample_rate)
Definition mixer.c:816
void ducking_destroy(ducking_t *duck)
Definition mixer.c:166
float buffer_peak(const float *buffer, int count)
Definition mixer.c:1064
float soft_clip(float sample, float threshold, float steepness)
Definition mixer.c:1000
void fade_buffer(float *buffer, int count, float start_gain, float end_gain)
Definition mixer.c:1086
void compressor_init(compressor_t *comp, float sample_rate)
Definition mixer.c:42
void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, uint64_t attack_ns, uint64_t release_ns, float makeup_dB)
Definition mixer.c:53
int ducking_init(ducking_t *duck, int num_sources, float sample_rate)
Definition mixer.c:111
void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate)
Definition mixer.c:901
float int16_to_float(int16_t sample)
Definition mixer.c:1044
float smoothstep(float t)
Definition mixer.c:1027
void fade_buffer_smooth(float *buffer, int count, bool fade_in)
Definition mixer.c:1098
int mixer_add_source(mixer_t *mixer, uint32_t client_id, audio_ring_buffer_t *buffer)
Definition mixer.c:364
int16_t float_to_int16(float sample)
Definition mixer.c:1035
void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate)
Definition mixer.c:951
mixer_t * mixer_create(int max_sources, int sample_rate)
Definition mixer.c:218
int mixer_process_excluding_source(mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id)
Definition mixer.c:605
float db_to_linear(float db)
Definition mixer.c:23
void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples)
Definition mixer.c:986
void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples)
Definition mixer.c:937
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)
Definition mixer.c:175
float compressor_process_sample(compressor_t *comp, float sidechain)
Definition mixer.c:87
void mixer_destroy(mixer_t *mixer)
Definition mixer.c:347
float lowpass_filter_process_sample(lowpass_filter_t *filter, float input)
Definition mixer.c:974
void noise_gate_set_params(noise_gate_t *gate, float threshold, uint64_t attack_ns, uint64_t release_ns, float hysteresis)
Definition mixer.c:830
float linear_to_db(float linear)
Definition mixer.c:27
#define M_PI
Definition mixer.c:19
void lowpass_filter_reset(lowpass_filter_t *filter)
Definition mixer.c:967
int mixer_process(mixer_t *mixer, float *output, int num_samples)
Definition mixer.c:460
void mixer_remove_source(mixer_t *mixer, uint32_t client_id)
Definition mixer.c:401
void buffer_int16_to_float(const int16_t *src, float *dst, int count)
Definition mixer.c:1056
float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude)
Definition mixer.c:848
int rwlock_init(rwlock_t *rwlock)
Definition threading.c:63
uint64_t time_get_ns(void)
Definition util/time.c:48
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Definition util/time.c:90