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/>.
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(

View file

@ -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<f32>,
/// 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
}

View file

@ -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
}

View file

@ -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<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
/// 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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
/// # 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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
pub fn process_overlap_add<M, F>(
&mut self,
main_buffer: &mut M,
window_function: &[f32],
overlap_times: usize,
mut process_cb: F,
) where
@ -271,7 +271,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
&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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
pub fn process_analyze_only<B, F>(
&mut self,
buffer: &B,
window_function: &[f32],
overlap_times: usize,
mut analyze_cb: F,
) where
@ -441,7 +434,6 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
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<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
self.current_pos,
input_ring_buffer,
);
multiply_with_window(&mut self.scratch_buffer, window_function);
analyze_cb(channel_idx, &mut self.scratch_buffer);
}
}