Swap log2 in Spectral Compressor out for ln
The ln() implementation is usually faster, and there's no reason to prefer a specific base.
This commit is contained in:
parent
ab66152f00
commit
144fafbed6
|
@ -37,7 +37,7 @@ const ENVELOPE_INIT_VALUE: f32 = std::f32::consts::FRAC_1_SQRT_2 / 8.0;
|
||||||
/// Compressor from getting brighter as the sample rate increases.
|
/// Compressor from getting brighter as the sample rate increases.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY: f32 = 22_050.0;
|
const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY: f32 = 22_050.0;
|
||||||
const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LOG2: f32 = 14.428_491;
|
const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LN: f32 = 10.001068; // 22_050.0f32.ln()
|
||||||
|
|
||||||
/// A bank of compressors so each FFT bin can be compressed individually. The vectors in this struct
|
/// A bank of compressors so each FFT bin can be compressed individually. The vectors in this struct
|
||||||
/// will have a capacity of `MAX_WINDOW_SIZE / 2 + 1` and a size that matches the current complex
|
/// will have a capacity of `MAX_WINDOW_SIZE / 2 + 1` and a size that matches the current complex
|
||||||
|
@ -60,9 +60,9 @@ pub struct CompressorBank {
|
||||||
/// The same as `should_update_downwards_knee_parabolas`, but for upwards compression.
|
/// The same as `should_update_downwards_knee_parabolas`, but for upwards compression.
|
||||||
pub should_update_upwards_knee_parabolas: Arc<AtomicBool>,
|
pub should_update_upwards_knee_parabolas: Arc<AtomicBool>,
|
||||||
|
|
||||||
/// For each compressor bin, `log2(freq)` where `freq` is the frequency associated with that
|
/// For each compressor bin, `ln(freq)` where `freq` is the frequency associated with that
|
||||||
/// compressor. This is precomputed since all update functions need it.
|
/// compressor. This is precomputed since all update functions need it.
|
||||||
log2_freqs: Vec<f32>,
|
ln_freqs: Vec<f32>,
|
||||||
|
|
||||||
/// Downwards compressor thresholds, in decibels.
|
/// Downwards compressor thresholds, in decibels.
|
||||||
downwards_thresholds_db: Vec<f32>,
|
downwards_thresholds_db: Vec<f32>,
|
||||||
|
@ -115,7 +115,7 @@ pub struct ThresholdParams {
|
||||||
pub threshold_db: FloatParam,
|
pub threshold_db: FloatParam,
|
||||||
/// The center frqeuency for the target curve when sidechaining is not enabled. The curve is a
|
/// The center frqeuency for the target curve when sidechaining is not enabled. The curve is a
|
||||||
/// polynomial `threshold_db + curve_slope*x + curve_curve*(x^2)` that evaluates to a decibel
|
/// polynomial `threshold_db + curve_slope*x + curve_curve*(x^2)` that evaluates to a decibel
|
||||||
/// value, where `x = log2(center_frequency) - log2(bin_frequency)`. In other words, this is
|
/// value, where `x = ln(center_frequency) - ln(bin_frequency)`. In other words, this is
|
||||||
/// evaluated in the log/log domain for decibels and octaves.
|
/// evaluated in the log/log domain for decibels and octaves.
|
||||||
#[id = "thresh_center_freq"]
|
#[id = "thresh_center_freq"]
|
||||||
pub center_frequency: FloatParam,
|
pub center_frequency: FloatParam,
|
||||||
|
@ -415,7 +415,7 @@ impl CompressorBank {
|
||||||
should_update_downwards_knee_parabolas: Arc::new(AtomicBool::new(true)),
|
should_update_downwards_knee_parabolas: Arc::new(AtomicBool::new(true)),
|
||||||
should_update_upwards_knee_parabolas: Arc::new(AtomicBool::new(true)),
|
should_update_upwards_knee_parabolas: Arc::new(AtomicBool::new(true)),
|
||||||
|
|
||||||
log2_freqs: Vec::with_capacity(complex_buffer_len),
|
ln_freqs: Vec::with_capacity(complex_buffer_len),
|
||||||
|
|
||||||
downwards_thresholds_db: Vec::with_capacity(complex_buffer_len),
|
downwards_thresholds_db: Vec::with_capacity(complex_buffer_len),
|
||||||
downwards_ratios: Vec::with_capacity(complex_buffer_len),
|
downwards_ratios: Vec::with_capacity(complex_buffer_len),
|
||||||
|
@ -444,8 +444,8 @@ impl CompressorBank {
|
||||||
pub fn update_capacity(&mut self, num_channels: usize, max_window_size: usize) {
|
pub fn update_capacity(&mut self, num_channels: usize, max_window_size: usize) {
|
||||||
let complex_buffer_len = max_window_size / 2 + 1;
|
let complex_buffer_len = max_window_size / 2 + 1;
|
||||||
|
|
||||||
self.log2_freqs
|
self.ln_freqs
|
||||||
.reserve_exact(complex_buffer_len.saturating_sub(self.log2_freqs.len()));
|
.reserve_exact(complex_buffer_len.saturating_sub(self.ln_freqs.len()));
|
||||||
|
|
||||||
self.downwards_thresholds_db
|
self.downwards_thresholds_db
|
||||||
.reserve_exact(complex_buffer_len.saturating_sub(self.downwards_thresholds_db.len()));
|
.reserve_exact(complex_buffer_len.saturating_sub(self.downwards_thresholds_db.len()));
|
||||||
|
@ -490,11 +490,11 @@ impl CompressorBank {
|
||||||
|
|
||||||
// These 2-log frequencies are needed when updating the compressor parameters, so we'll just
|
// These 2-log frequencies are needed when updating the compressor parameters, so we'll just
|
||||||
// precompute them to avoid having to repeat the same expensive computations all the time
|
// precompute them to avoid having to repeat the same expensive computations all the time
|
||||||
self.log2_freqs.resize(complex_buffer_len, 0.0);
|
self.ln_freqs.resize(complex_buffer_len, 0.0);
|
||||||
// The first one should always stay at zero, `0.0f32.log2() == NaN`.
|
// The first one should always stay at zero, `0.0f32.ln() == NaN`.
|
||||||
for (i, log2_freq) in self.log2_freqs.iter_mut().enumerate().skip(1) {
|
for (i, ln_freq) in self.ln_freqs.iter_mut().enumerate().skip(1) {
|
||||||
let freq = (i as f32 / window_size as f32) * buffer_config.sample_rate;
|
let freq = (i as f32 / window_size as f32) * buffer_config.sample_rate;
|
||||||
*log2_freq = freq.log2();
|
*ln_freq = freq.ln();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.downwards_thresholds_db.resize(complex_buffer_len, 1.0);
|
self.downwards_thresholds_db.resize(complex_buffer_len, 1.0);
|
||||||
|
@ -559,7 +559,7 @@ impl CompressorBank {
|
||||||
overlap_times: usize,
|
overlap_times: usize,
|
||||||
first_non_dc_bin: usize,
|
first_non_dc_bin: usize,
|
||||||
) {
|
) {
|
||||||
nih_debug_assert_eq!(buffer.len(), self.log2_freqs.len());
|
nih_debug_assert_eq!(buffer.len(), self.ln_freqs.len());
|
||||||
|
|
||||||
// The gain difference/reduction amounts are accumulated in `self.analyzer_input_data`. When
|
// The gain difference/reduction amounts are accumulated in `self.analyzer_input_data`. When
|
||||||
// processing the last channel, this data is divided by the channel count, the envelope
|
// processing the last channel, this data is divided by the channel count, the envelope
|
||||||
|
@ -642,7 +642,7 @@ impl CompressorBank {
|
||||||
/// call. These will be multiplied with the existing compressor thresholds and knee values to
|
/// call. These will be multiplied with the existing compressor thresholds and knee values to
|
||||||
/// get the effective values for use with sidechaining.
|
/// get the effective values for use with sidechaining.
|
||||||
pub fn process_sidechain(&mut self, sc_buffer: &mut [Complex32], channel_idx: usize) {
|
pub fn process_sidechain(&mut self, sc_buffer: &mut [Complex32], channel_idx: usize) {
|
||||||
nih_debug_assert_eq!(sc_buffer.len(), self.log2_freqs.len());
|
nih_debug_assert_eq!(sc_buffer.len(), self.ln_freqs.len());
|
||||||
|
|
||||||
self.update_sidechain_spectra(sc_buffer, channel_idx);
|
self.update_sidechain_spectra(sc_buffer, channel_idx);
|
||||||
}
|
}
|
||||||
|
@ -1006,12 +1006,12 @@ impl CompressorBank {
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
let downwards_intercept = params.compressors.downwards.threshold_offset_db.value();
|
let downwards_intercept = params.compressors.downwards.threshold_offset_db.value();
|
||||||
for (log2_freq, threshold_db) in self
|
for (ln_freq, threshold_db) in self
|
||||||
.log2_freqs
|
.ln_freqs
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.downwards_thresholds_db.iter_mut())
|
.zip(self.downwards_thresholds_db.iter_mut())
|
||||||
{
|
{
|
||||||
*threshold_db = curve.evaluate_log2(*log2_freq) + downwards_intercept;
|
*threshold_db = curve.evaluate_ln(*ln_freq) + downwards_intercept;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1021,12 +1021,12 @@ impl CompressorBank {
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
let upwards_intercept = params.compressors.upwards.threshold_offset_db.value();
|
let upwards_intercept = params.compressors.upwards.threshold_offset_db.value();
|
||||||
for (log2_freq, threshold_db) in self
|
for (ln_freq, threshold_db) in self
|
||||||
.log2_freqs
|
.ln_freqs
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.upwards_thresholds_db.iter_mut())
|
.zip(self.upwards_thresholds_db.iter_mut())
|
||||||
{
|
{
|
||||||
*threshold_db = curve.evaluate_log2(*log2_freq) + upwards_intercept;
|
*threshold_db = curve.evaluate_ln(*ln_freq) + upwards_intercept;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1041,8 +1041,8 @@ impl CompressorBank {
|
||||||
let target_ratio_recip = params.compressors.downwards.ratio.value().recip();
|
let target_ratio_recip = params.compressors.downwards.ratio.value().recip();
|
||||||
let downwards_high_freq_ratio_rolloff =
|
let downwards_high_freq_ratio_rolloff =
|
||||||
params.compressors.downwards.high_freq_ratio_rolloff.value();
|
params.compressors.downwards.high_freq_ratio_rolloff.value();
|
||||||
for (log2_freq, ratio) in self.log2_freqs.iter().zip(self.downwards_ratios.iter_mut()) {
|
for (ln_freq, ratio) in self.ln_freqs.iter().zip(self.downwards_ratios.iter_mut()) {
|
||||||
let octave_fraction = log2_freq / HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LOG2;
|
let octave_fraction = ln_freq / HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LN;
|
||||||
let rolloff_t = octave_fraction * downwards_high_freq_ratio_rolloff;
|
let rolloff_t = octave_fraction * downwards_high_freq_ratio_rolloff;
|
||||||
|
|
||||||
// If the octave fraction times the rolloff amount is high, then this should get
|
// If the octave fraction times the rolloff amount is high, then this should get
|
||||||
|
@ -1060,8 +1060,8 @@ impl CompressorBank {
|
||||||
let target_ratio_recip = params.compressors.upwards.ratio.value().recip();
|
let target_ratio_recip = params.compressors.upwards.ratio.value().recip();
|
||||||
let upwards_high_freq_ratio_rolloff =
|
let upwards_high_freq_ratio_rolloff =
|
||||||
params.compressors.upwards.high_freq_ratio_rolloff.value();
|
params.compressors.upwards.high_freq_ratio_rolloff.value();
|
||||||
for (log2_freq, ratio) in self.log2_freqs.iter().zip(self.upwards_ratios.iter_mut()) {
|
for (ln_freq, ratio) in self.ln_freqs.iter().zip(self.upwards_ratios.iter_mut()) {
|
||||||
let octave_fraction = log2_freq / HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LOG2;
|
let octave_fraction = ln_freq / HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LN;
|
||||||
let rolloff_t = octave_fraction * upwards_high_freq_ratio_rolloff;
|
let rolloff_t = octave_fraction * upwards_high_freq_ratio_rolloff;
|
||||||
|
|
||||||
let ratio_recip = (target_ratio_recip * (1.0 - rolloff_t)) + rolloff_t;
|
let ratio_recip = (target_ratio_recip * (1.0 - rolloff_t)) + rolloff_t;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/// Parameters for a curve, similar to the fields found in `ThresholdParams` but using plain floats
|
/// Parameters for a curve, similar to the fields found in `ThresholdParams` but using plain floats
|
||||||
/// instead of parameters.
|
/// instead of parameters.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
pub struct CurveParams {
|
pub struct CurveParams {
|
||||||
/// The compressor threshold at the center frequency. When sidechaining is enabled, the input
|
/// The compressor threshold at the center frequency. When sidechaining is enabled, the input
|
||||||
/// signal is gained by the inverse of this value. This replaces the input gain in the original
|
/// signal is gained by the inverse of this value. This replaces the input gain in the original
|
||||||
|
@ -13,7 +13,7 @@ pub struct CurveParams {
|
||||||
pub intercept: f32,
|
pub intercept: f32,
|
||||||
/// The center frqeuency for the target curve when sidechaining is not enabled. The curve is a
|
/// The center frqeuency for the target curve when sidechaining is not enabled. The curve is a
|
||||||
/// polynomial `threshold_db + curve_slope*x + curve_curve*(x^2)` that evaluates to a decibel
|
/// polynomial `threshold_db + curve_slope*x + curve_curve*(x^2)` that evaluates to a decibel
|
||||||
/// value, where `x = log2(center_frequency) - log2(bin_frequency)`. In other words, this is
|
/// value, where `x = ln(center_frequency) - ln(bin_frequency)`. In other words, this is
|
||||||
/// evaluated in the log/log domain for decibels and octaves.
|
/// evaluated in the log/log domain for decibels and octaves.
|
||||||
pub center_frequency: f32,
|
pub center_frequency: f32,
|
||||||
/// The slope for the curve, in the log/log domain. See the polynomial above.
|
/// The slope for the curve, in the log/log domain. See the polynomial above.
|
||||||
|
@ -33,30 +33,30 @@ pub struct CurveParams {
|
||||||
/// in decibels being the output of the equation).
|
/// in decibels being the output of the equation).
|
||||||
pub struct Curve<'a> {
|
pub struct Curve<'a> {
|
||||||
params: &'a CurveParams,
|
params: &'a CurveParams,
|
||||||
/// The 2-logarithm of [`CurveParams::cemter_frequency`].
|
/// The natural logarithm of [`CurveParams::cemter_frequency`].
|
||||||
log2_center_frequency: f32,
|
ln_center_frequency: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Curve<'a> {
|
impl<'a> Curve<'a> {
|
||||||
pub fn new(params: &'a CurveParams) -> Self {
|
pub fn new(params: &'a CurveParams) -> Self {
|
||||||
Self {
|
Self {
|
||||||
params,
|
params,
|
||||||
log2_center_frequency: params.center_frequency.log2(),
|
ln_center_frequency: params.center_frequency.ln(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the curve for the 2-logarithm of the frequency value. This can be used as an
|
/// Evaluate the curve for the natural logarithm of the frequency value. This can be used as an
|
||||||
/// optimization to avoid computing these logarithms all the time.
|
/// optimization to avoid computing these logarithms all the time.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn evaluate_log2(&self, log2_freq: f32) -> f32 {
|
pub fn evaluate_ln(&self, ln_freq: f32) -> f32 {
|
||||||
let offset = log2_freq - self.log2_center_frequency;
|
let offset = ln_freq - self.ln_center_frequency;
|
||||||
self.params.intercept + (self.params.slope * offset) + (self.params.curve * offset * offset)
|
self.params.intercept + (self.params.slope * offset) + (self.params.curve * offset * offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the curve for a value in Hertz.
|
/// Evaluate the curve for a value in Hertz.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn evaluate_plain(&self, freq: f32) -> f32 {
|
pub fn evaluate_linear(&self, freq: f32) -> f32 {
|
||||||
self.evaluate_log2(freq.log2())
|
self.evaluate_ln(freq.ln())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue