From ea4dd8ead254e7ccafd56bca717afb600cf12f9a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 21 Mar 2023 21:09:58 +0100 Subject: [PATCH] Move curve calculation to a dedicated struct So we can reuse this in the analyzer. --- .../src/compressor_bank.rs | 36 +++++------ plugins/spectral_compressor/src/curve.rs | 61 +++++++++++++++++++ plugins/spectral_compressor/src/lib.rs | 1 + 3 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 plugins/spectral_compressor/src/curve.rs diff --git a/plugins/spectral_compressor/src/compressor_bank.rs b/plugins/spectral_compressor/src/compressor_bank.rs index 28e0d916..63ed9ca8 100644 --- a/plugins/spectral_compressor/src/compressor_bank.rs +++ b/plugins/spectral_compressor/src/compressor_bank.rs @@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use crate::analyzer::AnalyzerData; +use crate::curve::{Curve, CurveParams}; use crate::SpectralCompressorParams; // 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. fn update_if_needed(&mut self, params: &SpectralCompressorParams) { // The threshold curve is a polynomial in log-log (decibels-octaves) space - let intercept = params.threshold.threshold_db.value(); - // The cheeky 3 additional dB/octave attenuation is to match pink noise with the default - // settings. When using sidechaining we explicitly don't want this because the curve should - // be a flat offset to the sidechain input at the default settings. - let slope = match params.threshold.mode.value() { - ThresholdMode::Internal => params.threshold.curve_slope.value() - 3.0, - ThresholdMode::SidechainMatch | ThresholdMode::SidechainCompress => { - params.threshold.curve_slope.value() - } + let curve_params = CurveParams { + intercept: params.threshold.threshold_db.value(), + center_frequency: params.threshold.center_frequency.value(), + // The cheeky 3 additional dB/octave attenuation is to match pink noise with the + // default settings. When using sidechaining we explicitly don't want this because + // the curve should be a flat offset to the sidechain input at the default settings. + slope: match params.threshold.mode.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 log2_center_freq = params.threshold.center_frequency.value().log2(); + let curve = Curve::new(&curve_params); if self .should_update_downwards_thresholds .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) .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 .log2_freqs .iter() .zip(self.downwards_thresholds_db.iter_mut()) { - let offset = log2_freq - log2_center_freq; - *threshold_db = intercept + (slope * offset) + (curve * offset * offset); + *threshold_db = curve.evaluate_log2(*log2_freq) + downwards_intercept; } } @@ -1017,14 +1020,13 @@ impl CompressorBank { .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) .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 .log2_freqs .iter() .zip(self.upwards_thresholds_db.iter_mut()) { - let offset = log2_freq - log2_center_freq; - *threshold_db = intercept + (slope * offset) + (curve * offset * offset); + *threshold_db = curve.evaluate_log2(*log2_freq) + upwards_intercept; } } diff --git a/plugins/spectral_compressor/src/curve.rs b/plugins/spectral_compressor/src/curve.rs new file mode 100644 index 00000000..711a596f --- /dev/null +++ b/plugins/spectral_compressor/src/curve.rs @@ -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()) + } +} diff --git a/plugins/spectral_compressor/src/lib.rs b/plugins/spectral_compressor/src/lib.rs index df4690f8..ee8e4b05 100644 --- a/plugins/spectral_compressor/src/lib.rs +++ b/plugins/spectral_compressor/src/lib.rs @@ -28,6 +28,7 @@ use triple_buffer::TripleBuffer; mod analyzer; mod compressor_bank; +mod curve; mod dry_wet_mixer; mod editor;