ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
processor.c
Go to the documentation of this file.
3#include <ascii-chat/common.h>
6#include <math.h>
7#include <string.h>
8
28
31 .sample_rate = sample_rate,
32 .flags = {.agc = true, .compressor = true, .highpass = true},
33 .spectral_enabled = true,
34 .spectral = spectral_default_config(sample_rate),
35 .agc_level = 16000,
36 .agc_max_gain = 35,
37 .highpass_hz = 150,
38 .lowpass_hz = 8000,
39 .comp_threshold_db = -12,
40 .comp_ratio = 3,
41 .comp_makeup_db = 6,
42 .comp_attack_ns = 5 * NS_PER_MS_INT,
43 .comp_release_ns = 150 * NS_PER_MS_INT,
44 .gate_threshold = 0.08f,
45 .gate_hysteresis = 0.3f,
46 .gate_attack_ns = 500 * NS_PER_US_INT,
47 .gate_release_ns = 30 * NS_PER_MS_INT,
48 };
49}
50
70
72 if (!out)
73 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing audio processor output");
74 *out = NULL;
75 if (!c || c->channels < 0 || c->channels > 2 || c->sample_rate < 16000 || c->sample_rate > 192000 ||
76 c->spectral.sample_rate != c->sample_rate || c->agc_level < 1 || c->agc_level > 32767 || c->agc_max_gain < 0 ||
77 c->agc_max_gain > 36 || !isfinite(c->highpass_hz) || c->highpass_hz <= 0 ||
78 c->highpass_hz >= c->sample_rate / 2.0f || !isfinite(c->lowpass_hz) || c->lowpass_hz <= 0 ||
79 c->lowpass_hz > c->sample_rate / 2.0f || !isfinite(c->comp_ratio) || c->comp_ratio < 1 || c->comp_ratio > 20 ||
80 !isfinite(c->comp_threshold_db) || !isfinite(c->comp_makeup_db) || !isfinite(c->gate_threshold) ||
81 c->gate_threshold < 0 || c->gate_threshold > 1 || !isfinite(c->gate_hysteresis) || c->gate_hysteresis < 0 ||
82 c->gate_hysteresis > 1)
83 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio processor configuration");
84 audio_processor_t *p = SAFE_CALLOC(1, sizeof(*p), audio_processor_t *);
85 p->config = *c;
86 if (p->config.channels == 0)
87 p->config.channels = 1;
88 if (p->config.channels == 2) {
90 log_warn_once("Stereo capture uses linked broadband dynamics; spectral AGC, multiband compression and spectral "
91 "gating options are unavailable");
92 // Stereo dynamics share one detector after the independent channel filters.
93 p->config.spectral.auto_gain = false;
94 p->config.spectral.compressor = false;
95 p->config.spectral.noise_gate = false;
96 }
97 p->max_gain = powf(10, c->agc_max_gain / 20.0f);
98 p->gain_attack = 1 - powf(0.75f, 100.0f / c->sample_rate);
99 p->gain_release = 1 - powf(0.95f, 100.0f / c->sample_rate);
100 if (c->spectral_enabled) {
102 if (result != ASCIICHAT_OK) {
103 SAFE_FREE(p);
104 return result;
105 }
106 if (p->config.channels == 2) {
108 if (result != ASCIICHAT_OK) {
110 SAFE_FREE(p);
111 return result;
112 }
113 }
114 }
116 *out = p;
117 return ASCIICHAT_OK;
118}
119
121 if (p)
122 p->config.flags = flags;
123}
124
125asciichat_error_t audio_processor_process(audio_processor_t *p, const float *input, float *output, size_t count) {
126 if (!p || (!input && count) || (!output && count))
127 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio processor buffers");
128 if (!count)
129 return ASCIICHAT_OK;
130 if (p->config.channels == 2) {
131 if (count % 2)
132 return SET_ERRNO(ERROR_INVALID_PARAM, "Incomplete stereo PCM frame");
133 const audio_processor_config_t *c = &p->config;
134 for (size_t base = 0; base < count;) {
135 size_t frames = (count - base) / 2;
136 if (frames > 480)
137 frames = 480;
138 float left[480], right[480];
139 for (size_t i = 0; i < frames; i++) {
140 left[i] = input[base + i * 2];
141 right[i] = input[base + i * 2 + 1];
142 }
143 if (p->spectral_left) {
144 asciichat_error_t result = spectral_process(p->spectral_left, left, left, frames);
145 if (result == ASCIICHAT_OK)
146 result = spectral_process(p->spectral_right, right, right, frames);
147 if (result != ASCIICHAT_OK)
148 return result;
149 }
150 for (size_t i = 0; i < frames; i++) {
151 float l = isfinite(left[i]) ? left[i] : 0;
152 float r = isfinite(right[i]) ? right[i] : 0;
153 if (c->flags.highpass) {
156 }
157 if (c->flags.lowpass) {
160 }
161 float gain = 1;
162 float peak = fmaxf(fabsf(l), fabsf(r));
163 if (c->flags.agc) {
164 p->energy += ((double)l * l + (double)r * r) * 0.5;
165 if (++p->level_samples == (size_t)c->sample_rate / 100) {
166 float rms = (float)sqrt(p->energy / p->level_samples);
167 float target = fminf(0.5f, fmaxf(0.05f, c->agc_level / 32768.0f));
168 p->target_gain = rms > 1e-5f ? fminf(p->max_gain, fmaxf(1, target / rms)) : 1;
169 p->energy = 0;
170 p->level_samples = 0;
171 }
172 float coefficient = p->target_gain < p->gain ? p->gain_attack : p->gain_release;
173 p->gain += coefficient * (p->target_gain - p->gain);
174 gain *= p->gain;
175 }
176 if (c->flags.noise_gate)
177 gain *= noise_gate_process_sample(&p->gate, 1, peak * gain);
178 if (c->flags.compressor)
179 gain *= compressor_process_sample(&p->compressor, peak * gain);
180 if (peak > 0)
181 gain = fminf(gain, 1.0f / peak);
182 output[base + i * 2] = l * gain;
183 output[base + i * 2 + 1] = r * gain;
184 }
185 base += frames * 2;
186 }
187 return ASCIICHAT_OK;
188 }
189 if (p->spectral_left) {
190 asciichat_error_t result = spectral_process(p->spectral_left, input, output, count);
191 if (result != ASCIICHAT_OK)
192 return result;
193 } else if (input != output) {
194 memcpy(output, input, count * sizeof(float));
195 }
196 const audio_processor_config_t *c = &p->config;
197 // Accumulate levels on the sample clock, independent of host callback sizes.
198 const size_t level_frame = (size_t)c->sample_rate / 100;
199 for (size_t i = 0; i < count; ++i) {
200 float sample = isfinite(output[i]) ? output[i] : 0;
201 if (c->flags.agc && !(p->spectral_left && c->spectral.auto_gain)) {
202 p->energy += (double)sample * sample;
203 if (++p->level_samples == level_frame) {
204 float rms = (float)sqrt(p->energy / level_frame);
205 float target = fminf(0.5f, fmaxf(0.05f, c->agc_level / 32768.0f));
206 p->target_gain = rms > 1e-5f ? fminf(p->max_gain, fmaxf(1, target / rms)) : 1;
207 p->energy = 0;
208 p->level_samples = 0;
209 }
210 float coefficient = p->target_gain < p->gain ? p->gain_attack : p->gain_release;
211 p->gain += coefficient * (p->target_gain - p->gain);
212 sample *= p->gain;
213 }
214 if (c->flags.highpass)
215 sample = highpass_filter_process_sample(&p->highpass, sample);
216 if (c->flags.lowpass)
217 sample = lowpass_filter_process_sample(&p->lowpass, sample);
218 if (c->flags.noise_gate)
219 sample = noise_gate_process_sample(&p->gate, sample, fabsf(sample));
220 if (c->flags.compressor && !(p->spectral_left && c->spectral.compressor)) {
221 sample *= compressor_process_sample(&p->compressor, sample);
222 sample = soft_clip(sample, 0.7f, 3);
223 }
224 output[i] = sample;
225 }
226 return ASCIICHAT_OK;
227}
228
230 if (!p)
231 return;
234 p->spectral_left = NULL;
235 SAFE_FREE(p);
236}
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
float highpass_filter_process_sample(highpass_filter_t *filter, float input)
Process a single sample through high-pass filter.
Definition effects.c:209
void noise_gate_init(noise_gate_t *gate, float sample_rate)
Initialize a noise gate.
Definition effects.c:102
float soft_clip(float sample, float threshold, float steepness)
Apply soft clipping to a sample.
Definition effects.c:286
void compressor_init(compressor_t *comp, float sample_rate)
Initialize a compressor.
Definition effects.c:29
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
void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a high-pass filter.
Definition effects.c:187
void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a low-pass filter.
Definition effects.c:237
float compressor_process_sample(compressor_t *comp, float sidechain)
Process a single sample through compressor.
Definition effects.c:74
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 noise_gate_process_sample(noise_gate_t *gate, float input, float peak_amplitude)
Process a single sample through noise gate.
Definition effects.c:134
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define NS_PER_MS_INT
Definition time.h:168
#define NS_PER_US_INT
Definition time.h:167
📝 Logging API with multiple log levels and terminal output control
#define log_warn_once(fmt,...)
Log WARN message exactly once.
Definition log/log.h:886
🔢 Mathematical Utility Functions
Multi-Source Audio Mixing and Processing System.
asciichat_error_t audio_processor_create(const audio_processor_config_t *c, audio_processor_t **out)
Definition processor.c:71
asciichat_error_t audio_processor_process(audio_processor_t *p, const float *input, float *output, size_t count)
Definition processor.c:125
void audio_processor_destroy(audio_processor_t *p)
Definition processor.c:229
audio_processor_config_t audio_processor_default_config(int sample_rate)
Definition processor.c:29
void audio_processor_set_flags(audio_processor_t *p, audio_processor_flags_t flags)
Definition processor.c:120
void audio_processor_reset(audio_processor_t *p)
Definition processor.c:51
struct audio_processor audio_processor_t
Definition processor.h:13
static bool size_t gotel_query_t * out
Definition queries.h:8
asciichat_error_t spectral_create(const spectral_config_t *c, spectral_processor_t **out)
Definition spectral.c:68
asciichat_error_t spectral_process(spectral_processor_t *p, const float *input, float *output, size_t count)
Definition spectral.c:279
spectral_config_t spectral_default_config(int sample_rate)
Definition spectral.c:36
void spectral_destroy(spectral_processor_t *p)
Definition spectral.c:104
void spectral_reset(spectral_processor_t *p)
Definition spectral.c:46
struct spectral_processor spectral_processor_t
Definition spectral.h:15
🔤 String Manipulation and Shell Escaping Utilities
int channels
Interleaved PCM channels; zero retains the mono API default.
Definition processor.h:25
spectral_config_t spectral
Definition processor.h:29
audio_processor_flags_t flags
Definition processor.h:27
audio processor config keeps the values that move through the subsystem: channels,...
Definition processor.h:20
For audio processor, this is the live record that ties config, spectral_left, spectral_right,...
Definition processor.c:14
size_t level_samples
Definition processor.c:25
highpass_filter_t highpass
Definition processor.c:20
float gain_release
Definition processor.c:26
noise_gate_t gate
Definition processor.c:19
float gain_attack
Definition processor.c:26
float target_gain
Definition processor.c:26
audio_processor_config_t config
Definition processor.c:15
spectral_processor_t * spectral_right
Definition processor.c:17
compressor_t compressor
Definition processor.c:18
lowpass_filter_t lowpass
Definition processor.c:21
lowpass_filter_t lowpass_right
Definition processor.c:23
highpass_filter_t highpass_right
Definition processor.c:22
spectral_processor_t * spectral_left
Definition processor.c:16
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