diff --git a/plugins/diopser/src/spectrum.rs b/plugins/diopser/src/spectrum.rs index 083838b1..5ee1e4b1 100644 --- a/plugins/diopser/src/spectrum.rs +++ b/plugins/diopser/src/spectrum.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . use nih_plug::prelude::*; +use nih_plug::util::window::multiply_with_window; use realfft::num_complex::Complex32; use realfft::{RealFftPlanner, RealToComplex}; use std::f32; @@ -81,9 +82,10 @@ impl SpectrumInput { pub fn compute(&mut self, buffer: &Buffer) { self.stft.process_analyze_only( buffer, - &self.compensated_window_function, SPECTRUM_WINDOW_OVERLAP, |channel_idx, real_fft_scratch_buffer| { + multiply_with_window(real_fft_scratch_buffer, &self.compensated_window_function); + // Forward FFT, the helper has already applied window function self.plan .process_with_scratch( diff --git a/plugins/examples/stft/src/lib.rs b/plugins/examples/stft/src/lib.rs index 535ed8ae..ddecb3aa 100644 --- a/plugins/examples/stft/src/lib.rs +++ b/plugins/examples/stft/src/lib.rs @@ -12,7 +12,7 @@ struct Stft { /// An adapter that performs most of the overlap-add algorithm for us. stft: util::StftHelper, - /// A Hann window function, passed to the overlap-add helper. + /// A Hann window function, applied after the IDFT operation to minimize time domain aliasing. window_function: Vec, /// The FFT of a simple low-pass FIR filter. @@ -126,11 +126,8 @@ impl Plugin for Stft { // IDFT operation const GAIN_COMPENSATION: f32 = f32::consts::E / OVERLAP_TIMES as f32 / WINDOW_SIZE as f32; - self.stft.process_overlap_add( - buffer, - &self.window_function, - OVERLAP_TIMES, - |_channel_idx, real_fft_buffer| { + self.stft + .process_overlap_add(buffer, OVERLAP_TIMES, |_channel_idx, real_fft_buffer| { // Forward FFT, the helper has already applied window function self.r2c_plan .process_with_scratch(real_fft_buffer, &mut self.complex_fft_buffer, &mut []) @@ -151,8 +148,10 @@ impl Plugin for Stft { self.c2r_plan .process_with_scratch(&mut self.complex_fft_buffer, real_fft_buffer, &mut []) .unwrap(); - }, - ); + + // Apply the window function. We can do this either before the DFT or after the IDFT + util::window::multiply_with_window(real_fft_buffer, &self.window_function); + }); ProcessStatus::Normal } diff --git a/plugins/puberty_simulator/src/lib.rs b/plugins/puberty_simulator/src/lib.rs index 04dbae26..1946b622 100644 --- a/plugins/puberty_simulator/src/lib.rs +++ b/plugins/puberty_simulator/src/lib.rs @@ -205,7 +205,8 @@ impl Plugin for PubertySimulator { let window_size = self.window_size(); let overlap_times = self.overlap_times(); let sample_rate = context.transport().sample_rate; - let gain_compensation: f32 = 1.0 / (overlap_times as f32).log2() / window_size as f32; + let gain_compensation: f32 = + (overlap_times as f32 / 2.0).sqrt().recip() / window_size as f32; // If the window size has changed since the last process call, reset the buffers and chance // our latency. All of these buffers already have enough capacity @@ -221,11 +222,8 @@ impl Plugin for PubertySimulator { [self.params.window_size_order.value as usize - MIN_WINDOW_ORDER]; let mut smoothed_pitch_value = 0.0; - self.stft.process_overlap_add( - buffer, - &self.window_function, - overlap_times, - |channel_idx, real_fft_buffer| { + self.stft + .process_overlap_add(buffer, overlap_times, |channel_idx, real_fft_buffer| { // This loop runs whenever there's a block ready, so we can't easily do any post- or // pre-processing without muddying up the interface. But if this is channel 0, then // we're dealing with a new block. We'll use this for our parameter smoothing. @@ -298,8 +296,10 @@ impl Plugin for PubertySimulator { .c2r_plan .process_with_scratch(&mut self.complex_fft_buffer, real_fft_buffer, &mut []) .unwrap(); - }, - ); + + // Apply the window function. We can do this either before the DFT or after the IDFT + util::window::multiply_with_window(real_fft_buffer, &self.window_function); + }); ProcessStatus::Normal } diff --git a/src/util/stft.rs b/src/util/stft.rs index 5fc61ea6..ae35d1d4 100644 --- a/src/util/stft.rs +++ b/src/util/stft.rs @@ -1,6 +1,5 @@ //! Utilities for buffering audio, likely used as part of a short-term Fourier transform. -use super::window::multiply_with_window; use crate::buffer::{Block, Buffer}; /// Some buffer that can be used with the [`StftHelper`]. @@ -30,6 +29,7 @@ pub trait StftInputMut: StftInput { /// the same number of channels as the main input. /// /// TODO: Better name? +/// TODO: This needs an option that adds padding to the `real_fft_window` /// TODO: We may need something like this purely for analysis, e.g. for showing spectrums in a GUI. /// Figure out the cleanest way to adapt this for the non-processing use case. pub struct StftHelper { @@ -232,7 +232,8 @@ impl StftHelper { /// Process the audio in `main_buffer` in small overlapping blocks with a window function /// applied, adding up the results for the main buffer so they can be written back to the host. - /// The window overlap amount is compensated automatically when adding up these samples. + /// Since there are a couple ways to do it, the window function needs to be applied in the + /// process callbacks. Check the [`nih_plug::util::window`] module for more information. /// Whenever a new block is available, `process_cb()` gets called with a new audio block of the /// specified size with the windowing function already applied. The summed reults will then be /// written back to `main_buffer` exactly one block later, which means that this function will @@ -252,8 +253,8 @@ impl StftHelper { /// # Panics /// /// Panics if `main_buffer` or the buffers in `sidechain_buffers` do not have the same number of - /// channels as this [`StftHelper`], if the sidechain buffers do not contain the same number of - /// samples as the main buffer, or if the window function does not match the block size. + /// channels as this [`StftHelper`], or if the sidechain buffers do not contain the same number of + /// samples as the main buffer. /// /// TODO: Add more useful ways to do STFT and other buffered operations. I just went with this /// approach because it's what I needed myself, but generic combinators like this could @@ -261,7 +262,6 @@ impl StftHelper { pub fn process_overlap_add( &mut self, main_buffer: &mut M, - window_function: &[f32], overlap_times: usize, mut process_cb: F, ) where @@ -271,7 +271,6 @@ impl StftHelper { self.process_overlap_add_sidechain( main_buffer, [&NoSidechain; NUM_SIDECHAIN_INPUTS], - window_function, overlap_times, |channel_idx, sidechain_idx, real_fft_scratch_buffer| { if sidechain_idx.is_none() { @@ -290,7 +289,6 @@ impl StftHelper { &mut self, main_buffer: &mut M, sidechain_buffers: [&S; NUM_SIDECHAIN_INPUTS], - window_function: &[f32], overlap_times: usize, mut process_cb: F, ) where @@ -302,7 +300,6 @@ impl StftHelper { main_buffer.num_channels(), self.main_input_ring_buffers.len() ); - assert_eq!(window_function.len(), self.main_input_ring_buffers[0].len()); assert!(overlap_times > 0); // We'll copy samples from `*_buffer` into `*_ring_buffers` while simultaneously copying @@ -395,7 +392,6 @@ impl StftHelper { self.current_pos, sidechain_ring_buffer, ); - multiply_with_window(&mut self.scratch_buffer, window_function); process_cb(channel_idx, Some(sidechain_idx), &mut self.scratch_buffer); } } @@ -411,11 +407,9 @@ impl StftHelper { self.current_pos, input_ring_buffer, ); - multiply_with_window(&mut self.scratch_buffer, window_function); process_cb(channel_idx, None, &mut self.scratch_buffer); // The actual overlap-add part of the equation - multiply_with_window(&mut self.scratch_buffer, window_function); add_scratch_to_ring_buffer( &self.scratch_buffer, self.current_pos, @@ -433,7 +427,6 @@ impl StftHelper { pub fn process_analyze_only( &mut self, buffer: &B, - window_function: &[f32], overlap_times: usize, mut analyze_cb: F, ) where @@ -441,7 +434,6 @@ impl StftHelper { F: FnMut(usize, &mut [f32]), { assert_eq!(buffer.num_channels(), self.main_input_ring_buffers.len()); - assert_eq!(window_function.len(), self.main_input_ring_buffers[0].len()); assert!(overlap_times > 0); // See `process_overlap_add_sidechain` for an annotated version @@ -486,7 +478,6 @@ impl StftHelper { self.current_pos, input_ring_buffer, ); - multiply_with_window(&mut self.scratch_buffer, window_function); analyze_cb(channel_idx, &mut self.scratch_buffer); } }