From 9550fe0d10b35d2cd0ecc71ad7fbe5c00e2c1e2f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 20 Aug 2022 16:37:20 +0200 Subject: [PATCH] Add spectral sidechain compression to SC --- .../src/compressor_bank.rs | 113 ++++++++++++++++-- 1 file changed, 102 insertions(+), 11 deletions(-) diff --git a/plugins/spectral_compressor/src/compressor_bank.rs b/plugins/spectral_compressor/src/compressor_bank.rs index 12c6f1bd..4ba880a4 100644 --- a/plugins/spectral_compressor/src/compressor_bank.rs +++ b/plugins/spectral_compressor/src/compressor_bank.rs @@ -136,8 +136,14 @@ pub enum ThresholdMode { /// how how much of the other channel values to mix in before multiplying the sidechain gain /// values with the thresholds. #[id = "sidechain"] - #[name = "Sidechain"] - Sidechain, + #[name = "Sidechain Matching"] + 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. @@ -519,11 +525,26 @@ impl CompressorBank { nih_debug_assert_eq!(buffer.len(), self.log2_freqs.len()); self.update_if_needed(params); - self.update_envelopes(buffer, channel_idx, params, overlap_times, skip_bins_below); match params.threshold.mode.value() { - ThresholdMode::Internal => self.compress(buffer, channel_idx, params, skip_bins_below), - ThresholdMode::Sidechain => { - self.compress_sidechain(buffer, channel_idx, params, skip_bins_below) + ThresholdMode::Internal => { + self.update_envelopes(buffer, channel_idx, params, overlap_times, 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::(); + + 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 fn update_sidechain_spectra(&mut self, sc_buffer: &mut [Complex32], channel_idx: usize) { 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 - /// this. + /// Actually do the thing. [`Self::update_envelopes()`] or + /// [`Self::update_envelopes_sidechain()`] must have been called before calling this. /// /// # Panics /// @@ -630,6 +717,8 @@ impl CompressorBank { assert!(self.upwards_ratio_recips.len() == buffer.len()); assert!(self.upwards_knee_starts.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 .iter_mut() .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 - /// with the sidehcain gains. + /// with the sidechain gains. /// /// # Panics /// /// Panics if the buffer does not have the same length as the one that was passed to the last /// `resize()` call. - fn compress_sidechain( + fn compress_sidechain_match( &self, buffer: &mut [Complex32], channel_idx: usize, @@ -792,7 +881,9 @@ impl CompressorBank { // 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::Sidechain => params.threshold.curve_slope.value, + ThresholdMode::SidechainMatch | ThresholdMode::SidechainCompress => { + params.threshold.curve_slope.value + } }; let curve = params.threshold.curve_curve.value; let log2_center_freq = params.threshold.center_frequency.value.log2();