1
0
Fork 0

Move curve calculation to a dedicated struct

So we can reuse this in the analyzer.
This commit is contained in:
Robbert van der Helm 2023-03-21 21:09:58 +01:00
parent 2de1fd563b
commit ea4dd8ead2
3 changed files with 81 additions and 17 deletions

View file

@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use crate::analyzer::AnalyzerData; use crate::analyzer::AnalyzerData;
use crate::curve::{Curve, CurveParams};
use crate::SpectralCompressorParams; use crate::SpectralCompressorParams;
// These are the parameter name prefixes used for the downwards and upwards compression parameters. // These are the parameter name prefixes used for the downwards and upwards compression parameters.
@ -983,32 +984,34 @@ impl CompressorBank {
/// are updated in accordance to the atomic flags set on this struct. /// are updated in accordance to the atomic flags set on this struct.
fn update_if_needed(&mut self, params: &SpectralCompressorParams) { fn update_if_needed(&mut self, params: &SpectralCompressorParams) {
// The threshold curve is a polynomial in log-log (decibels-octaves) space // The threshold curve is a polynomial in log-log (decibels-octaves) space
let intercept = params.threshold.threshold_db.value(); let curve_params = CurveParams {
// The cheeky 3 additional dB/octave attenuation is to match pink noise with the default intercept: params.threshold.threshold_db.value(),
// settings. When using sidechaining we explicitly don't want this because the curve should center_frequency: params.threshold.center_frequency.value(),
// be a flat offset to the sidechain input at the default settings. // The cheeky 3 additional dB/octave attenuation is to match pink noise with the
let slope = match params.threshold.mode.value() { // default settings. When using sidechaining we explicitly don't want this because
ThresholdMode::Internal => params.threshold.curve_slope.value() - 3.0, // the curve should be a flat offset to the sidechain input at the default settings.
ThresholdMode::SidechainMatch | ThresholdMode::SidechainCompress => { slope: match params.threshold.mode.value() {
params.threshold.curve_slope.value() ThresholdMode::Internal => params.threshold.curve_slope.value() - 3.0,
} ThresholdMode::SidechainMatch | ThresholdMode::SidechainCompress => {
params.threshold.curve_slope.value()
}
},
curve: params.threshold.curve_curve.value(),
}; };
let curve = params.threshold.curve_curve.value(); let curve = Curve::new(&curve_params);
let log2_center_freq = params.threshold.center_frequency.value().log2();
if self if self
.should_update_downwards_thresholds .should_update_downwards_thresholds
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
.is_ok() .is_ok()
{ {
let intercept = 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 (log2_freq, threshold_db) in self
.log2_freqs .log2_freqs
.iter() .iter()
.zip(self.downwards_thresholds_db.iter_mut()) .zip(self.downwards_thresholds_db.iter_mut())
{ {
let offset = log2_freq - log2_center_freq; *threshold_db = curve.evaluate_log2(*log2_freq) + downwards_intercept;
*threshold_db = intercept + (slope * offset) + (curve * offset * offset);
} }
} }
@ -1017,14 +1020,13 @@ impl CompressorBank {
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
.is_ok() .is_ok()
{ {
let intercept = 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 (log2_freq, threshold_db) in self
.log2_freqs .log2_freqs
.iter() .iter()
.zip(self.upwards_thresholds_db.iter_mut()) .zip(self.upwards_thresholds_db.iter_mut())
{ {
let offset = log2_freq - log2_center_freq; *threshold_db = curve.evaluate_log2(*log2_freq) + upwards_intercept;
*threshold_db = intercept + (slope * offset) + (curve * offset * offset);
} }
} }

View file

@ -0,0 +1,61 @@
//! Abstractions for the parameterized threshold curve.
//!
//! This was previously computed directly inside of the `CompressorBank` but this makes it easier to
//! reuse it when drawing the GUI.
/// Parameters for a curve, similar to the fields found in `ThresholdParams` but using plain floats
/// instead of parameters.
#[derive(Debug, Clone, Copy)]
pub struct CurveParams {
/// 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
/// Spectral Compressor. In the polynomial below, this is the intercept.
pub intercept: f32,
/// 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
/// value, where `x = log2(center_frequency) - log2(bin_frequency)`. In other words, this is
/// evaluated in the log/log domain for decibels and octaves.
pub center_frequency: f32,
/// The slope for the curve, in the log/log domain. See the polynomial above.
pub slope: f32,
/// The, uh, 'curve' for the curve, in the logarithmic domain. This is the third coefficient in
/// the quadratic polynomial and controls the parabolic behavior. Positive values turn the curve
/// into a v-shaped curve, while negative values attenuate everything outside of the center
/// frequency. See the polynomial above.
pub curve: f32,
}
/// Evaluates the quadratic threshold curve. This used to be calculated directly inside of the
/// compressor bank since it's so simple, but the editor also needs to compute this so it makes
/// sense to deduplicate it a bit.
///
/// The curve is evaluated in log-log space (so with octaves being the independent variable and gain
/// in decibels being the output of the equation).
pub struct Curve<'a> {
params: &'a CurveParams,
/// The 2-logarithm of [`CurveParams::cemter_frequency`].
log2_center_frequency: f32,
}
impl<'a> Curve<'a> {
pub fn new(params: &'a CurveParams) -> Self {
Self {
params,
log2_center_frequency: params.center_frequency.log2(),
}
}
/// Evaluate the curve for the 2-logarithm of the frequency value. This can be used as an
/// optimization to avoid computing these logarithms all the time.
#[inline]
pub fn evaluate_log2(&self, log2_freq: f32) -> f32 {
let offset = log2_freq - self.log2_center_frequency;
self.params.intercept + (self.params.slope * offset) + (self.params.curve * offset * offset)
}
/// Evaluate the curve for a value in Hertz.
#[inline]
pub fn evaluate_plain(&self, freq: f32) -> f32 {
self.evaluate_log2(freq.log2())
}
}

View file

@ -28,6 +28,7 @@ use triple_buffer::TripleBuffer;
mod analyzer; mod analyzer;
mod compressor_bank; mod compressor_bank;
mod curve;
mod dry_wet_mixer; mod dry_wet_mixer;
mod editor; mod editor;