1
0
Fork 0

Move window function handling out of StftHelper

And apply the window function only once at the end of the process
function for the plugins that used it.
This commit is contained in:
Robbert van der Helm 2022-04-28 17:20:39 +02:00
parent dd770b6bde
commit e35886c216
4 changed files with 23 additions and 31 deletions

View file

@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use nih_plug::prelude::*; use nih_plug::prelude::*;
use nih_plug::util::window::multiply_with_window;
use realfft::num_complex::Complex32; use realfft::num_complex::Complex32;
use realfft::{RealFftPlanner, RealToComplex}; use realfft::{RealFftPlanner, RealToComplex};
use std::f32; use std::f32;
@ -81,9 +82,10 @@ impl SpectrumInput {
pub fn compute(&mut self, buffer: &Buffer) { pub fn compute(&mut self, buffer: &Buffer) {
self.stft.process_analyze_only( self.stft.process_analyze_only(
buffer, buffer,
&self.compensated_window_function,
SPECTRUM_WINDOW_OVERLAP, SPECTRUM_WINDOW_OVERLAP,
|channel_idx, real_fft_scratch_buffer| { |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 // Forward FFT, the helper has already applied window function
self.plan self.plan
.process_with_scratch( .process_with_scratch(

View file

@ -12,7 +12,7 @@ struct Stft {
/// An adapter that performs most of the overlap-add algorithm for us. /// An adapter that performs most of the overlap-add algorithm for us.
stft: util::StftHelper, 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<f32>, window_function: Vec<f32>,
/// The FFT of a simple low-pass FIR filter. /// The FFT of a simple low-pass FIR filter.
@ -126,11 +126,8 @@ impl Plugin for Stft {
// IDFT operation // IDFT operation
const GAIN_COMPENSATION: f32 = f32::consts::E / OVERLAP_TIMES as f32 / WINDOW_SIZE as f32; const GAIN_COMPENSATION: f32 = f32::consts::E / OVERLAP_TIMES as f32 / WINDOW_SIZE as f32;
self.stft.process_overlap_add( self.stft
buffer, .process_overlap_add(buffer, OVERLAP_TIMES, |_channel_idx, real_fft_buffer| {
&self.window_function,
OVERLAP_TIMES,
|_channel_idx, real_fft_buffer| {
// Forward FFT, the helper has already applied window function // Forward FFT, the helper has already applied window function
self.r2c_plan self.r2c_plan
.process_with_scratch(real_fft_buffer, &mut self.complex_fft_buffer, &mut []) .process_with_scratch(real_fft_buffer, &mut self.complex_fft_buffer, &mut [])
@ -151,8 +148,10 @@ impl Plugin for Stft {
self.c2r_plan self.c2r_plan
.process_with_scratch(&mut self.complex_fft_buffer, real_fft_buffer, &mut []) .process_with_scratch(&mut self.complex_fft_buffer, real_fft_buffer, &mut [])
.unwrap(); .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 ProcessStatus::Normal
} }

View file

@ -205,7 +205,8 @@ impl Plugin for PubertySimulator {
let window_size = self.window_size(); let window_size = self.window_size();
let overlap_times = self.overlap_times(); let overlap_times = self.overlap_times();
let sample_rate = context.transport().sample_rate; 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 // 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 // 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]; [self.params.window_size_order.value as usize - MIN_WINDOW_ORDER];
let mut smoothed_pitch_value = 0.0; let mut smoothed_pitch_value = 0.0;
self.stft.process_overlap_add( self.stft
buffer, .process_overlap_add(buffer, overlap_times, |channel_idx, real_fft_buffer| {
&self.window_function,
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 // 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 // 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. // 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 .c2r_plan
.process_with_scratch(&mut self.complex_fft_buffer, real_fft_buffer, &mut []) .process_with_scratch(&mut self.complex_fft_buffer, real_fft_buffer, &mut [])
.unwrap(); .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 ProcessStatus::Normal
} }

View file

@ -1,6 +1,5 @@
//! Utilities for buffering audio, likely used as part of a short-term Fourier transform. //! 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}; use crate::buffer::{Block, Buffer};
/// Some buffer that can be used with the [`StftHelper`]. /// 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. /// the same number of channels as the main input.
/// ///
/// TODO: Better name? /// 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. /// 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. /// Figure out the cleanest way to adapt this for the non-processing use case.
pub struct StftHelper<const NUM_SIDECHAIN_INPUTS: usize = 0> { pub struct StftHelper<const NUM_SIDECHAIN_INPUTS: usize = 0> {
@ -232,7 +232,8 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
/// Process the audio in `main_buffer` in small overlapping blocks with a window function /// 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. /// 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 /// 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 /// 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 /// written back to `main_buffer` exactly one block later, which means that this function will
@ -252,8 +253,8 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
/// # Panics /// # Panics
/// ///
/// Panics if `main_buffer` or the buffers in `sidechain_buffers` do not have the same number of /// 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 /// channels as this [`StftHelper`], or 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. /// samples as the main buffer.
/// ///
/// TODO: Add more useful ways to do STFT and other buffered operations. I just went with this /// 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 /// approach because it's what I needed myself, but generic combinators like this could
@ -261,7 +262,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
pub fn process_overlap_add<M, F>( pub fn process_overlap_add<M, F>(
&mut self, &mut self,
main_buffer: &mut M, main_buffer: &mut M,
window_function: &[f32],
overlap_times: usize, overlap_times: usize,
mut process_cb: F, mut process_cb: F,
) where ) where
@ -271,7 +271,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
self.process_overlap_add_sidechain( self.process_overlap_add_sidechain(
main_buffer, main_buffer,
[&NoSidechain; NUM_SIDECHAIN_INPUTS], [&NoSidechain; NUM_SIDECHAIN_INPUTS],
window_function,
overlap_times, overlap_times,
|channel_idx, sidechain_idx, real_fft_scratch_buffer| { |channel_idx, sidechain_idx, real_fft_scratch_buffer| {
if sidechain_idx.is_none() { if sidechain_idx.is_none() {
@ -290,7 +289,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
&mut self, &mut self,
main_buffer: &mut M, main_buffer: &mut M,
sidechain_buffers: [&S; NUM_SIDECHAIN_INPUTS], sidechain_buffers: [&S; NUM_SIDECHAIN_INPUTS],
window_function: &[f32],
overlap_times: usize, overlap_times: usize,
mut process_cb: F, mut process_cb: F,
) where ) where
@ -302,7 +300,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
main_buffer.num_channels(), main_buffer.num_channels(),
self.main_input_ring_buffers.len() self.main_input_ring_buffers.len()
); );
assert_eq!(window_function.len(), self.main_input_ring_buffers[0].len());
assert!(overlap_times > 0); assert!(overlap_times > 0);
// We'll copy samples from `*_buffer` into `*_ring_buffers` while simultaneously copying // We'll copy samples from `*_buffer` into `*_ring_buffers` while simultaneously copying
@ -395,7 +392,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
self.current_pos, self.current_pos,
sidechain_ring_buffer, sidechain_ring_buffer,
); );
multiply_with_window(&mut self.scratch_buffer, window_function);
process_cb(channel_idx, Some(sidechain_idx), &mut self.scratch_buffer); process_cb(channel_idx, Some(sidechain_idx), &mut self.scratch_buffer);
} }
} }
@ -411,11 +407,9 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
self.current_pos, self.current_pos,
input_ring_buffer, input_ring_buffer,
); );
multiply_with_window(&mut self.scratch_buffer, window_function);
process_cb(channel_idx, None, &mut self.scratch_buffer); process_cb(channel_idx, None, &mut self.scratch_buffer);
// The actual overlap-add part of the equation // The actual overlap-add part of the equation
multiply_with_window(&mut self.scratch_buffer, window_function);
add_scratch_to_ring_buffer( add_scratch_to_ring_buffer(
&self.scratch_buffer, &self.scratch_buffer,
self.current_pos, self.current_pos,
@ -433,7 +427,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
pub fn process_analyze_only<B, F>( pub fn process_analyze_only<B, F>(
&mut self, &mut self,
buffer: &B, buffer: &B,
window_function: &[f32],
overlap_times: usize, overlap_times: usize,
mut analyze_cb: F, mut analyze_cb: F,
) where ) where
@ -441,7 +434,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
F: FnMut(usize, &mut [f32]), F: FnMut(usize, &mut [f32]),
{ {
assert_eq!(buffer.num_channels(), self.main_input_ring_buffers.len()); 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); assert!(overlap_times > 0);
// See `process_overlap_add_sidechain` for an annotated version // See `process_overlap_add_sidechain` for an annotated version
@ -486,7 +478,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
self.current_pos, self.current_pos,
input_ring_buffer, input_ring_buffer,
); );
multiply_with_window(&mut self.scratch_buffer, window_function);
analyze_cb(channel_idx, &mut self.scratch_buffer); analyze_cb(channel_idx, &mut self.scratch_buffer);
} }
} }