1
0
Fork 0

Add a not very functional process function

This commit is contained in:
Robbert van der Helm 2022-07-22 19:28:55 +02:00
parent 9aa4a64e5f
commit 147cf3f633
2 changed files with 59 additions and 7 deletions

View file

@ -18,6 +18,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use nih_plug::prelude::*; use nih_plug::prelude::*;
use realfft::num_complex::Complex32;
/// Type alias for the compressor parameters. These two are split up so the parameter list/tree /// Type alias for the compressor parameters. These two are split up so the parameter list/tree
/// looks a bit nicer. /// looks a bit nicer.
@ -58,7 +59,12 @@ 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>>,
// TODO: Parameters for the envelope followers so we can actuall ydo soemthing useful. /// The window size this compressor bank was configured for. This is used to compute the
/// coefficients for the envelope followers in the process function.
window_size: usize,
/// The sample rate this compressor bank was configured for. This is used to compute the
/// coefficients for the envelope followers in the process function.
sample_rate: f32,
} }
#[derive(Params)] #[derive(Params)]
@ -352,6 +358,8 @@ impl CompressorBank {
upwards_ratios: Vec::with_capacity(complex_buffer_len), upwards_ratios: 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],
window_size: 0,
sample_rate: 1.0,
} }
} }
@ -402,6 +410,9 @@ impl CompressorBank {
envelopes.resize(complex_buffer_len, 0.0); envelopes.resize(complex_buffer_len, 0.0);
} }
self.window_size = window_size;
self.sample_rate = buffer_config.sample_rate;
// The compressors need to be updated on the next processing cycle // The compressors need to be updated on the next processing cycle
self.should_update_downwards_thresholds self.should_update_downwards_thresholds
.store(true, Ordering::SeqCst); .store(true, Ordering::SeqCst);
@ -420,6 +431,42 @@ impl CompressorBank {
} }
} }
/// Apply the magnitude compression to a buffer of FFT bins. The compressors are first updated
/// if needed. The overlap amount is needed to compute the effective sample rate. The
/// `skip_bins_below` argument is used to avoid compressing DC bins, or the neighbouring bins
/// the DC signal may have been convolved into because of the Hann window function.
pub fn process(
&mut self,
buffer: &mut [Complex32],
params @ (_, compressor): CompressorParams,
overlap_times: usize,
skip_bins_below: usize,
) {
assert_eq!(buffer.len(), self.log2_freqs.len());
self.update_if_needed(params);
// 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 after the attack time has elapsed, and current value is 63.2% of the way towards 1.
// 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
// for every 512 samples.
let effective_sample_rate =
self.sample_rate / (self.window_size as f32 / overlap_times as f32);
let attack_retention = (compressor.compressor_attack_ms.value / 1000.0
* effective_sample_rate)
.recip()
.exp();
// The same as `attack_retention`, but for the release phase of the envelope follower
let release_retention = (compressor.compressor_release_ms.value / 1000.0
* effective_sample_rate)
.recip()
.exp();
// TODO: Actually compress things
}
/// Update the compressors if needed. This is called just before processing, and the compressors /// Update the compressors if needed. This is called just before processing, and the compressors
/// are updated in accordance to the atomic flags set on this struct. /// are updated in accordance to the atomic flags set on this struct.
fn update_if_needed(&mut self, (threshold, compressor): CompressorParams) { fn update_if_needed(&mut self, (threshold, compressor): CompressorParams) {

View file

@ -298,6 +298,10 @@ impl Plugin for SpectralCompressor {
let fft_plan = &mut self.plan_for_order.as_mut().unwrap() let fft_plan = &mut self.plan_for_order.as_mut().unwrap()
[self.params.window_size_order.value as usize - MIN_WINDOW_ORDER]; [self.params.window_size_order.value as usize - MIN_WINDOW_ORDER];
let num_bins = self.complex_fft_buffer.len(); let num_bins = self.complex_fft_buffer.len();
// The Hann window function spreads the DC signal out slightly, so we'll clear
// all 0-20 Hz bins for this.
let highest_dcish_bin_idx =
(20.0 / ((self.buffer_config.sample_rate / 2.0) / num_bins as f32)).floor() as usize;
// The overlap gain compensation is based on a squared Hann window, which will sum perfectly // The overlap gain compensation is based on a squared Hann window, which will sum perfectly
// at four times overlap or higher. We'll apply a regular Hann window before the analysis // at four times overlap or higher. We'll apply a regular Hann window before the analysis
@ -335,16 +339,17 @@ impl Plugin for SpectralCompressor {
.process_with_scratch(real_fft_buffer, &mut self.complex_fft_buffer, &mut []) .process_with_scratch(real_fft_buffer, &mut self.complex_fft_buffer, &mut [])
.unwrap(); .unwrap();
// TODO: Do the thing // This is where the magic happens
self.compressor_bank.process(
&mut self.complex_fft_buffer,
(&self.params.threhold, &self.params.compressors),
overlap_times,
highest_dcish_bin_idx,
);
// The DC and other low frequency bins doesn't contain much semantic meaning anymore // The DC and other low frequency bins doesn't contain much semantic meaning anymore
// after all of this, so it only ends up consuming headroom. // after all of this, so it only ends up consuming headroom.
if self.params.dc_filter.value { if self.params.dc_filter.value {
// The Hann window function spreads the DC signal out slightly, so we'll clear
// all 0-20 Hz bins for this.
let highest_dcish_bin_idx = (20.0
/ ((self.buffer_config.sample_rate / 2.0) / num_bins as f32))
.floor() as usize;
self.complex_fft_buffer[..highest_dcish_bin_idx + 1].fill(Complex32::default()); self.complex_fft_buffer[..highest_dcish_bin_idx + 1].fill(Complex32::default());
} }