From b513b98a9a20a0c9f1a9e4c4413bcfcdc1bd0437 Mon Sep 17 00:00:00 2001 From: Kuba Pro Date: Sat, 1 Mar 2025 21:00:56 +0100 Subject: [PATCH] add compressor --- .vscode/.server-controller-port.log | 2 +- .vscode/settings.json | 3 +- lib/filters.c | 233 ++++++++++++++++++++++++++++ lib/filters.h | 44 ++++-- src/fm95.c | 11 +- 5 files changed, 278 insertions(+), 15 deletions(-) diff --git a/.vscode/.server-controller-port.log b/.vscode/.server-controller-port.log index 08e0781..eacf256 100644 --- a/.vscode/.server-controller-port.log +++ b/.vscode/.server-controller-port.log @@ -1,5 +1,5 @@ { "port": 13452, - "time": 1738344784291, + "time": 1740856477215, "version": "0.0.3" } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 9f1ad52..d7f8309 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,5 +19,6 @@ "stdint.h": "c", "asoundlib.h": "c", "hilbert.h": "c" - } + }, + "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/lib/filters.c b/lib/filters.c index ceb3ccc..c8190fd 100644 --- a/lib/filters.c +++ b/lib/filters.c @@ -77,4 +77,237 @@ float hard_clip(float sample, float threshold) { } else { return sample; // No clipping } +} + +float voltage_db_to_voltage(float db) { + return powf(10.0f, db / 20.0f); +} + +float power_db_to_voltage(float db) { + return powf(10.0f, db / 10.0f); +} + +float voltage_to_voltage_db(float linear) { + return 20.0f * log10f(fmaxf(linear, 1e-10f)); // Avoid log(0) +} + +float voltage_to_power_db(float linear) { + return 10.0f * log10f(fmaxf(linear, 1e-10f)); // Avoid log(0) +} + +void init_compressor(Compressor *compressor, float threshold, float ratio, float knee, float makeup_gain, float attack, float release, float rmsTime, float sample_rate) { + compressor->threshold = threshold; + compressor->ratio = ratio; + compressor->knee = knee; + compressor->makeup_gain = makeup_gain; + compressor->attack = attack; + compressor->release = release; + compressor->sample_rate = sample_rate; + compressor->gainReduction = 0.0f; + compressor->rmsEnv = 0.0f; + compressor->rmsTime = rmsTime; +} + +float rms_compress(Compressor *compressor, float sample) { + float env; + float rmsAlpha = 1.0f - exp(-1.0f / (compressor->rmsTime * compressor->sample_rate)); + compressor->rmsEnv = (1.0f - rmsAlpha) * compressor->rmsEnv + rmsAlpha * (sample * sample); + env = sqrtf(compressor->rmsEnv); + + float input_db = voltage_to_voltage_db(env); + + float targetGR = 0.0f; + if(input_db > compressor->threshold) { + if(compressor->knee > 0.0f) { + float delta = input_db - compressor->threshold; + if(delta < compressor->knee / 2.0f) { + targetGR = (1.0f - 1.0f / compressor->ratio) * (delta * delta) / compressor->knee; + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * delta; + } + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * (input_db - compressor->threshold); + } + } else { + targetGR = 0.0f; + } + + float coeff; + if(targetGR > compressor->gainReduction) { + coeff = expf(-1.0f / (compressor->attack * compressor->sample_rate)); + } else { + coeff = expf(-1.0f / (compressor->release * compressor->sample_rate)); + } + compressor->gainReduction = coeff * compressor->gainReduction + (1.0f - coeff) * targetGR; + + float gain = voltage_db_to_voltage(compressor->makeup_gain - compressor->gainReduction); + return sample * gain; +} + +float peak_compress(Compressor *compressor, float sample) { + float env = fabsf(sample); + + float input_db = voltage_to_voltage_db(env); + + float targetGR = 0.0f; + if(input_db > compressor->threshold) { + if(compressor->knee > 0.0f) { + float delta = input_db - compressor->threshold; + if(delta < compressor->knee / 2.0f) { + targetGR = (1.0f - 1.0f / compressor->ratio) * (delta * delta) / compressor->knee; + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * delta; + } + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * (input_db - compressor->threshold); + } + } else { + targetGR = 0.0f; + } + + float coeff; + if(targetGR > compressor->gainReduction) { + coeff = expf(-1.0f / (compressor->attack * compressor->sample_rate)); + } else { + coeff = expf(-1.0f / (compressor->release * compressor->sample_rate)); + } + compressor->gainReduction = coeff * compressor->gainReduction + (1.0f - coeff) * targetGR; + + float gain = voltage_db_to_voltage(compressor->makeup_gain - compressor->gainReduction); + return sample * gain; +} + +void init_compressor_stereo(StereoCompressor *compressor, float threshold, float ratio, float knee, float makeup_gain, float attack, float release, float rmsTime, float sample_rate) { + compressor->threshold = threshold; + compressor->ratio = ratio; + compressor->knee = knee; + compressor->makeup_gain = makeup_gain; + compressor->attack = attack; + compressor->release = release; + compressor->sample_rate = sample_rate; + compressor->gainReduction = 0.0f; + compressor->rmsEnv = 0.0f; + compressor->rmsEnv2 = 0.0f; + compressor->rmsTime = rmsTime; +} + +float rms_compress_stereo(StereoCompressor *compressor, float l, float r, float *output_r) { + float env_l; + float env_r; + float rmsAlpha = 1.0f - exp(-1.0f / (compressor->rmsTime * compressor->sample_rate)); + compressor->rmsEnv = (1.0f - rmsAlpha) * compressor->rmsEnv + rmsAlpha * (l * l); + compressor->rmsEnv2 = (1.0f - rmsAlpha) * compressor->rmsEnv + rmsAlpha * (r * r); + env_l = sqrtf(compressor->rmsEnv); + env_r = sqrtf(compressor->rmsEnv2); + + float input_db = voltage_to_voltage_db(env_l); + float input_db_r = voltage_to_voltage_db(env_r); + + float targetGR = 0.0f; + if(input_db > compressor->threshold) { + if(compressor->knee > 0.0f) { + float delta = input_db - compressor->threshold; + if(delta < compressor->knee / 2.0f) { + targetGR = (1.0f - 1.0f / compressor->ratio) * (delta * delta) / compressor->knee; + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * delta; + } + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * (input_db - compressor->threshold); + } + } else { + targetGR = 0.0f; + } + float targetGR_r = 0.0f; + if(input_db_r > compressor->threshold) { + if(compressor->knee > 0.0f) { + float delta = input_db_r - compressor->threshold; + if(delta < compressor->knee / 2.0f) { + targetGR_r = (1.0f - 1.0f / compressor->ratio) * (delta * delta) / compressor->knee; + } else { + targetGR_r = (1.0f - 1.0f / compressor->ratio) * delta; + } + } else { + targetGR_r = (1.0f - 1.0f / compressor->ratio) * (input_db_r - compressor->threshold); + } + } else { + targetGR_r = 0.0f; + } + + float shared_target_gr; + if(targetGR > targetGR_r) { + shared_target_gr = targetGR; + } else { + shared_target_gr = targetGR_r; + } + + float coeff; + if(shared_target_gr > compressor->gainReduction) { + coeff = expf(-1.0f / (compressor->attack * compressor->sample_rate)); + } else { + coeff = expf(-1.0f / (compressor->release * compressor->sample_rate)); + } + compressor->gainReduction = coeff * compressor->gainReduction + (1.0f - coeff) * shared_target_gr; + + float gain = voltage_db_to_voltage(compressor->makeup_gain - compressor->gainReduction); + *output_r = r * gain; + return l * gain; +} + +float peak_compress_stereo(StereoCompressor *compressor, float l, float r, float *output_r) { + float env_l = fabsf(l); + float env_r = fabsf(r); + + float input_db = voltage_to_voltage_db(env_l); + float input_db_r = voltage_to_voltage_db(env_r); + + float targetGR = 0.0f; + if(input_db > compressor->threshold) { + if(compressor->knee > 0.0f) { + float delta = input_db - compressor->threshold; + if(delta < compressor->knee / 2.0f) { + targetGR = (1.0f - 1.0f / compressor->ratio) * (delta * delta) / compressor->knee; + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * delta; + } + } else { + targetGR = (1.0f - 1.0f / compressor->ratio) * (input_db - compressor->threshold); + } + } else { + targetGR = 0.0f; + } + float targetGR_r = 0.0f; + if(input_db_r > compressor->threshold) { + if(compressor->knee > 0.0f) { + float delta = input_db_r - compressor->threshold; + if(delta < compressor->knee / 2.0f) { + targetGR_r = (1.0f - 1.0f / compressor->ratio) * (delta * delta) / compressor->knee; + } else { + targetGR_r = (1.0f - 1.0f / compressor->ratio) * delta; + } + } else { + targetGR_r = (1.0f - 1.0f / compressor->ratio) * (input_db_r - compressor->threshold); + } + } else { + targetGR_r = 0.0f; + } + + float shared_target_gr; + if(targetGR > targetGR_r) { + shared_target_gr = targetGR; + } else { + shared_target_gr = targetGR_r; + } + + float coeff; + if(shared_target_gr > compressor->gainReduction) { + coeff = expf(-1.0f / (compressor->attack * compressor->sample_rate)); + } else { + coeff = expf(-1.0f / (compressor->release * compressor->sample_rate)); + } + compressor->gainReduction = coeff * compressor->gainReduction + (1.0f - coeff) * shared_target_gr; + + float gain = voltage_db_to_voltage(compressor->makeup_gain - compressor->gainReduction); + *output_r = r * gain; + return l * gain; } \ No newline at end of file diff --git a/lib/filters.h b/lib/filters.h index d7f43b7..8ce8f20 100644 --- a/lib/filters.h +++ b/lib/filters.h @@ -25,16 +25,40 @@ void init_hpf(BiquadFilter* filter, float cutoffFreq, float qFactor, float sampl float apply_frequency_filter(BiquadFilter* filter, float input); float hard_clip(float sample, float threshold); +float voltage_db_to_voltage(float db); +float power_db_to_voltage(float db); +float voltage_to_voltage_db(float linear); +float voltage_to_power_db(float linear); typedef struct { - float *buffer; - int write_idx; // Write position - int read_idx; // Read position - int size; // Total buffer size - int delay; // Delay in samples -} DelayLine; + float threshold; + float ratio; + float knee; + float makeup_gain; + float attack; + float release; + float sample_rate; + float gainReduction; + float rmsEnv; + float rmsTime; +} Compressor; +void init_compressor(Compressor *compressor, float threshold, float ratio, float knee, float makeup_gain, float attack, float release, float rmsTime, float sample_rate); +float peak_compress(Compressor *compressor, float sample); +float rms_compress(Compressor *compressor, float sample); -void init_delay_line(DelayLine *delay_line, int max_delay); -void set_delay_line(DelayLine *delay_line, int new_delay); -float delay_line(DelayLine *delay_line, float in); -void exit_delay_line(DelayLine *delay_line); \ No newline at end of file +typedef struct { + float threshold; + float ratio; + float knee; + float makeup_gain; + float attack; + float release; + float sample_rate; + float gainReduction; + float rmsEnv; + float rmsEnv2; + float rmsTime; +} StereoCompressor; +void init_compressor_stereo(StereoCompressor *compressor, float threshold, float ratio, float knee, float makeup_gain, float attack, float release, float rmsTime, float sample_rate); +float peak_compress_stereo(StereoCompressor *compressor, float l, float r, float *output_r); +float rms_compress_stereo(StereoCompressor *compressor, float l, float r, float *output_r); diff --git a/src/fm95.c b/src/fm95.c index 93e95d6..346c8f3 100644 --- a/src/fm95.c +++ b/src/fm95.c @@ -34,7 +34,7 @@ #include #define MASTER_VOLUME 1.0f // Volume of everything combined, for calibration -#define AUDIO_VOLUME 2.0f // Audio volume, before clipper +#define AUDIO_VOLUME 1.0f // Audio volume, before clipper #define MONO_VOLUME 0.45f // L+R Signal #define PILOT_VOLUME 0.09f // 19 KHz Pilot #define STEREO_VOLUME 0.45f // L-R signal, should be same as MONO @@ -373,6 +373,9 @@ int main(int argc, char **argv) { BiquadFilter lpf_l, lpf_r; init_lpf(&lpf_l, LPF_CUTOFF, 1.25f, SAMPLE_RATE); init_lpf(&lpf_r, LPF_CUTOFF, 1.25f, SAMPLE_RATE); + + StereoCompressor comp; + init_compressor_stereo(&comp, -2.0f, 15.0f, 2.0f, 0.0f, 0.015f, 0.25f, 0.01f, SAMPLE_RATE); // #endregion signal(SIGINT, stop); @@ -413,8 +416,10 @@ int main(int argc, char **argv) { float current_mpx_in = mpx_in[i]; float current_sca_in = sca_in[i]; - float ready_l = apply_frequency_filter(&lpf_l, l_in); - float ready_r = apply_frequency_filter(&lpf_r, r_in); + float ready_r; + float ready_l = peak_compress_stereo(&comp, l_in, r_in, &ready_r); + ready_l = apply_frequency_filter(&lpf_l, l_in); + ready_r = apply_frequency_filter(&lpf_r, r_in); ready_l = apply_preemphasis(&preemp_l, ready_l)*2; ready_r = apply_preemphasis(&preemp_r, ready_r)*2; ready_l = hard_clip(ready_l*audio_volume, clipper_threshold);