ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
mixer.c File Reference

🎚️ Real-time audio mixer with ducking, gain control, and multi-stream blending More...

Go to the source code of this file.

Macros

#define M_PI   3.14159265358979323846
 

Functions

float db_to_linear (float db)
 
float linear_to_db (float linear)
 
float clamp_float (float value, float min, float max)
 
void compressor_init (compressor_t *comp, float sample_rate)
 
void compressor_set_params (compressor_t *comp, float threshold_dB, float ratio, uint64_t attack_ns, uint64_t release_ns, float makeup_dB)
 
float compressor_process_sample (compressor_t *comp, float sidechain)
 
int ducking_init (ducking_t *duck, int num_sources, float sample_rate)
 
void ducking_destroy (ducking_t *duck)
 
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)
 
void ducking_process_frame (ducking_t *duck, float *envelopes, float *gains, int num_sources)
 
mixer_t * mixer_create (int max_sources, int sample_rate)
 
void mixer_destroy (mixer_t *mixer)
 
int mixer_add_source (mixer_t *mixer, uint32_t client_id, audio_ring_buffer_t *buffer)
 
void mixer_remove_source (mixer_t *mixer, uint32_t client_id)
 
void mixer_set_source_active (mixer_t *mixer, uint32_t client_id, bool active)
 
int mixer_process (mixer_t *mixer, float *output, int num_samples)
 
int mixer_process_excluding_source (mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id)
 
void noise_gate_init (noise_gate_t *gate, float sample_rate)
 
void noise_gate_set_params (noise_gate_t *gate, float threshold, uint64_t attack_ns, uint64_t release_ns, float hysteresis)
 
float noise_gate_process_sample (noise_gate_t *gate, float input, float peak_amplitude)
 
void noise_gate_process_buffer (noise_gate_t *gate, float *buffer, int num_samples)
 
bool noise_gate_is_open (const noise_gate_t *gate)
 
void highpass_filter_init (highpass_filter_t *filter, float cutoff_hz, float sample_rate)
 
void highpass_filter_reset (highpass_filter_t *filter)
 
float highpass_filter_process_sample (highpass_filter_t *filter, float input)
 
void highpass_filter_process_buffer (highpass_filter_t *filter, float *buffer, int num_samples)
 
void lowpass_filter_init (lowpass_filter_t *filter, float cutoff_hz, float sample_rate)
 
void lowpass_filter_reset (lowpass_filter_t *filter)
 
float lowpass_filter_process_sample (lowpass_filter_t *filter, float input)
 
void lowpass_filter_process_buffer (lowpass_filter_t *filter, float *buffer, int num_samples)
 
float soft_clip (float sample, float threshold, float steepness)
 
void soft_clip_buffer (float *buffer, int num_samples, float threshold, float steepness)
 
float smoothstep (float t)
 
int16_t float_to_int16 (float sample)
 
float int16_to_float (int16_t sample)
 
void buffer_float_to_int16 (const float *src, int16_t *dst, int count)
 
void buffer_int16_to_float (const int16_t *src, float *dst, int count)
 
float buffer_peak (const float *buffer, int count)
 
void apply_gain_buffer (float *buffer, int count, float gain)
 
void fade_buffer (float *buffer, int count, float start_gain, float end_gain)
 
void fade_buffer_smooth (float *buffer, int count, bool fade_in)
 
void copy_buffer_with_gain (const float *src, float *dst, int count, float gain)
 

Detailed Description

🎚️ Real-time audio mixer with ducking, gain control, and multi-stream blending

Definition in file mixer.c.

Macro Definition Documentation

◆ M_PI

#define M_PI   3.14159265358979323846

Definition at line 19 of file mixer.c.

Function Documentation

◆ apply_gain_buffer()

void apply_gain_buffer ( float *  buffer,
int  count,
float  gain 
)

Definition at line 1077 of file mixer.c.

1077 {
1078 if (!buffer || count <= 0)
1079 return;
1080
1081 for (int i = 0; i < count; i++) {
1082 buffer[i] *= gain;
1083 }
1084}

◆ buffer_float_to_int16()

void buffer_float_to_int16 ( const float *  src,
int16_t *  dst,
int  count 
)

Definition at line 1048 of file mixer.c.

1048 {
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}
int16_t float_to_int16(float sample)
Definition mixer.c:1035

References float_to_int16().

◆ buffer_int16_to_float()

void buffer_int16_to_float ( const int16_t *  src,
float *  dst,
int  count 
)

Definition at line 1056 of file mixer.c.

1056 {
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}
float int16_to_float(int16_t sample)
Definition mixer.c:1044

References int16_to_float().

◆ buffer_peak()

float buffer_peak ( const float *  buffer,
int  count 
)

Definition at line 1064 of file mixer.c.

1064 {
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}

◆ clamp_float()

float clamp_float ( float  value,
float  min,
float  max 
)

Definition at line 31 of file mixer.c.

31 {
32 if (value < min) {
33 return min;
34 }
35 if (value > max) {
36 return max;
37 }
38 return value;
39}

◆ compressor_init()

void compressor_init ( compressor_t *  comp,
float  sample_rate 
)

Definition at line 42 of file mixer.c.

42 {
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}
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

References compressor_set_params().

Referenced by client_audio_pipeline_create(), and mixer_create().

◆ compressor_process_sample()

float compressor_process_sample ( compressor_t *  comp,
float  sidechain 
)

Definition at line 87 of file mixer.c.

87 {
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}
float db_to_linear(float db)
Definition mixer.c:23
float linear_to_db(float linear)
Definition mixer.c:27

References db_to_linear(), and linear_to_db().

Referenced by client_audio_pipeline_process_duplex(), mixer_process(), and mixer_process_excluding_source().

◆ compressor_set_params()

void compressor_set_params ( compressor_t *  comp,
float  threshold_dB,
float  ratio,
uint64_t  attack_ns,
uint64_t  release_ns,
float  makeup_dB 
)

Definition at line 53 of file mixer.c.

54 {
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}

Referenced by client_audio_pipeline_create(), and compressor_init().

◆ copy_buffer_with_gain()

void copy_buffer_with_gain ( const float *  src,
float *  dst,
int  count,
float  gain 
)

Definition at line 1109 of file mixer.c.

1109 {
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}

Referenced by client_audio_pipeline_process_duplex().

◆ db_to_linear()

float db_to_linear ( float  db)

Definition at line 23 of file mixer.c.

23 {
24 return powf(10.0f, db / 20.0f);
25}

Referenced by compressor_process_sample(), ducking_process_frame(), mixer_process(), and mixer_process_excluding_source().

◆ ducking_destroy()

void ducking_destroy ( ducking_t *  duck)

Definition at line 166 of file mixer.c.

166 {
167 if (duck->envelope) {
168 SAFE_FREE(duck->envelope);
169 }
170 if (duck->gain) {
171 SAFE_FREE(duck->gain);
172 }
173}

Referenced by mixer_destroy().

◆ ducking_init()

int ducking_init ( ducking_t *  duck,
int  num_sources,
float  sample_rate 
)

Definition at line 111 of file mixer.c.

111 {
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}

Referenced by mixer_create().

◆ ducking_process_frame()

void ducking_process_frame ( ducking_t *  duck,
float *  envelopes,
float *  gains,
int  num_sources 
)

Definition at line 184 of file mixer.c.

184 {
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}

References db_to_linear(), and linear_to_db().

Referenced by mixer_process(), and mixer_process_excluding_source().

◆ ducking_set_params()

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 at line 175 of file mixer.c.

176 {
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}

◆ fade_buffer()

void fade_buffer ( float *  buffer,
int  count,
float  start_gain,
float  end_gain 
)

Definition at line 1086 of file mixer.c.

1086 {
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}

◆ fade_buffer_smooth()

void fade_buffer_smooth ( float *  buffer,
int  count,
bool  fade_in 
)

Definition at line 1098 of file mixer.c.

1098 {
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}
float smoothstep(float t)
Definition mixer.c:1027

References smoothstep().

◆ float_to_int16()

int16_t float_to_int16 ( float  sample)

Definition at line 1035 of file mixer.c.

1035 {
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}

Referenced by buffer_float_to_int16().

◆ highpass_filter_init()

void highpass_filter_init ( highpass_filter_t *  filter,
float  cutoff_hz,
float  sample_rate 
)

Definition at line 901 of file mixer.c.

901 {
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}
void highpass_filter_reset(highpass_filter_t *filter)
Definition mixer.c:915
#define M_PI
Definition mixer.c:19

References highpass_filter_reset(), and M_PI.

Referenced by client_audio_pipeline_create().

◆ highpass_filter_process_buffer()

void highpass_filter_process_buffer ( highpass_filter_t *  filter,
float *  buffer,
int  num_samples 
)

Definition at line 937 of file mixer.c.

937 {
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}
float highpass_filter_process_sample(highpass_filter_t *filter, float input)
Definition mixer.c:923

References highpass_filter_process_sample().

Referenced by client_audio_pipeline_process_duplex().

◆ highpass_filter_process_sample()

float highpass_filter_process_sample ( highpass_filter_t *  filter,
float  input 
)

Definition at line 923 of file mixer.c.

923 {
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}

Referenced by highpass_filter_process_buffer().

◆ highpass_filter_reset()

void highpass_filter_reset ( highpass_filter_t *  filter)

Definition at line 915 of file mixer.c.

915 {
916 if (!filter)
917 return;
918
919 filter->prev_input = 0.0f;
920 filter->prev_output = 0.0f;
921}

Referenced by highpass_filter_init().

◆ int16_to_float()

float int16_to_float ( int16_t  sample)

Definition at line 1044 of file mixer.c.

1044 {
1045 return (float)sample / 32768.0f;
1046}

Referenced by buffer_int16_to_float().

◆ linear_to_db()

float linear_to_db ( float  linear)

Definition at line 27 of file mixer.c.

27 {
28 return 20.0f * log10f(fmaxf(linear, 1e-12f));
29}

Referenced by compressor_process_sample(), and ducking_process_frame().

◆ lowpass_filter_init()

void lowpass_filter_init ( lowpass_filter_t *  filter,
float  cutoff_hz,
float  sample_rate 
)

Definition at line 951 of file mixer.c.

951 {
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}
void lowpass_filter_reset(lowpass_filter_t *filter)
Definition mixer.c:967

References lowpass_filter_reset(), and M_PI.

Referenced by client_audio_pipeline_create().

◆ lowpass_filter_process_buffer()

void lowpass_filter_process_buffer ( lowpass_filter_t *  filter,
float *  buffer,
int  num_samples 
)

Definition at line 986 of file mixer.c.

986 {
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}
float lowpass_filter_process_sample(lowpass_filter_t *filter, float input)
Definition mixer.c:974

References lowpass_filter_process_sample().

Referenced by client_audio_pipeline_process_duplex().

◆ lowpass_filter_process_sample()

float lowpass_filter_process_sample ( lowpass_filter_t *  filter,
float  input 
)

Definition at line 974 of file mixer.c.

974 {
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}

Referenced by lowpass_filter_process_buffer().

◆ lowpass_filter_reset()

void lowpass_filter_reset ( lowpass_filter_t *  filter)

Definition at line 967 of file mixer.c.

967 {
968 if (!filter)
969 return;
970
971 filter->prev_output = 0.0f;
972}

Referenced by lowpass_filter_init().

◆ mixer_add_source()

int mixer_add_source ( mixer_t *  mixer,
uint32_t  client_id,
audio_ring_buffer_t *  buffer 
)

Definition at line 364 of file mixer.c.

364 {
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}

Referenced by add_client(), and add_webrtc_client().

◆ mixer_create()

mixer_t * mixer_create ( int  max_sources,
int  sample_rate 
)

Definition at line 218 of file mixer.c.

218 {
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}
void compressor_init(compressor_t *comp, float sample_rate)
Definition mixer.c:42
int ducking_init(ducking_t *duck, int num_sources, float sample_rate)
Definition mixer.c:111
int rwlock_init(rwlock_t *rwlock)
Definition threading.c:63

References compressor_init(), ducking_init(), and rwlock_init().

Referenced by server_main().

◆ mixer_destroy()

void mixer_destroy ( mixer_t *  mixer)

Definition at line 347 of file mixer.c.

347 {
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}
void ducking_destroy(ducking_t *duck)
Definition mixer.c:166

References ducking_destroy().

Referenced by server_main().

◆ mixer_process()

int mixer_process ( mixer_t *  mixer,
float *  output,
int  num_samples 
)

Definition at line 460 of file mixer.c.

460 {
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}
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
void ducking_process_frame(ducking_t *duck, float *envelopes, float *gains, int num_sources)
Definition mixer.c:184
float soft_clip(float sample, float threshold, float steepness)
Definition mixer.c:1000
float compressor_process_sample(compressor_t *comp, float sidechain)
Definition mixer.c:87

References audio_ring_buffer_read(), compressor_process_sample(), db_to_linear(), ducking_process_frame(), and soft_clip().

◆ mixer_process_excluding_source()

int mixer_process_excluding_source ( mixer_t *  mixer,
float *  output,
int  num_samples,
uint32_t  exclude_client_id 
)

Definition at line 605 of file mixer.c.

605 {
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}
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

References audio_ring_buffer_read(), compressor_process_sample(), db_to_linear(), ducking_process_frame(), soft_clip(), time_elapsed_ns(), and time_get_ns().

Referenced by client_audio_render_thread().

◆ mixer_remove_source()

void mixer_remove_source ( mixer_t *  mixer,
uint32_t  client_id 
)

Definition at line 401 of file mixer.c.

401 {
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}

Referenced by remove_client().

◆ mixer_set_source_active()

void mixer_set_source_active ( mixer_t *  mixer,
uint32_t  client_id,
bool  active 
)

Definition at line 433 of file mixer.c.

433 {
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}

◆ noise_gate_init()

void noise_gate_init ( noise_gate_t *  gate,
float  sample_rate 
)

Definition at line 816 of file mixer.c.

816 {
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}
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

References noise_gate_set_params().

Referenced by client_audio_pipeline_create().

◆ noise_gate_is_open()

bool noise_gate_is_open ( const noise_gate_t *  gate)

Definition at line 892 of file mixer.c.

892 {
893 return gate ? gate->gate_open : false;
894}

◆ noise_gate_process_buffer()

void noise_gate_process_buffer ( noise_gate_t *  gate,
float *  buffer,
int  num_samples 
)

Definition at line 873 of file mixer.c.

873 {
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}
float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude)
Definition mixer.c:848

References noise_gate_process_sample().

Referenced by client_audio_pipeline_playback(), and client_audio_pipeline_process_duplex().

◆ noise_gate_process_sample()

float noise_gate_process_sample ( noise_gate_t *  gate,
float  input,
float  peak_amplitude 
)

Definition at line 848 of file mixer.c.

848 {
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}

Referenced by noise_gate_process_buffer().

◆ noise_gate_set_params()

void noise_gate_set_params ( noise_gate_t *  gate,
float  threshold,
uint64_t  attack_ns,
uint64_t  release_ns,
float  hysteresis 
)

Definition at line 830 of file mixer.c.

831 {
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}

Referenced by client_audio_pipeline_create(), and noise_gate_init().

◆ smoothstep()

float smoothstep ( float  t)

Definition at line 1027 of file mixer.c.

1027 {
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}

Referenced by client_audio_pipeline_process_duplex(), and fade_buffer_smooth().

◆ soft_clip()

float soft_clip ( float  sample,
float  threshold,
float  steepness 
)

Definition at line 1000 of file mixer.c.

1000 {
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}

Referenced by client_audio_pipeline_process_duplex(), mixer_process(), mixer_process_excluding_source(), and soft_clip_buffer().

◆ soft_clip_buffer()

void soft_clip_buffer ( float *  buffer,
int  num_samples,
float  threshold,
float  steepness 
)

Definition at line 1013 of file mixer.c.

1013 {
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}

References soft_clip().

Referenced by client_audio_pipeline_process_duplex().