convert pump compressor to procedural approach
This commit is contained in:
189
dynamics/pump.h
189
dynamics/pump.h
@@ -1,66 +1,92 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include "audio_math.h"
|
#include "audio_math.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace trnr {
|
namespace trnr {
|
||||||
|
struct pump {
|
||||||
|
double samplerate;
|
||||||
|
float threshold_db = 0.f;
|
||||||
|
float attack_ms = 10.f;
|
||||||
|
float release_ms = 100.f;
|
||||||
|
float hp_filter = 80.f;
|
||||||
|
float ratio = 1000.f;
|
||||||
|
float filter_frq = 40000.f;
|
||||||
|
float filter_exp = 0.f;
|
||||||
|
float treble_boost = 0.f;
|
||||||
|
|
||||||
|
float filtered = 0.f;
|
||||||
|
float filtered_l = 0.f;
|
||||||
|
float filtered_r = 0.f;
|
||||||
|
float boosted_l = 0.f;
|
||||||
|
float boosted_r = 0.f;
|
||||||
|
float attack_coef = 0.f;
|
||||||
|
float release_coef = 0.f;
|
||||||
|
float envelope_db = 0.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum pump_param {
|
||||||
|
PUMP_THRESHOLD,
|
||||||
|
PUMP_ATTACK,
|
||||||
|
PUMP_RELEASE,
|
||||||
|
PUMP_HP_FILTER,
|
||||||
|
PUMP_RATIO,
|
||||||
|
PUMP_FILTER_FRQ,
|
||||||
|
PUMP_FILTER_EXP,
|
||||||
|
PUMP_TREBLE_BOOST
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void pump_set_param(pump& p, pump_param param, float value)
|
||||||
|
{
|
||||||
|
switch (param) {
|
||||||
|
case PUMP_THRESHOLD:
|
||||||
|
p.threshold_db = value;
|
||||||
|
break;
|
||||||
|
case PUMP_ATTACK:
|
||||||
|
p.attack_ms = value;
|
||||||
|
p.attack_coef = exp(-1000.0 / (p.attack_ms * p.samplerate));
|
||||||
|
break;
|
||||||
|
case PUMP_RELEASE:
|
||||||
|
p.release_ms = value;
|
||||||
|
p.release_coef = exp(-1000.0 / (p.release_ms * p.samplerate));
|
||||||
|
break;
|
||||||
|
case PUMP_HP_FILTER:
|
||||||
|
p.hp_filter = value;
|
||||||
|
break;
|
||||||
|
case PUMP_RATIO:
|
||||||
|
p.ratio = value;
|
||||||
|
break;
|
||||||
|
case PUMP_FILTER_FRQ:
|
||||||
|
p.filter_frq = value;
|
||||||
|
break;
|
||||||
|
case PUMP_FILTER_EXP:
|
||||||
|
p.filter_exp = value;
|
||||||
|
break;
|
||||||
|
case PUMP_TREBLE_BOOST:
|
||||||
|
p.treble_boost = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void pump_init(pump& p, double samplerate)
|
||||||
|
{
|
||||||
|
p.samplerate = samplerate;
|
||||||
|
pump_set_param(p, PUMP_ATTACK, p.attack_ms);
|
||||||
|
pump_set_param(p, PUMP_RELEASE, p.release_ms);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename sample>
|
template <typename sample>
|
||||||
class pump {
|
inline void pump_process_block(pump& p, sample** audio, sample** sidechain, int frames)
|
||||||
public:
|
{
|
||||||
pump()
|
|
||||||
: attack_ms(10.f)
|
|
||||||
, release_ms(100.f)
|
|
||||||
, hp_filter(80.0f)
|
|
||||||
, threshold_db(0.f)
|
|
||||||
, ratio(1000.0f)
|
|
||||||
, filter_frq(40000.f)
|
|
||||||
, filter_exp(0.f)
|
|
||||||
, treble_boost(0.f)
|
|
||||||
{
|
|
||||||
set_samplerate(44100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_samplerate(double _samplerate)
|
|
||||||
{
|
|
||||||
samplerate = _samplerate;
|
|
||||||
set_attack(attack_ms);
|
|
||||||
set_release(release_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_ratio(float value) { ratio = value; }
|
|
||||||
|
|
||||||
/* set threshold in db */
|
|
||||||
void set_threshold(float value) { threshold_db = value; }
|
|
||||||
|
|
||||||
/* set attack in ms */
|
|
||||||
void set_attack(float value)
|
|
||||||
{
|
|
||||||
attack_ms = value;
|
|
||||||
attack_coef = exp(-1000.0 / (attack_ms * samplerate));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* set release in ms */
|
|
||||||
void set_release(float value)
|
|
||||||
{
|
|
||||||
release_ms = value;
|
|
||||||
release_coef = exp(-1000.0 / (release_ms * samplerate));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* lowpass filter frequency in hz */
|
|
||||||
void set_filter_frq(float value) { filter_frq = value; }
|
|
||||||
|
|
||||||
/* accentuates the filter pumping effect */
|
|
||||||
void set_filter_exp(float value) { filter_exp = value; }
|
|
||||||
|
|
||||||
void process_block(sample** audio, sample** sidechain, int frames)
|
|
||||||
{
|
|
||||||
// highpass filter coefficients
|
// highpass filter coefficients
|
||||||
float hp_x = std::exp(-2.0 * M_PI * hp_filter / samplerate);
|
float hp_x = std::exp(-2.0 * M_PI * p.hp_filter / p.samplerate);
|
||||||
float hp_a0 = 1.0 - hp_x;
|
float hp_a0 = 1.0 - hp_x;
|
||||||
float hp_b1 = -hp_x;
|
float hp_b1 = -hp_x;
|
||||||
|
|
||||||
// top end boost filter coefficients
|
// top end boost filter coefficients
|
||||||
float bst_x = exp(-2.0 * M_PI * 5000.0 / samplerate);
|
float bst_x = exp(-2.0 * M_PI * 5000.0 / p.samplerate);
|
||||||
float bst_a0 = 1.0 - bst_x;
|
float bst_a0 = 1.0 - bst_x;
|
||||||
float bst_b1 = -bst_x;
|
float bst_b1 = -bst_x;
|
||||||
|
|
||||||
@@ -72,76 +98,55 @@ public:
|
|||||||
sample sidechain_in = (sidechain[0][i] + sidechain[1][i]) / 2.0;
|
sample sidechain_in = (sidechain[0][i] + sidechain[1][i]) / 2.0;
|
||||||
|
|
||||||
// highpass filter sidechain signal
|
// highpass filter sidechain signal
|
||||||
filtered = hp_a0 * sidechain_in - hp_b1 * filtered;
|
p.filtered = hp_a0 * sidechain_in - hp_b1 * p.filtered;
|
||||||
sidechain_in = sidechain_in - filtered;
|
sidechain_in = sidechain_in - p.filtered;
|
||||||
|
|
||||||
// rectify sidechain input for envelope following
|
// rectify sidechain input for envelope following
|
||||||
float link = std::fabs(sidechain_in);
|
float link = std::fabs(sidechain_in);
|
||||||
float linked_db = trnr::lin_2_db(link);
|
float linked_db = trnr::lin_2_db(link);
|
||||||
|
|
||||||
// cut envelope below threshold
|
// cut envelope below threshold
|
||||||
float overshoot_db = linked_db - (threshold_db - 10.0);
|
float overshoot_db = linked_db - (p.threshold_db - 10.0);
|
||||||
if (overshoot_db < 0.0) overshoot_db = 0.0;
|
if (overshoot_db < 0.0) overshoot_db = 0.0;
|
||||||
|
|
||||||
// process envelope
|
// process envelope
|
||||||
if (overshoot_db > envelope_db) {
|
if (overshoot_db > p.envelope_db) {
|
||||||
envelope_db = overshoot_db + attack_coef * (envelope_db - overshoot_db);
|
p.envelope_db = overshoot_db + p.attack_coef * (p.envelope_db - overshoot_db);
|
||||||
} else {
|
} else {
|
||||||
envelope_db = overshoot_db + release_coef * (envelope_db - overshoot_db);
|
p.envelope_db = overshoot_db + p.release_coef * (p.envelope_db - overshoot_db);
|
||||||
}
|
}
|
||||||
|
|
||||||
float slope = 1.f / ratio;
|
float slope = 1.f / p.ratio;
|
||||||
|
|
||||||
// transfer function
|
// transfer function
|
||||||
float gain_reduction_db = envelope_db * (slope - 1.0);
|
float gain_reduction_db = p.envelope_db * (slope - 1.0);
|
||||||
float gain_reduction_lin = db_2_lin(gain_reduction_db);
|
float gain_reduction_lin = db_2_lin(gain_reduction_db);
|
||||||
|
|
||||||
// compress left and right signals
|
// compress left and right signals
|
||||||
sample output_l = input_l * gain_reduction_lin;
|
sample output_l = input_l * gain_reduction_lin;
|
||||||
sample output_r = input_r * gain_reduction_lin;
|
sample output_r = input_r * gain_reduction_lin;
|
||||||
|
|
||||||
if (filter_exp > 0.f) {
|
if (p.filter_exp > 0.f) {
|
||||||
// one pole lowpass filter with envelope applied to frequency for pumping effect
|
// one pole lowpass filter with envelope applied to frequency for pumping effect
|
||||||
float freq = filter_frq * pow(gain_reduction_lin, filter_exp);
|
float freq = p.filter_frq * pow(gain_reduction_lin, p.filter_exp);
|
||||||
float lp_x = exp(-2.0 * M_PI * freq / samplerate);
|
float lp_x = exp(-2.0 * M_PI * freq / p.samplerate);
|
||||||
float lp_a0 = 1.0 - lp_x;
|
float lp_a0 = 1.0 - lp_x;
|
||||||
float lp_b1 = -lp_x;
|
float lp_b1 = -lp_x;
|
||||||
filtered_l = lp_a0 * output_l - lp_b1 * filtered_l;
|
p.filtered_l = lp_a0 * output_l - lp_b1 * p.filtered_l;
|
||||||
filtered_r = lp_a0 * output_r - lp_b1 * filtered_r;
|
p.filtered_r = lp_a0 * output_r - lp_b1 * p.filtered_r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// top end boost
|
// top end boost
|
||||||
boosted_l = bst_a0 * filtered_l - bst_b1 * boosted_l;
|
p.boosted_l = bst_a0 * p.filtered_l - bst_b1 * p.boosted_l;
|
||||||
boosted_r = bst_a0 * filtered_r - bst_b1 * boosted_r;
|
p.boosted_r = bst_a0 * p.filtered_r - bst_b1 * p.boosted_r;
|
||||||
output_l = filtered_l + (filtered_l - boosted_l) * treble_boost;
|
output_l = p.filtered_l + (p.filtered_l - p.boosted_l) * p.treble_boost;
|
||||||
output_r = filtered_r + (filtered_r - boosted_r) * treble_boost;
|
output_r = p.filtered_r + (p.filtered_r - p.boosted_r) * p.treble_boost;
|
||||||
|
|
||||||
// calculate makeup gain
|
// calculate makeup gain
|
||||||
float makeup_lin = trnr::db_2_lin(-threshold_db / 5.f);
|
float makeup_lin = trnr::db_2_lin(-p.threshold_db / 5.f);
|
||||||
|
|
||||||
audio[0][i] = input_l * gain_reduction_lin * makeup_lin;
|
audio[0][i] = input_l * gain_reduction_lin * makeup_lin;
|
||||||
audio[1][i] = input_r * gain_reduction_lin * makeup_lin;
|
audio[1][i] = input_r * gain_reduction_lin * makeup_lin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
double samplerate;
|
|
||||||
float threshold_db = 0.f;
|
|
||||||
float attack_ms;
|
|
||||||
float release_ms;
|
|
||||||
float hp_filter;
|
|
||||||
float ratio;
|
|
||||||
float filter_frq;
|
|
||||||
float filter_exp;
|
|
||||||
float treble_boost;
|
|
||||||
|
|
||||||
sample filtered;
|
|
||||||
sample filtered_l = 0;
|
|
||||||
sample filtered_r = 0;
|
|
||||||
sample boosted_l = 0;
|
|
||||||
sample boosted_r = 0;
|
|
||||||
float attack_coef;
|
|
||||||
float release_coef;
|
|
||||||
float envelope_db = 0;
|
|
||||||
};
|
|
||||||
} // namespace trnr
|
} // namespace trnr
|
||||||
Reference in New Issue
Block a user