ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
effects.c
Go to the documentation of this file.
3#include <math.h>
4
5#ifndef M_PI
6#define M_PI 3.14159265358979323846
7#endif
8
9// Utility functions
10float db_to_linear(float db) {
11 return powf(10.0f, db / 20.0f);
12}
13
14float linear_to_db(float linear) {
15 return 20.0f * log10f(fmaxf(linear, 1e-12f));
16}
17
18float clamp_float(float value, float min, float max) {
19 if (value < min) {
20 return min;
21 }
22 if (value > max) {
23 return max;
24 }
25 return value;
26}
27
28// Compressor implementation
29void compressor_init(compressor_t *comp, float sample_rate) {
30 comp->sample_rate = sample_rate;
31 comp->envelope = 0.0f;
32 comp->gain_lin = 1.0f;
33
34 // Set default parameters with 0dB makeup gain (unity)
35 // Soft clip at 0.7 provides 3dB headroom to prevent hard clipping
36 // Ducking and crowd scaling naturally reduce volume, no compensation needed
37 compressor_set_params(comp, -10.0f, 4.0f, 10 * NS_PER_MS_INT, 100 * NS_PER_MS_INT, 0.0f);
38}
39
40void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, uint64_t attack_ns, uint64_t release_ns,
41 float makeup_dB) {
42 comp->threshold_dB = threshold_dB;
43 comp->ratio = ratio;
44 comp->attack_ns = attack_ns;
45 comp->release_ns = release_ns;
46 comp->makeup_dB = makeup_dB;
47 comp->knee_dB = 2.0f; // Fixed soft knee
48
49 // Calculate time constants
50 float attack_tau = (float)attack_ns / NS_PER_SEC;
51 float release_tau = (float)release_ns / NS_PER_SEC;
52 comp->attack_coeff = expf(-1.0f / (attack_tau * comp->sample_rate + 1e-12f));
53 comp->release_coeff = expf(-1.0f / (release_tau * comp->sample_rate + 1e-12f));
54}
55
56static float compressor_gain_reduction_db(const compressor_t *comp, float level_dB) {
57 float over = level_dB - comp->threshold_dB;
58 float knee = comp->knee_dB;
59
60 if (knee > 0.0f) {
61 if (over <= -knee * 0.5f)
62 return 0.0f;
63 if (over >= knee * 0.5f)
64 return (1.0f / comp->ratio - 1.0f) * over;
65 float x = over + knee * 0.5f;
66 return (1.0f / comp->ratio - 1.0f) * (x * x) / (2.0f * knee);
67 }
68 if (over <= 0.0f) {
69 return 0.0f;
70 }
71 return (1.0f / comp->ratio - 1.0f) * over;
72}
73
74float compressor_process_sample(compressor_t *comp, float sidechain) {
75 float x = fabsf(sidechain);
76
77 // Update envelope with attack/release
78 if (x > comp->envelope)
79 comp->envelope = comp->attack_coeff * comp->envelope + (1.0f - comp->attack_coeff) * x;
80 else
81 comp->envelope = comp->release_coeff * comp->envelope + (1.0f - comp->release_coeff) * x;
82
83 // Calculate gain reduction
84 float level_dB = linear_to_db(comp->envelope);
85 float gr_dB = compressor_gain_reduction_db(comp, level_dB);
86 float target_lin = db_to_linear(gr_dB + comp->makeup_dB);
87
88 // Smooth gain changes
89 if (target_lin < comp->gain_lin)
90 comp->gain_lin = comp->attack_coeff * comp->gain_lin + (1.0f - comp->attack_coeff) * target_lin;
91 else
92 comp->gain_lin = comp->release_coeff * comp->gain_lin + (1.0f - comp->release_coeff) * target_lin;
93
94 return comp->gain_lin;
95}
96
97/* ============================================================================
98 * Noise Gate Implementation
99 * ============================================================================
100 */
101
102void noise_gate_init(noise_gate_t *gate, float sample_rate) {
103 if (!gate)
104 return;
105
106 gate->sample_rate = sample_rate;
107 gate->envelope = 0.0f;
108 gate->gate_open = false;
109
110 // Default parameters
111 // Slower attack (10ms) prevents clicking artifacts during gate transitions
112 // threshold=0.01, attack=10ms, release=50ms, hysteresis=0.9
113 noise_gate_set_params(gate, 0.01f, 10 * NS_PER_MS_INT, 50 * NS_PER_MS_INT, 0.9f);
114}
115
116void noise_gate_set_params(noise_gate_t *gate, float threshold, uint64_t attack_ns, uint64_t release_ns,
117 float hysteresis) {
118 if (!gate)
119 return;
120
121 gate->threshold = threshold;
122 gate->attack_ns = attack_ns;
123 gate->release_ns = release_ns;
124 gate->hysteresis = hysteresis;
125
126 // Calculate coefficients for envelope follower
127 // Using exponential moving average: coeff = 1 - exp(-1 / (time_s * sample_rate))
128 float attack_s = (float)attack_ns / NS_PER_SEC;
129 float release_s = (float)release_ns / NS_PER_SEC;
130 gate->attack_coeff = 1.0f - expf(-1.0f / (attack_s * gate->sample_rate + 1e-12f));
131 gate->release_coeff = 1.0f - expf(-1.0f / (release_s * gate->sample_rate + 1e-12f));
132}
133
134float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude) {
135 if (!gate)
136 return input;
137
138 // Determine target state with hysteresis
139 float target;
140 if (gate->gate_open) {
141 // Gate is open - use lower threshold (hysteresis) to close
142 target = (peak_amplitude > gate->threshold * gate->hysteresis) ? 1.0f : 0.0f;
143 } else {
144 // Gate is closed - use normal threshold to open
145 target = (peak_amplitude > gate->threshold) ? 1.0f : 0.0f;
146 }
147
148 // Update gate state
149 gate->gate_open = (target > 0.5f);
150
151 // Update envelope with appropriate coefficient
152 float coeff = (target > gate->envelope) ? gate->attack_coeff : gate->release_coeff;
153 gate->envelope += coeff * (target - gate->envelope);
154
155 // Apply gate
156 return input * gate->envelope;
157}
158
159void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples) {
160 if (!gate || !buffer || num_samples <= 0)
161 return;
162
163 // First pass: find peak amplitude
164 float peak = 0.0f;
165 for (int i = 0; i < num_samples; i++) {
166 float abs_sample = fabsf(buffer[i]);
167 if (abs_sample > peak) {
168 peak = abs_sample;
169 }
170 }
171
172 // Second pass: apply gate
173 for (int i = 0; i < num_samples; i++) {
174 buffer[i] = noise_gate_process_sample(gate, buffer[i], peak);
175 }
176}
177
179 return gate ? gate->gate_open : false;
180}
181
182/* ============================================================================
183 * High-Pass Filter Implementation
184 * ============================================================================
185 */
186
187void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate) {
188 if (!filter)
189 return;
190
191 filter->cutoff_hz = cutoff_hz;
192 filter->sample_rate = sample_rate;
193
194 // Calculate filter coefficient
195 // alpha = 1 / (1 + 2*pi*fc/fs)
196 filter->alpha = 1.0f / (1.0f + 2.0f * M_PI * cutoff_hz / sample_rate);
197
198 highpass_filter_reset(filter);
199}
200
202 if (!filter)
203 return;
204
205 filter->prev_input = 0.0f;
206 filter->prev_output = 0.0f;
207}
208
210 if (!filter)
211 return input;
212
213 // First-order high-pass filter
214 // y[n] = alpha * (y[n-1] + x[n] - x[n-1])
215 float output = filter->alpha * (filter->prev_output + input - filter->prev_input);
216
217 filter->prev_input = input;
218 filter->prev_output = output;
219
220 return output;
221}
222
223void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples) {
224 if (!filter || !buffer || num_samples <= 0)
225 return;
226
227 for (int i = 0; i < num_samples; i++) {
228 buffer[i] = highpass_filter_process_sample(filter, buffer[i]);
229 }
230}
231
232/* ============================================================================
233 * Low-Pass Filter Implementation
234 * ============================================================================
235 */
236
237void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate) {
238 if (!filter)
239 return;
240
241 filter->cutoff_hz = cutoff_hz;
242 filter->sample_rate = sample_rate;
243
244 // Calculate filter coefficient using RC time constant formula
245 // alpha = dt / (RC + dt) where RC = 1 / (2 * pi * fc)
246 float dt = 1.0f / sample_rate;
247 float rc = 1.0f / (2.0f * (float)M_PI * cutoff_hz);
248 filter->alpha = dt / (rc + dt);
249
250 lowpass_filter_reset(filter);
251}
252
254 if (!filter)
255 return;
256
257 filter->prev_output = 0.0f;
258}
259
261 if (!filter)
262 return input;
263
264 // First-order IIR low-pass filter: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
265 float output = filter->alpha * input + (1.0f - filter->alpha) * filter->prev_output;
266
267 filter->prev_output = output;
268
269 return output;
270}
271
272void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples) {
273 if (!filter || !buffer || num_samples <= 0)
274 return;
275
276 for (int i = 0; i < num_samples; i++) {
277 buffer[i] = lowpass_filter_process_sample(filter, buffer[i]);
278 }
279}
280
281/* ============================================================================
282 * Soft Clipping Implementation
283 * ============================================================================
284 */
285
286float soft_clip(float sample, float threshold, float steepness) {
287 if (sample > threshold) {
288 // Soft clip positive values using tanh curve
289 // Maps samples above threshold asymptotically toward 1.0
290 return threshold + (1.0f - threshold) * tanhf((sample - threshold) * steepness);
291 }
292 if (sample < -threshold) {
293 // Soft clip negative values symmetrically
294 return -threshold + (-1.0f + threshold) * tanhf((sample + threshold) * steepness);
295 }
296 return sample;
297}
298
299void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness) {
300 if (!buffer || num_samples <= 0)
301 return;
302
303 for (int i = 0; i < num_samples; i++) {
304 buffer[i] = soft_clip(buffer[i], threshold, steepness);
305 }
306}
307
308/* ============================================================================
309 * Buffer Utility Functions
310 * ============================================================================
311 */
312
313float smoothstep(float t) {
314 if (t <= 0.0f)
315 return 0.0f;
316 if (t >= 1.0f)
317 return 1.0f;
318 return t * t * (3.0f - 2.0f * t);
319}
320
321int16_t float_to_int16(float sample) {
322 // Clamp to [-1, 1] then scale to int16 range
323 if (sample > 1.0f)
324 sample = 1.0f;
325 if (sample < -1.0f)
326 sample = -1.0f;
327 return (int16_t)(sample * 32767.0f);
328}
329
330float int16_to_float(int16_t sample) {
331 return (float)sample / 32768.0f;
332}
333
334void buffer_float_to_int16(const float *src, int16_t *dst, int count) {
335 if (!src || !dst || count <= 0)
336 return;
337 for (int i = 0; i < count; i++) {
338 dst[i] = float_to_int16(src[i]);
339 }
340}
341
342void buffer_int16_to_float(const int16_t *src, float *dst, int count) {
343 if (!src || !dst || count <= 0)
344 return;
345 for (int i = 0; i < count; i++) {
346 dst[i] = int16_to_float(src[i]);
347 }
348}
349
350float buffer_peak(const float *buffer, int count) {
351 if (!buffer || count <= 0)
352 return 0.0f;
353
354 float peak = 0.0f;
355 for (int i = 0; i < count; i++) {
356 float abs_sample = fabsf(buffer[i]);
357 if (abs_sample > peak)
358 peak = abs_sample;
359 }
360 return peak;
361}
362
363void apply_gain_buffer(float *buffer, int count, float gain) {
364 if (!buffer || count <= 0)
365 return;
366
367 for (int i = 0; i < count; i++) {
368 buffer[i] *= gain;
369 }
370}
371
372void fade_buffer(float *buffer, int count, float start_gain, float end_gain) {
373 if (!buffer || count <= 0)
374 return;
375
376 float step = (end_gain - start_gain) / (float)count;
377 float gain = start_gain;
378 for (int i = 0; i < count; i++) {
379 buffer[i] *= gain;
380 gain += step;
381 }
382}
383
384void fade_buffer_smooth(float *buffer, int count, bool fade_in) {
385 if (!buffer || count <= 0)
386 return;
387
388 for (int i = 0; i < count; i++) {
389 float t = (float)i / (float)(count - 1);
390 float gain = smoothstep(fade_in ? t : (1.0f - t));
391 buffer[i] *= gain;
392 }
393}
394
395void copy_buffer_with_gain(const float *src, float *dst, int count, float gain) {
396 if (!src || !dst || count <= 0)
397 return;
398
399 for (int i = 0; i < count; i++) {
400 dst[i] = src[i] * gain;
401 }
402}
#define M_PI
Definition effects.c:6
float hysteresis
Hysteresis factor (0-1, prevents gate chatter).
Definition mixer.h:191
float release_coeff
Release coefficient (converted from release_ms).
Definition mixer.h:200
bool noise_gate_is_open(const noise_gate_t *gate)
Check if noise gate is currently open.
Definition effects.c:178
void copy_buffer_with_gain(const float *src, float *dst, int count, float gain)
Copy buffer with gain scaling.
Definition effects.c:395
void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples)
Process a buffer of samples through noise gate.
Definition effects.c:159
void buffer_float_to_int16(const float *src, int16_t *dst, int count)
Convert float buffer to int16 buffer.
Definition effects.c:334
float alpha
Filter coefficient alpha (calculated from cutoff_hz).
Definition mixer.h:229
float clamp_float(float value, float min, float max)
Clamp a float value to a range.
Definition effects.c:18
float ratio
Compression ratio (e.g., 4.0 for 4:1 compression).
Definition mixer.h:145
void highpass_filter_reset(highpass_filter_t *filter)
Reset high-pass filter state.
Definition effects.c:201
float sample_rate
Sample rate in Hz (set during initialization).
Definition mixer.h:226
void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness)
Apply soft clipping to a buffer.
Definition effects.c:299
float prev_input
Previous input sample (filter state).
Definition mixer.h:231
float prev_output
Previous output sample (filter state).
Definition mixer.h:256
float highpass_filter_process_sample(highpass_filter_t *filter, float input)
Process a single sample through high-pass filter.
Definition effects.c:209
float envelope
Current envelope follower state (linear, 0-1).
Definition mixer.h:156
float cutoff_hz
Cutoff frequency in Hz (frequencies above this are attenuated).
Definition mixer.h:249
void apply_gain_buffer(float *buffer, int count, float gain)
Apply gain to buffer in-place.
Definition effects.c:363
void noise_gate_init(noise_gate_t *gate, float sample_rate)
Initialize a noise gate.
Definition effects.c:102
float buffer_peak(const float *buffer, int count)
Find peak absolute value in buffer.
Definition effects.c:350
float cutoff_hz
Cutoff frequency in Hz (frequencies below this are attenuated).
Definition mixer.h:224
float soft_clip(float sample, float threshold, float steepness)
Apply soft clipping to a sample.
Definition effects.c:286
float sample_rate
Sample rate in Hz (set during initialization).
Definition mixer.h:194
float knee_dB
Knee width in dB for soft knee (e.g., 2.0).
Definition mixer.h:143
void fade_buffer(float *buffer, int count, float start_gain, float end_gain)
Apply linear fade to buffer in-place.
Definition effects.c:372
void compressor_init(compressor_t *comp, float sample_rate)
Initialize a compressor.
Definition effects.c:29
float alpha
Filter coefficient alpha (calculated from cutoff_hz).
Definition mixer.h:254
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 effects.c:40
uint64_t attack_ns
Attack time in nanoseconds (how fast gate opens).
Definition mixer.h:187
void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a high-pass filter.
Definition effects.c:187
float int16_to_float(int16_t sample)
Convert int16 sample to float.
Definition effects.c:330
float threshold_dB
Compression threshold in dB (e.g., -10.0).
Definition mixer.h:141
float envelope
Current envelope follower state (linear, 0-1).
Definition mixer.h:196
float sample_rate
Sample rate in Hz (set during initialization).
Definition mixer.h:154
bool gate_open
True if gate is currently open (allowing audio through).
Definition mixer.h:202
float smoothstep(float t)
Compute smoothstep interpolation.
Definition effects.c:313
void fade_buffer_smooth(float *buffer, int count, bool fade_in)
Apply smoothstep fade to buffer in-place.
Definition effects.c:384
int16_t float_to_int16(float sample)
Convert float sample to int16 (WebRTC format).
Definition effects.c:321
float sample_rate
Sample rate in Hz (set during initialization).
Definition mixer.h:251
void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a low-pass filter.
Definition effects.c:237
float db_to_linear(float db)
Convert decibels to linear gain.
Definition effects.c:10
void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through low-pass filter.
Definition effects.c:272
uint64_t release_ns
Release time in nanoseconds (how fast gate closes).
Definition mixer.h:189
void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through high-pass filter.
Definition effects.c:223
float makeup_dB
Makeup gain in dB (compensates for gain reduction).
Definition mixer.h:151
float compressor_process_sample(compressor_t *comp, float sidechain)
Process a single sample through compressor.
Definition effects.c:74
uint64_t attack_ns
Attack time in nanoseconds (how fast compression kicks in).
Definition mixer.h:147
float lowpass_filter_process_sample(lowpass_filter_t *filter, float input)
Process a single sample through low-pass filter.
Definition effects.c:260
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 effects.c:116
float linear_to_db(float linear)
Convert linear gain to decibels.
Definition effects.c:14
uint64_t release_ns
Release time in nanoseconds (how fast compression releases).
Definition mixer.h:149
void lowpass_filter_reset(lowpass_filter_t *filter)
Reset low-pass filter state.
Definition effects.c:253
float attack_coeff
Attack coefficient (converted from attack_ms).
Definition mixer.h:160
void buffer_int16_to_float(const int16_t *src, float *dst, int count)
Convert int16 buffer to float buffer.
Definition effects.c:342
float threshold
Gate threshold in linear units (e.g., 0.01f for -40dB).
Definition mixer.h:185
float prev_output
Previous output sample (filter state).
Definition mixer.h:233
float release_coeff
Release coefficient (converted from release_ms).
Definition mixer.h:162
float gain_lin
Current gain multiplier (linear, calculated from envelope).
Definition mixer.h:158
float attack_coeff
Attack coefficient (converted from attack_ms).
Definition mixer.h:198
float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude)
Process a single sample through noise gate.
Definition effects.c:134
unsigned long long uint64_t
Definition common.h:63
#define NS_PER_MS_INT
Definition time.h:168
#define NS_PER_SEC
Definition time.h:160
🔢 Mathematical Utility Functions
Multi-Source Audio Mixing and Processing System.
Dynamic range compressor settings and state.
Definition mixer.h:139
noise gate keeps the values that move through the subsystem: threshold, attack_ns,...
Definition mixer.h:222
This is the handoff point for highpass filter. Callers fill cutoff_hz, sample_rate,...
Definition mixer.h:247
This is the handoff point for compressor. Callers fill threshold_dB, knee_dB, ratio,...
Definition mixer.h:183
⏱️ High-precision timing utilities using sokol_time.h and uthash