Move curve calculation to a dedicated struct
So we can reuse this in the analyzer.
This commit is contained in:
parent
2de1fd563b
commit
ea4dd8ead2
|
@ -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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
61
plugins/spectral_compressor/src/curve.rs
Normal file
61
plugins/spectral_compressor/src/curve.rs
Normal 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())
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ use triple_buffer::TripleBuffer;
|
|||
|
||||
mod analyzer;
|
||||
mod compressor_bank;
|
||||
mod curve;
|
||||
mod dry_wet_mixer;
|
||||
mod editor;
|
||||
|
||||
|
|
Loading…
Reference in a new issue