19#define M_PI 3.14159265358979323846
24 return powf(10.0f, db / 20.0f);
28 return 20.0f * log10f(fmaxf(linear, 1e-12f));
63 float attack_tau = attack_ms / 1000.0f;
64 float release_tau = release_ms / 1000.0f;
69static float compressor_gain_reduction_db(
const compressor_t *comp,
float level_dB) {
74 if (over <= -knee * 0.5f)
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);
84 return (1.0f / comp->
ratio - 1.0f) * over;
88 float x = fabsf(sidechain);
98 float gr_dB = compressor_gain_reduction_db(comp, level_dB);
102 if (target_lin < comp->gain_lin)
129 float attack_tau = duck->
attack_ms / 1000.0f;
130 float release_tau = duck->
release_ms / 1000.0f;
131 duck->
attack_coeff = expf(-1.0f / (attack_tau * sample_rate + 1e-12f));
132 duck->
release_coeff = expf(-1.0f / (release_tau * sample_rate + 1e-12f));
135 size_t envelope_size = 0;
136 if (checked_size_mul((
size_t)num_sources,
sizeof(
float), &envelope_size) !=
ASCIICHAT_OK) {
144 size_t gain_size = 0;
145 if (checked_size_mul((
size_t)num_sources,
sizeof(
float), &gain_size) !=
ASCIICHAT_OK) {
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;
186 float max_dB = -120.0f;
189 for (
int i = 0; i < num_sources; i++) {
191 if (env_dB[i] > max_dB)
198 for (
int i = 0; i < num_sources; i++) {
200 bool is_leader = is_speaking && (env_dB[i] >= max_dB - duck->
leader_margin_dB);
203 if (is_speaking && !is_leader) {
210 if (target < gains[i])
225 if (sample_rate <= 0 || sample_rate > 192000) {
242 size_t buffers_size = 0;
268 size_t active_size = 0;
269 if (checked_size_mul((
size_t)max_sources,
sizeof(
bool), &active_size) !=
ASCIICHAT_OK) {
295 size_t mix_buffer_size = 0;
341 log_info(
"Audio mixer created: max_sources=%d, sample_rate=%d", max_sources, sample_rate);
364 if (!mixer || !buffer)
381 log_warn(
"Mixer: No available slots for client %u", client_id);
396 log_info(
"Mixer: Added source for client %u at slot %d", client_id, slot);
424 log_info(
"Mixer: Removed source for client %u from slot %d", client_id, i);
451 log_debug(
"Mixer: Set source %u active=%d", client_id, active);
460 if (!mixer || !output || num_samples <= 0)
468 SAFE_MEMSET(output, num_samples *
sizeof(
float), 0, num_samples *
sizeof(
float));
471 int active_count = 0;
478 if (active_count == 0) {
484 for (
int frame_start = 0; frame_start < num_samples; frame_start +=
MIXER_FRAME_SIZE) {
492 int source_count = 0;
499 size_t samples_read_size =
501 int samples_read = (int)samples_read_size;
505 if (samples_read > 0) {
507 if (samples_read < frame_size) {
508 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) *
sizeof(
float), 0,
509 (frame_size - samples_read) *
sizeof(
float));
512 source_map[source_count] = i;
523 int speaking_count = 0;
525 for (
int i = 0; i < source_count; i++) {
526 int slot = source_map[i];
530 for (
int s = 0; s < frame_size; s++) {
531 float abs_sample = fabsf(source_samples[i][s]);
532 if (abs_sample > peak)
554 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((
float)speaking_count, mixer->
crowd_alpha)) : 1.0f;
555 float pre_bus = mixer->
base_gain * crowd_gain;
559 for (
int i = 0; i < source_count; i++) {
560 int slot = source_map[i];
561 combined_gains[i] = mixer->
ducking.
gain[slot] * pre_bus;
566 for (
int s = 0; s < frame_size; s++) {
568 for (
int i = 0; i < source_count; i++) {
569 mix += source_samples[i][s] * combined_gains[i];
579 float frame_peak = 0.0f;
580 for (
int s = 0; s < frame_size; s++) {
582 if (abs_val > frame_peak)
583 frame_peak = abs_val;
590 for (
int s = 0; s < frame_size; s++) {
596 output[frame_start + s] =
soft_clip(mix, 1.0f, 3.0f);
605 if (!mixer || !output || num_samples <= 0)
618 SAFE_MEMSET(output, num_samples *
sizeof(
float), 0, num_samples *
sizeof(
float));
628 bool valid_exclude = (exclude_index <
MIXER_MAX_SOURCES && exclude_index != 0xFF &&
629 mixer->
source_ids[exclude_index] == exclude_client_id);
634 uint64_t mask_without_excluded = active_mask & ~(1ULL << exclude_index);
635 if (mask_without_excluded == 0 && mixer->
source_buffers[exclude_index]) {
641 size_t to_skip = ((size_t)num_samples < available) ? (size_t)num_samples : available;
644 unsigned int old_read_idx = atomic_load_explicit(&rb->
read_index, memory_order_relaxed);
646 atomic_store_explicit(&rb->
read_index, new_read_idx, memory_order_release);
652 active_mask = mask_without_excluded;
657 "MIXER EXCLUSION: exclude_client=%u, exclude_index=%u, active_mask_before=0x%llx, active_mask_after=0x%llx",
658 exclude_client_id, exclude_index, (
unsigned long long)(active_mask | (1ULL << exclude_index)),
659 (
unsigned long long)active_mask);
662 log_warn_every(1000000,
"MIXER EXCLUSION FAILED: exclude_client=%u, exclude_index=%u (valid=%d), lookup_id=%u",
663 exclude_client_id, exclude_index, valid_exclude,
668 if (active_mask == 0) {
677 for (
int frame_start = 0; frame_start < num_samples; frame_start +=
MIXER_FRAME_SIZE) {
680 struct timespec read_start, read_end;
681 (void)clock_gettime(CLOCK_MONOTONIC, &read_start);
688 int source_count = 0;
692 uint64_t current_mask = active_mask;
694 int i = find_first_set_bit(current_mask);
695 current_mask &= current_mask - 1;
700 size_t samples_read_size =
702 int samples_read = (int)samples_read_size;
706 if (samples_read > 0) {
708 if (samples_read < frame_size) {
709 SAFE_MEMSET(&source_samples[source_count][samples_read], (frame_size - samples_read) *
sizeof(
float), 0,
710 (frame_size - samples_read) *
sizeof(
float));
714 float source_rms = 0.0f;
715 if (frame_start == 0) {
716 float sum_squares = 0.0f;
717 for (
int s = 0; s < samples_read; s++) {
718 sum_squares += source_samples[source_count][s] * source_samples[source_count][s];
720 source_rms = sqrtf(sum_squares / (
float)samples_read);
721 log_info_every(1000000,
"MIXER SOURCE READ: client_id=%u, slot=%d, samples_read=%d, RMS=%.6f",
722 mixer->
source_ids[i], i, samples_read, source_rms);
725 source_map[source_count] = i;
731 (void)clock_gettime(CLOCK_MONOTONIC, &read_end);
733 ((
uint64_t)read_start.tv_sec * 1000000 + (
uint64_t)read_start.tv_nsec / 1000);
735 if (read_time_us > 10000) {
737 (
float)read_time_us / 1000.0f, source_count);
742 int speaking_count = 0;
744 for (
int i = 0; i < source_count; i++) {
745 int slot = source_map[i];
749 for (
int s = 0; s < frame_size; s++) {
750 float abs_sample = fabsf(source_samples[i][s]);
751 if (abs_sample > peak)
773 float crowd_gain = (speaking_count > 0) ? (1.0f / powf((
float)speaking_count, mixer->
crowd_alpha)) : 1.0f;
774 float pre_bus = mixer->
base_gain * crowd_gain;
778 for (
int i = 0; i < source_count; i++) {
779 int slot = source_map[i];
780 combined_gains[i] = mixer->
ducking.
gain[slot] * pre_bus;
785 for (
int s = 0; s < frame_size; s++) {
787 for (
int i = 0; i < source_count; i++) {
788 mix += source_samples[i][s] * combined_gains[i];
798 float frame_peak = 0.0f;
799 for (
int s = 0; s < frame_size; s++) {
801 if (abs_val > frame_peak)
802 frame_peak = abs_val;
809 for (
int s = 0; s < frame_size; s++) {
815 output[frame_start + s] =
soft_clip(mix, 1.0f, 3.0f);
823 if (total_ns > 2000000) {
824 char duration_str[32];
826 log_warn(
"Slow mixer: total=%s, num_samples=%d", duration_str, num_samples);
878 target = (peak_amplitude > gate->
threshold) ? 1.0f : 0.0f;
893 if (!gate || !buffer || num_samples <= 0)
898 for (
int i = 0; i < num_samples; i++) {
899 float abs_sample = fabsf(buffer[i]);
900 if (abs_sample > peak) {
906 for (
int i = 0; i < num_samples; i++) {
929 filter->
alpha = 1.0f / (1.0f + 2.0f *
M_PI * cutoff_hz / sample_rate);
957 if (!filter || !buffer || num_samples <= 0)
960 for (
int i = 0; i < num_samples; i++) {
979 float dt = 1.0f / sample_rate;
980 float rc = 1.0f / (2.0f * (float)
M_PI * cutoff_hz);
981 filter->
alpha = dt / (rc + dt);
1006 if (!filter || !buffer || num_samples <= 0)
1009 for (
int i = 0; i < num_samples; i++) {
1019float soft_clip(
float sample,
float threshold,
float steepness) {
1020 if (sample > threshold) {
1023 return threshold + (1.0f - threshold) * tanhf((sample - threshold) * steepness);
1025 if (sample < -threshold) {
1027 return -threshold + (-1.0f + threshold) * tanhf((sample + threshold) * steepness);
1033 if (!buffer || num_samples <= 0)
1036 for (
int i = 0; i < num_samples; i++) {
1037 buffer[i] =
soft_clip(buffer[i], threshold, steepness);
1051 return t * t * (3.0f - 2.0f * t);
1060 return (int16_t)(sample * 32767.0f);
1064 return (
float)sample / 32768.0f;
1068 if (!src || !dst || count <= 0)
1070 for (
int i = 0; i < count; i++) {
1076 if (!src || !dst || count <= 0)
1078 for (
int i = 0; i < count; i++) {
1084 if (!buffer || count <= 0)
1088 for (
int i = 0; i < count; i++) {
1089 float abs_sample = fabsf(buffer[i]);
1090 if (abs_sample > peak)
1097 if (!buffer || count <= 0)
1100 for (
int i = 0; i < count; i++) {
1105void fade_buffer(
float *buffer,
int count,
float start_gain,
float end_gain) {
1106 if (!buffer || count <= 0)
1109 float step = (end_gain - start_gain) / (
float)count;
1110 float gain = start_gain;
1111 for (
int i = 0; i < count; i++) {
1118 if (!buffer || count <= 0)
1121 for (
int i = 0; i < count; i++) {
1122 float t = (float)i / (
float)(count - 1);
1123 float gain =
smoothstep(fade_in ? t : (1.0f - t));
1129 if (!src || !dst || count <= 0)
1132 for (
int i = 0; i < count; i++) {
1133 dst[i] = src[i] * gain;
⚠️‼️ Error and/or exit() when things go bad.
🔢 Bit Manipulation Utilities
float hysteresis
Hysteresis factor (0-1, prevents gate chatter)
float release_coeff
Release coefficient (converted from release_ms)
bool noise_gate_is_open(const noise_gate_t *gate)
Check if noise gate is currently open.
uint8_t source_id_to_index[256]
Hash table mapping client_id → mixer source index (uses hash function for 32-bit IDs)
float base_gain
Base gain before crowd scaling is applied.
void copy_buffer_with_gain(const float *src, float *dst, int count, float gain)
Copy buffer with gain scaling.
float leader_margin_dB
Leader margin in dB (sources within this of loudest are leaders)
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
Read audio samples from ring buffer.
void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples)
Process a buffer of samples through noise gate.
void buffer_float_to_int16(const float *src, int16_t *dst, int count)
Convert float buffer to int16 buffer.
float alpha
Filter coefficient alpha (calculated from cutoff_hz)
float release_coeff
Release coefficient (converted from release_ms)
float clamp_float(float value, float min, float max)
Clamp a float value to a range.
float ratio
Compression ratio (e.g., 4.0 for 4:1 compression)
void highpass_filter_reset(highpass_filter_t *filter)
Reset high-pass filter state.
float sample_rate
Sample rate in Hz (set during initialization)
void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness)
Apply soft clipping to a buffer.
float prev_input
Previous input sample (filter state)
float prev_output
Previous output sample (filter state)
#define MIXER_MAX_SOURCES
Maximum number of simultaneous audio sources.
float highpass_filter_process_sample(highpass_filter_t *filter, float input)
Process a single sample through high-pass filter.
float envelope
Current envelope follower state (linear, 0-1)
bool * source_active
Array of active flags (true if source is active)
float cutoff_hz
Cutoff frequency in Hz (frequencies above this are attenuated)
void apply_gain_buffer(float *buffer, int count, float gain)
Apply gain to buffer in-place.
float atten_dB
Attenuation in dB for non-leader sources.
rwlock_t source_lock
Reader-writer lock protecting source arrays and bitset.
void mixer_set_source_active(mixer_t *mixer, uint32_t client_id, bool active)
Set whether a source is active (receiving audio)
float * gain
Per-source ducking gain (linear, calculated from envelope)
float attack_coeff
Attack coefficient (converted from attack_ms)
void ducking_set_params(ducking_t *duck, float threshold_dB, float leader_margin_dB, float atten_dB, float attack_ms, float release_ms)
Set ducking parameters.
float release_ms
Release time in milliseconds (how fast gate closes)
void ducking_free(ducking_t *duck)
Free ducking system resources.
float threshold_dB
Speaking threshold in dB (sources below this are not "speaking")
size_t audio_ring_buffer_available_read(audio_ring_buffer_t *rb)
Get number of samples available for reading.
void ducking_process_frame(ducking_t *duck, float *envelopes, float *gains, int num_sources)
Process a frame of audio through ducking system.
void noise_gate_init(noise_gate_t *gate, float sample_rate)
Initialize a noise gate.
float crowd_alpha
Crowd scaling exponent (typically 0.5 for sqrt scaling)
float buffer_peak(const float *buffer, int count)
Find peak absolute value in buffer.
ducking_t ducking
Ducking system (active speaker detection and attenuation)
float cutoff_hz
Cutoff frequency in Hz (frequencies below this are attenuated)
int num_sources
Current number of active audio sources.
float soft_clip(float sample, float threshold, float steepness)
Apply soft clipping to a sample.
float sample_rate
Sample rate in Hz (set during initialization)
float knee_dB
Knee width in dB for soft knee (e.g., 2.0)
float attack_ms
Attack time in milliseconds (how fast compression kicks in)
void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, float attack_ms, float release_ms, float makeup_dB)
Set compressor parameters.
void fade_buffer(float *buffer, int count, float start_gain, float end_gain)
Apply linear fade to buffer in-place.
void compressor_init(compressor_t *comp, float sample_rate)
Initialize a compressor.
float alpha
Filter coefficient alpha (calculated from cutoff_hz)
int ducking_init(ducking_t *duck, int num_sources, float sample_rate)
Initialize ducking system.
#define MIXER_FRAME_SIZE
Number of samples processed per audio frame.
void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a high-pass filter.
uint32_t * source_ids
Array of client IDs (one per source slot)
uint64_t active_sources_mask
Bitset of active sources (bit i = source i is active, O(1) iteration)
float int16_to_float(int16_t sample)
Convert int16 sample to float.
float * envelope
Per-source envelope follower state (linear, allocated per source)
float threshold_dB
Compression threshold in dB (e.g., -10.0)
float release_ms
Release time in milliseconds (how fast compression releases)
int max_sources
Maximum number of sources (allocated array sizes)
float * mix_buffer
Temporary buffer for mixing operations (pre-allocated)
float attack_ms
Attack time in milliseconds (how fast gate opens)
float envelope
Current envelope follower state (linear, 0-1)
float sample_rate
Sample rate in Hz (set during initialization)
bool gate_open
True if gate is currently open (allowing audio through)
float smoothstep(float t)
Compute smoothstep interpolation.
void fade_buffer_smooth(float *buffer, int count, bool fade_in)
Apply smoothstep fade to buffer in-place.
float attack_ms
Ducking attack time in milliseconds.
int mixer_add_source(mixer_t *mixer, uint32_t client_id, audio_ring_buffer_t *buffer)
Add an audio source to the mixer.
int16_t float_to_int16(float sample)
Convert float sample to int16 (WebRTC format)
compressor_t compressor
Compressor (dynamic range compression)
float sample_rate
Sample rate in Hz (set during initialization)
void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a low-pass filter.
mixer_t * mixer_create(int max_sources, int sample_rate)
Create a new audio mixer.
int mixer_process_excluding_source(mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id)
Process audio from all sources except one (for per-client output)
float db_to_linear(float db)
Convert decibels to linear gain.
void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through low-pass filter.
void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through high-pass filter.
float release_ms
Ducking release time in milliseconds.
audio_ring_buffer_t ** source_buffers
Array of pointers to client audio ring buffers.
void noise_gate_set_params(noise_gate_t *gate, float threshold, float attack_ms, float release_ms, float hysteresis)
Set noise gate parameters.
float makeup_dB
Makeup gain in dB (compensates for gain reduction)
float compressor_process_sample(compressor_t *comp, float sidechain)
Process a single sample through compressor.
void mixer_destroy(mixer_t *mixer)
Destroy a mixer and free all resources.
int sample_rate
Sample rate in Hz (e.g., 44100)
float lowpass_filter_process_sample(lowpass_filter_t *filter, float input)
Process a single sample through low-pass filter.
float linear_to_db(float linear)
Convert linear gain to decibels.
void lowpass_filter_reset(lowpass_filter_t *filter)
Reset low-pass filter state.
float attack_coeff
Attack coefficient (converted from attack_ms)
int mixer_process(mixer_t *mixer, float *output, int num_samples)
Process audio from all active sources.
void mixer_remove_source(mixer_t *mixer, uint32_t client_id)
Remove an audio source from the mixer.
void buffer_int16_to_float(const int16_t *src, float *dst, int count)
Convert int16 buffer to float buffer.
float threshold
Gate threshold in linear units (e.g., 0.01f for -40dB)
float prev_output
Previous output sample (filter state)
float release_coeff
Release coefficient (converted from release_ms)
float gain_lin
Current gain multiplier (linear, calculated from envelope)
float attack_coeff
Attack coefficient (converted from attack_ms)
float noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude)
Process a single sample through noise gate.
#define SAFE_MEMSET(dest, dest_size, ch, count)
#define SAFE_MALLOC(size, cast)
unsigned long long uint64_t
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
#define log_warn(...)
Log a WARN message.
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
Format nanoseconds as human-readable duration string.
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
#define STOP_TIMER(name_fmt,...)
Stop a timer with formatted name and return elapsed time.
#define AUDIO_RING_BUFFER_SIZE
Audio ring buffer size in samples (192000 samples = 4 seconds @ 48kHz)
🔊 Audio Capture and Playback Interface for ascii-chat
🔢 Mathematical Utility Functions
Multi-Source Audio Mixing and Processing System.
✅ Safe Integer Arithmetic and Overflow Detection
Audio ring buffer for real-time audio streaming.
atomic_uint read_index
Read index (consumer position) - LOCK-FREE with atomic operations.
Dynamic range compressor settings and state.
Ducking system settings and state.
High-pass filter settings and state.
Main mixer structure for multi-source audio processing.
Noise gate settings and state.
⏱️ High-precision timing utilities using sokol_time.h and uthash