diff --git a/filter/bs412.c b/filter/bs412.c index dee1c06..f3eb0eb 100644 --- a/filter/bs412.c +++ b/filter/bs412.c @@ -9,32 +9,36 @@ inline float power_to_dbr(float power, float ref) { return 10*log10f(power / ref); } -void init_bs412(BS412Compressor* comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate, uint32_t sample_rate) { - comp->reference = (19000.0f / mpx_deviation) * (19000.0f / mpx_deviation); // 0 dbr is a signal which generates a deviation of 19 khz +void init_bs412(BS412Compressor* comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate, float knee_db, uint32_t sample_rate) { + comp->reference = (19000.0f / mpx_deviation) * (19000.0f / mpx_deviation); comp->avg_power = 0.0f; comp->alpha = 1.0f / (BS412_TIME * sample_rate); comp->sample_rate = sample_rate; comp->attack = expf(-1.0f / (attack * sample_rate)); comp->release = expf(-1.0f / (release * sample_rate)); - comp->target = comp->reference * powf(10.0f, target_power / 10.0f);; + comp->target = comp->reference * powf(10.0f, target_power / 10.0f); + comp->target_dbr = power_to_dbr(comp->target, comp->reference); comp->gain = 1.0f; comp->can_compress = 0; comp->second_counter = 0; comp->max_gain = max_gain; comp->gate_threshold = comp->reference * powf(10.0f, gate / 10.0f); + comp->knee_db = knee_db; // e.g. 6.0f — width in dB around target on each side comp->init = true; #ifdef BS412_DEBUG debug_printf("Initialized MPX power measurement with sample rate: %d\n", sample_rate); #endif } -void reinit_bs412(BS412Compressor* comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate) { - comp->reference = (19000.0f / mpx_deviation) * (19000.0f / mpx_deviation); // 0 dbr is a signal which generates a deviation of 19 khz - comp->target = comp->reference * powf(10.0f, target_power / 10.0f);; +void reinit_bs412(BS412Compressor* comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate, float knee_db) { + comp->reference = (19000.0f / mpx_deviation) * (19000.0f / mpx_deviation); + comp->target = comp->reference * powf(10.0f, target_power / 10.0f); + comp->target_dbr = power_to_dbr(comp->target, comp->reference); comp->gate_threshold = comp->reference * powf(10.0f, gate / 10.0f); comp->attack = expf(-1.0f / (attack * comp->sample_rate)); comp->release = expf(-1.0f / (release * comp->sample_rate)); comp->max_gain = max_gain; + comp->knee_db = knee_db; } float bs412_compress(BS412Compressor* comp, float audio, float sample_mpx, float* mpx_power) { @@ -63,17 +67,36 @@ float bs412_compress(BS412Compressor* comp, float audio, float sample_mpx, float float safe_power = fmaxf(comp->avg_power, 1e-12f); float target_gain = sqrtf(comp->target / safe_power); - if (comp->avg_power > comp->target) - comp->gain = comp->attack * comp->gain + (1.0f - comp->attack) * target_gain; - else - comp->gain = comp->release * comp->gain + (1.0f - comp->release) * target_gain; - + + // Soft knee: blend between unity gain and target_gain based on how far + // we are inside the knee region. knee_db is the half-width on each side + // of the target. Below (target - knee), gain tracks freely toward max_gain. + // Above (target + knee), full compression applies. In between, it blends. + float level_dbr = power_to_dbr(comp->avg_power, comp->reference); + float half_knee = comp->knee_db * 0.5f; + float dist = level_dbr - comp->target_dbr; // negative = below target, positive = above + + float knee_blend; + if (dist <= -half_knee) knee_blend = 0.0f; // well below target — don't pull gain toward target + else if (dist >= half_knee) knee_blend = 1.0f; // well above target — full compression + else { + // Smooth cubic ramp: 0→1 over the knee width + float t = (dist + half_knee) / comp->knee_db; // 0..1 + knee_blend = t * t * (3.0f - 2.0f * t); // smoothstep + } + + // The effective target_gain for the smoothed region blends between + // current gain (no correction) and the true target_gain. + float blended_target = comp->gain + knee_blend * (target_gain - comp->gain); + + float coeff = (comp->avg_power > comp->target) ? comp->attack : comp->release; + comp->gain = coeff * comp->gain + (1.0f - coeff) * blended_target; comp->gain = CLAMP(comp->gain, 0.01f, comp->max_gain); comp->sample_counter++; - if(mpx_power != NULL) *mpx_power = power_to_dbr(comp->avg_power, comp->reference); + if(mpx_power != NULL) *mpx_power = level_dbr; if(comp->can_compress) return output_sample; return combined; -} +} \ No newline at end of file diff --git a/filter/bs412.h b/filter/bs412.h index 82b212d..c00918b 100644 --- a/filter/bs412.h +++ b/filter/bs412.h @@ -17,6 +17,7 @@ typedef struct { uint32_t sample_rate; uint32_t sample_counter; float target; + float target_dbr; float attack; float release; float max_gain; @@ -28,8 +29,9 @@ typedef struct { float last_output; float gate_threshold; bool init; + float knee_db; } BS412Compressor; -void init_bs412(BS412Compressor *comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate, uint32_t sample_rate); -void reinit_bs412(BS412Compressor *comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate); +void init_bs412(BS412Compressor *comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate, float knee_db, uint32_t sample_rate); +void reinit_bs412(BS412Compressor *comp, uint32_t mpx_deviation, float target_power, float attack, float release, float max_gain, float gate, float knee_db); float bs412_compress(BS412Compressor *comp, float audio, float sample_mpx, float* mpx_power); \ No newline at end of file diff --git a/src/fm95.c b/src/fm95.c index 756bb28..6b8e783 100644 --- a/src/fm95.c +++ b/src/fm95.c @@ -3,7 +3,7 @@ #include "../inih/ini.h" #include -#define DEFAULT_INI_PATH "/etc/fm95.conf" +#define DEFAULT_INI_PATH "/etc/fm95/fm95.conf" #define buffer_maxlength 99960 #define buffer_tlength_fragsize 99960 @@ -67,6 +67,7 @@ typedef struct { float bs412_release; float bs412_max; float bs412_gate; + float bs412_knee; float lpf_cutoff; } FM95_Config; @@ -221,7 +222,7 @@ int run_fm95(FM95_Config* config, FM95_Runtime* runtime, FM95_RunResult* result) l *= agc_gain; r *= agc_gain; temp_result.agc_gain = agc_gain; - } + } else temp_result.agc_gain = 0.0f; float mod_l, mod_r; @@ -334,6 +335,7 @@ static int config_handler(void* user, const char* section, const char* name, con else if(MATCH("fm95", "bs412_release")) pconfig->bs412_release = strtof(value, NULL); else if(MATCH("fm95", "bs412_max")) pconfig->bs412_max = strtof(value, NULL); else if(MATCH("fm95", "bs412_gate")) pconfig->bs412_gate = strtof(value, NULL); + else if(MATCH("fm95", "bs412_knee")) pconfig->bs412_knee = strtof(value, NULL); else if(MATCH("advanced", "lpf_order")) pconfig->lpf_order = atoi(value); else if(MATCH("advanced", "stereo_ssb")) pconfig->stereo_ssb = atoi(value); else if(MATCH("advanced", "preemp_unity")) pconfig->preemp_unity_freq = strtof(value, NULL); @@ -444,8 +446,8 @@ void init_runtime(FM95_Runtime* runtime, const FM95_Config config) { } if(runtime->bs412.init == true && (runtime->bs412.sample_rate == config.sample_rate)) { - reinit_bs412(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.bs412_gate); - } else init_bs412(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.bs412_gate, config.sample_rate); + reinit_bs412(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.bs412_gate, config.bs412_knee); + } else init_bs412(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.bs412_gate, config.bs412_knee, config.sample_rate); init_stereo_encoder(&runtime->stencode, config.stereo_ssb, 4.0f, &runtime->osc, config.volumes.audio, config.volumes.pilot); float last_gain = 0.0f; @@ -560,6 +562,16 @@ static void *handle_client(ipc_client_arg_t *arg) { to_run = 0; to_reload = 1; break; + case 110: + // Set BS412 gate + memcpy(&val, buf + 1, sizeof(float)); + data->runtime->bs412.knee_db = val; + reply = 0; + break; + case 0xfe: + // Fetch config + send(fd, data->config, sizeof(FM95_Config), 0); + break; case 0xff: // Fetch data send(fd, data->run_result, sizeof(FM95_RunResult), 0); @@ -617,6 +629,7 @@ int main(int argc, char **argv) { .bs412_release = 0.025f, .bs412_max = 2.82f, .bs412_gate = -20.0f, + .bs412_knee = 4.0f, .lpf_cutoff = 15000.0f, };