1
0
Fork 0

Slowly fade in SC envelope followers

This is much more gentle.
This commit is contained in:
Robbert van der Helm 2023-03-22 14:32:41 +01:00
parent e2ab5e16bc
commit a8fa8cde66
2 changed files with 55 additions and 32 deletions

View file

@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0.html). Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- Reworked the resetting behavior again to smoothly fade the timings in after a
reset to allow the envelop followers to more gently settle in.
## [0.4.1] - 2023-03-22 ## [0.4.1] - 2023-03-22
### Fixed ### Fixed

View file

@ -39,6 +39,10 @@ const ENVELOPE_INIT_VALUE: f32 = std::f32::consts::FRAC_1_SQRT_2 / 8.0;
const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY: f32 = 22_050.0; const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY: f32 = 22_050.0;
const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LN: f32 = 10.001068; // 22_050.0f32.ln() const HIGH_FREQ_RATIO_ROLLOFF_FREQUENCY_LN: f32 = 10.001068; // 22_050.0f32.ln()
/// The length of time over which the envelope followers fade back from being instant to using the
/// configured timingsafter the compressor bank has been reset.
const ENVELOPE_FOLLOWER_TIMING_FADE_MS: f32 = 150.0;
/// A bank of compressors so each FFT bin can be compressed individually. The vectors in this struct /// A bank of compressors so each FFT bin can be compressed individually. The vectors in this struct
/// will have a capacity of `MAX_WINDOW_SIZE / 2 + 1` and a size that matches the current complex /// will have a capacity of `MAX_WINDOW_SIZE / 2 + 1` and a size that matches the current complex
/// FFT buffer size. This is stored as a struct of arrays to make SIMD-ing easier in the future. /// FFT buffer size. This is stored as a struct of arrays to make SIMD-ing easier in the future.
@ -89,11 +93,10 @@ pub struct CompressorBank {
/// The current envelope value for this bin, in linear space. Indexed by /// The current envelope value for this bin, in linear space. Indexed by
/// `[channel_idx][compressor_idx]`. /// `[channel_idx][compressor_idx]`.
envelopes: Vec<Vec<f32>>, envelopes: Vec<Vec<f32>>,
/// This is set to `true` for the first cycle after [`CompressorBank::reset()`] was called. This /// A scaling factor for the envelope follower timings. This is set to 0 and then slowly brought
/// causes the timings to briefly be zero so they'll initialize to the buffer's current value. /// back up to 1 after after [`CompressorBank::reset()`] has been called to allow the envelope
/// This results in a more natural/less unexpected behavior because with extreme settings you /// followers to settle back in.
/// might otherwise get a huge spike after a reset envelope_followers_timing_scale: f32,
envelopes_were_reset: bool,
/// When sidechaining is enabled, this contains the per-channel frqeuency spectrum magnitudes /// When sidechaining is enabled, this contains the per-channel frqeuency spectrum magnitudes
/// for the current block. The compressor thresholds and knee values are multiplied by these /// for the current block. The compressor thresholds and knee values are multiplied by these
/// values to get the effective thresholds. /// values to get the effective thresholds.
@ -451,7 +454,7 @@ impl CompressorBank {
upwards_knee_parabola_intercept: Vec::with_capacity(complex_buffer_len), upwards_knee_parabola_intercept: Vec::with_capacity(complex_buffer_len),
envelopes: vec![Vec::with_capacity(complex_buffer_len); num_channels], envelopes: vec![Vec::with_capacity(complex_buffer_len); num_channels],
envelopes_were_reset: true, envelope_followers_timing_scale: 0.0,
sidechain_spectrum_magnitudes: vec![ sidechain_spectrum_magnitudes: vec![
Vec::with_capacity(complex_buffer_len); Vec::with_capacity(complex_buffer_len);
num_channels num_channels
@ -563,10 +566,11 @@ impl CompressorBank {
/// Clear out the envelope followers. /// Clear out the envelope followers.
pub fn reset(&mut self) { pub fn reset(&mut self) {
// This will make the timings instant for the first iteration after a reset so it can settle // This will make the timings instant for the first iteration after a reset and then slowly
// in. Otherwise suspending and resetting the plugin, or changing the window size, may // fade the timings back to their intended values so the envelope followers can settle in.
// result in some huge spikes. // Otherwise suspending and resetting the plugin, or changing the window size, may result in
self.envelopes_were_reset = true; // some huge spikes.
self.envelope_followers_timing_scale = 0.0;
// Sidechain data doesn't need to be reset as it will be overwritten immediately before use // Sidechain data doesn't need to be reset as it will be overwritten immediately before use
} }
@ -685,15 +689,23 @@ impl CompressorBank {
params: &SpectralCompressorParams, params: &SpectralCompressorParams,
overlap_times: usize, overlap_times: usize,
) { ) {
let (attack_ms, release_ms) = if self.envelopes_were_reset { let effective_sample_rate =
(0.0, 0.0) self.sample_rate / (self.window_size as f32 / overlap_times as f32);
} else {
( // The timings are scaled by `self.envelope_followers_timing_scale` to allow the envelope
params.global.compressor_attack_ms.value(), // followers to settle in quicker after a reset
params.global.compressor_release_ms.value(), let attack_ms =
) params.global.compressor_attack_ms.value() * self.envelope_followers_timing_scale;
}; let release_ms =
self.envelopes_were_reset = false; params.global.compressor_release_ms.value() * self.envelope_followers_timing_scale;
// This needs to gradually fade from 0.0 back to 1.0 after a reset
if self.envelope_followers_timing_scale < 1.0 && channel_idx == self.envelopes.len() - 1 {
let delta =
((ENVELOPE_FOLLOWER_TIMING_FADE_MS / 1000.0) * effective_sample_rate).recip();
self.envelope_followers_timing_scale =
(self.envelope_followers_timing_scale + delta).min(1.0);
}
// The coefficient the old envelope value is multiplied by when the current rectified sample // The coefficient the old envelope value is multiplied by when the current rectified sample
// value is above the envelope's value. The 0 to 1 step response retains 36.8% of the old // value is above the envelope's value. The 0 to 1 step response retains 36.8% of the old
@ -701,8 +713,6 @@ impl CompressorBank {
// The effective sample rate needs to compensate for the periodic nature of the STFT // The effective sample rate needs to compensate for the periodic nature of the STFT
// operation. Since with a 2048 sample window and 4x overlap, you'd run this function once // operation. Since with a 2048 sample window and 4x overlap, you'd run this function once
// for every 512 samples. // for every 512 samples.
let effective_sample_rate =
self.sample_rate / (self.window_size as f32 / overlap_times as f32);
let attack_old_t = if attack_ms == 0.0 { let attack_old_t = if attack_ms == 0.0 {
0.0 0.0
} else { } else {
@ -739,19 +749,25 @@ impl CompressorBank {
params: &SpectralCompressorParams, params: &SpectralCompressorParams,
overlap_times: usize, overlap_times: usize,
) { ) {
let (attack_ms, release_ms) = if self.envelopes_were_reset {
(0.0, 0.0)
} else {
(
params.global.compressor_attack_ms.value(),
params.global.compressor_release_ms.value(),
)
};
self.envelopes_were_reset = false;
// See `update_envelopes()`
let effective_sample_rate = let effective_sample_rate =
self.sample_rate / (self.window_size as f32 / overlap_times as f32); self.sample_rate / (self.window_size as f32 / overlap_times as f32);
// The timings are scaled by `self.envelope_followers_timing_scale` to allow the envelope
// followers to settle in quicker after a reset
let attack_ms =
params.global.compressor_attack_ms.value() * self.envelope_followers_timing_scale;
let release_ms =
params.global.compressor_release_ms.value() * self.envelope_followers_timing_scale;
// This needs to gradually fade from 0.0 back to 1.0 after a reset
if self.envelope_followers_timing_scale < 1.0 && channel_idx == self.envelopes.len() - 1 {
let delta =
((ENVELOPE_FOLLOWER_TIMING_FADE_MS / 1000.0) * effective_sample_rate).recip();
self.envelope_followers_timing_scale =
(self.envelope_followers_timing_scale + delta).min(1.0);
}
// See `update_envelopes()`
let attack_old_t = if attack_ms == 0.0 { let attack_old_t = if attack_ms == 0.0 {
0.0 0.0
} else { } else {