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:
parent
dd770b6bde
commit
e35886c216
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue