1
0
Fork 0

Add spectral sidechain compression to SC

This commit is contained in:
Robbert van der Helm 2022-08-20 16:37:20 +02:00
parent 5010ce5e1a
commit 9550fe0d10

View file

@ -136,8 +136,14 @@ pub enum ThresholdMode {
/// how how much of the other channel values to mix in before multiplying the sidechain gain /// how how much of the other channel values to mix in before multiplying the sidechain gain
/// values with the thresholds. /// values with the thresholds.
#[id = "sidechain"] #[id = "sidechain"]
#[name = "Sidechain"] #[name = "Sidechain Matching"]
Sidechain, SidechainMatch,
/// Compress the input signal based on the sidechain signal's activity. Can be used to
/// spectrally duck the input, or to amplify parts of the input based on holes in the sidechain
/// signal.
#[id = "sidechain_compress"]
#[name = "Sidechain Compression"]
SidechainCompress,
} }
/// Contains the compressor parameters for both the upwards and downwards compressor banks. /// Contains the compressor parameters for both the upwards and downwards compressor banks.
@ -519,11 +525,26 @@ impl CompressorBank {
nih_debug_assert_eq!(buffer.len(), self.log2_freqs.len()); nih_debug_assert_eq!(buffer.len(), self.log2_freqs.len());
self.update_if_needed(params); self.update_if_needed(params);
self.update_envelopes(buffer, channel_idx, params, overlap_times, skip_bins_below);
match params.threshold.mode.value() { match params.threshold.mode.value() {
ThresholdMode::Internal => self.compress(buffer, channel_idx, params, skip_bins_below), ThresholdMode::Internal => {
ThresholdMode::Sidechain => { self.update_envelopes(buffer, channel_idx, params, overlap_times, skip_bins_below);
self.compress_sidechain(buffer, channel_idx, params, skip_bins_below) self.compress(buffer, channel_idx, params, skip_bins_below)
}
ThresholdMode::SidechainMatch => {
self.update_envelopes(buffer, channel_idx, params, overlap_times, skip_bins_below);
self.compress_sidechain_match(buffer, channel_idx, params, skip_bins_below)
}
ThresholdMode::SidechainCompress => {
// This mode uses regular compression, but the envelopes are computed from the
// sidechain input magnitudes. These are already set in `process_sidechain`. This
// separate envelope updating function is needed for the channel linking.
self.update_envelopes_sidechain(
channel_idx,
params,
overlap_times,
skip_bins_below,
);
self.compress(buffer, channel_idx, params, skip_bins_below)
} }
}; };
} }
@ -586,6 +607,72 @@ impl CompressorBank {
} }
} }
/// The same as [`update_envelopes()`][Self::update_envelopes()], but based on the previously
/// set sidechain bin magnitudes. This allows for channel linking.
/// [`process_sidechain()`][Self::process_sidechain()] needs to be called for all channels
/// before this function can be used to set the magnitude spectra.
fn update_envelopes_sidechain(
&mut self,
channel_idx: usize,
params: &SpectralCompressorParams,
overlap_times: usize,
skip_bins_below: usize,
) {
// See `update_envelopes()`
let effective_sample_rate =
self.sample_rate / (self.window_size as f32 / overlap_times as f32);
let attack_old_t = if params.global.compressor_attack_ms.value == 0.0 {
0.0
} else {
(-1.0 / (params.global.compressor_attack_ms.value / 1000.0 * effective_sample_rate))
.exp()
};
let attack_new_t = 1.0 - attack_old_t;
let release_old_t = if params.global.compressor_release_ms.value == 0.0 {
0.0
} else {
(-1.0 / (params.global.compressor_release_ms.value / 1000.0 * effective_sample_rate))
.exp()
};
let release_new_t = 1.0 - release_old_t;
// For the channel linking
let num_channels = self.sidechain_spectrum_magnitudes.len() as f32;
let other_channels_t = params.threshold.sc_channel_link.value / num_channels;
let this_channel_t = 1.0 - (other_channels_t * (num_channels - 1.0));
for (bin_idx, envelope) in self.envelopes[channel_idx]
.iter_mut()
.enumerate()
.skip(skip_bins_below)
{
// In this mode the envelopes are set based on the sidechain signal, taking channel
// linking into account
let sidechain_magnitude: f32 = self
.sidechain_spectrum_magnitudes
.iter()
.enumerate()
.map(|(sidechain_channel_idx, magnitudes)| {
let t = if sidechain_channel_idx == channel_idx {
this_channel_t
} else {
other_channels_t
};
unsafe { magnitudes.get_unchecked(bin_idx) * t }
})
.sum::<f32>();
if *envelope > sidechain_magnitude {
// Release stage
*envelope = (release_old_t * *envelope) + (release_new_t * sidechain_magnitude);
} else {
// Attack stage
*envelope = (attack_old_t * *envelope) + (attack_new_t * sidechain_magnitude);
}
}
}
/// Update the spectral data using the sidechain input /// Update the spectral data using the sidechain input
fn update_sidechain_spectra(&mut self, sc_buffer: &mut [Complex32], channel_idx: usize) { fn update_sidechain_spectra(&mut self, sc_buffer: &mut [Complex32], channel_idx: usize) {
nih_debug_assert!(channel_idx < self.sidechain_spectrum_magnitudes.len()); nih_debug_assert!(channel_idx < self.sidechain_spectrum_magnitudes.len());
@ -598,8 +685,8 @@ impl CompressorBank {
} }
} }
/// Actually do the thing. [`Self::update_envelopes()`] must have been called before calling /// Actually do the thing. [`Self::update_envelopes()`] or
/// this. /// [`Self::update_envelopes_sidechain()`] must have been called before calling this.
/// ///
/// # Panics /// # Panics
/// ///
@ -630,6 +717,8 @@ impl CompressorBank {
assert!(self.upwards_ratio_recips.len() == buffer.len()); assert!(self.upwards_ratio_recips.len() == buffer.len());
assert!(self.upwards_knee_starts.len() == buffer.len()); assert!(self.upwards_knee_starts.len() == buffer.len());
assert!(self.upwards_knee_ends.len() == buffer.len()); assert!(self.upwards_knee_ends.len() == buffer.len());
// NOTE: In the sidechain compression mode these envelopes are computed from the sidechain
// signal instead of the main input
for (bin_idx, (bin, envelope)) in buffer for (bin_idx, (bin, envelope)) in buffer
.iter_mut() .iter_mut()
.zip(self.envelopes[channel_idx].iter()) .zip(self.envelopes[channel_idx].iter())
@ -679,13 +768,13 @@ impl CompressorBank {
} }
/// The same as [`compress()`][Self::compress()], but multiplying the threshold and knee values /// The same as [`compress()`][Self::compress()], but multiplying the threshold and knee values
/// with the sidehcain gains. /// with the sidechain gains.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the buffer does not have the same length as the one that was passed to the last /// Panics if the buffer does not have the same length as the one that was passed to the last
/// `resize()` call. /// `resize()` call.
fn compress_sidechain( fn compress_sidechain_match(
&self, &self,
buffer: &mut [Complex32], buffer: &mut [Complex32],
channel_idx: usize, channel_idx: usize,
@ -792,7 +881,9 @@ impl CompressorBank {
// be a flat offset to the sidechain input at the default settings. // be a flat offset to the sidechain input at the default settings.
let slope = match params.threshold.mode.value() { let slope = match params.threshold.mode.value() {
ThresholdMode::Internal => params.threshold.curve_slope.value - 3.0, ThresholdMode::Internal => params.threshold.curve_slope.value - 3.0,
ThresholdMode::Sidechain => params.threshold.curve_slope.value, ThresholdMode::SidechainMatch | ThresholdMode::SidechainCompress => {
params.threshold.curve_slope.value
}
}; };
let curve = params.threshold.curve_curve.value; let curve = params.threshold.curve_curve.value;
let log2_center_freq = params.threshold.center_frequency.value.log2(); let log2_center_freq = params.threshold.center_frequency.value.log2();