From 88bd6307ee43f3c3609d5b2f49d440d285185765 Mon Sep 17 00:00:00 2001 From: ozpv <39195175+ozpv@users.noreply.github.com> Date: Mon, 18 May 2026 15:20:06 -0500 Subject: add window function parameter --- src/audio.rs | 8 ++++ src/gui.rs | 103 +++++++++++++++++++++++++++++++++---------------- src/params.rs | 73 ++++++++++++++++++++++++++++++----- src/window_function.rs | 28 ++++++++++++-- src/window_type.rs | 28 ++++++++++++-- src/windowed_fft.rs | 14 +++++-- 6 files changed, 201 insertions(+), 53 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 71be7d3..9e50465 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -93,6 +93,7 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread } let prev_window_size = self.params.get_window_size(); + let prev_window_type = self.params.get_window_type().as_bits(); // Receive any param updates from the main thread and/or the GUI. let has_ui_param_updates = self.params.fetch_updates(&self.shared.params); @@ -113,6 +114,13 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread } } + // update change in window type if needed + let window_type = self.params.get_window_type(); + if prev_window_type != window_type.as_bits() { + self.fft_left.window_function(&window_type); + self.fft_right.window_function(&window_type); + } + // Now let's process the audio, while splitting the processing in batches between each // sample-accurate event. for event_batch in events.input.batch() { diff --git a/src/gui.rs b/src/gui.rs index 9216d5a..5e3932a 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -4,13 +4,14 @@ use crate::{ RetainPluginMainThread, RetainPluginShared, params::{RetainParamsLocal, RetainParamsShared}, window_size::WindowSize, + window_type::WindowType, }; use baseview::{Size, WindowHandle, WindowOpenOptions, WindowScalePolicy, gl::GlConfig}; use clack_extensions::gui::{GuiApiType, GuiConfiguration, GuiSize, PluginGuiImpl, Window}; use clack_plugin::prelude::*; use egui_baseview::{ EguiWindow, GraphicsConfig, Queue, - egui::{self, ComboBox, Context, Slider}, + egui::{self, Align, ComboBox, Context, Layout, Slider}, }; use std::sync::Arc; @@ -64,41 +65,75 @@ impl RetainPluginGui { state.local_params.fetch_updates(&state.shared_params); egui::CentralPanel::default().show(egui_ctx, |ui| { - ui.heading("Retain"); - let mut order = state.local_params.get_order(); - let window_size = state.local_params.get_window_size(); - - let slider = ui.add( - Slider::new(&mut order, 0..=window_size) - .text("Order") - .logarithmic(true), - ); - - if slider.changed() { - state.local_params.set_order(order); - state.local_params.push_updates(&state.shared_params); - } - - state.local_params.has_gesture = slider.is_pointer_button_down_on(); - state.local_params.push_gesture(&state.shared_params); - - let mut window_size = WindowSize::from(state.local_params.get_window_size()); - ComboBox::from_label("Window Size") - .selected_text(window_size.as_str()) - .show_ui(ui, |ui| { - for size in WindowSize::iter() { - let display = size.as_str(); - let inner = size.inner(); - - if ui - .selectable_value(&mut window_size, size, display) - .changed() - { - state.local_params.set_window_size(inner); - state.local_params.push_updates(&state.shared_params); - } + ui.with_layout(Layout::top_down(Align::Center), |ui| { + ui.heading("Retain"); + }); + + ui.horizontal(|ui| { + ui.with_layout(Layout::top_down(Align::LEFT), |ui| { + let mut order = state.local_params.get_order(); + let window_size = state.local_params.get_window_size(); + + let slider = ui.add( + Slider::new(&mut order, 0..=window_size) + .text("Order") + .logarithmic(true), + ); + + if slider.changed() { + state.local_params.set_order(order); + state.local_params.push_updates(&state.shared_params); } + + state.local_params.has_gesture = slider.is_pointer_button_down_on(); + state.local_params.push_gesture(&state.shared_params); + + let mut window_size = + WindowSize::from(state.local_params.get_window_size()); + ComboBox::from_label("Window Size") + .selected_text(window_size.as_str()) + .show_ui(ui, |ui| { + for size in WindowSize::iter() { + let display = size.as_str(); + let inner = size.inner(); + + if ui + .selectable_value(&mut window_size, size, display) + .changed() + { + state.local_params.set_window_size(inner); + state.local_params.push_updates(&state.shared_params); + } + } + }); + }); + + ui.with_layout(Layout::right_to_left(Align::RIGHT), |ui| { + let window_type = state.local_params.get_window_type(); + let selected = window_type.as_str(); + let mut window_type = window_type.as_bits(); + ComboBox::from_label("Window Function") + .selected_text(selected) + .show_ui(ui, |ui| { + for function in WindowType::iter() { + let display = function.as_str(); + let bits = function.as_bits(); + + if ui + .selectable_value( + &mut window_type, + function.as_bits(), + display, + ) + .changed() + { + state.local_params.set_window_type_from_bits(bits); + state.local_params.push_updates(&state.shared_params); + } + } + }); }); + }); }); }, ); diff --git a/src/params.rs b/src/params.rs index 2916ace..0762cd2 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,6 +1,6 @@ //! Contains all types and implementations related to parameter management. -use crate::{RetainPluginMainThread, window_size::WindowSize}; +use crate::{RetainPluginMainThread, window_size::WindowSize, window_type::WindowType}; use clack_extensions::{ params::{ ParamDisplayWriter, ParamInfo, ParamInfoFlags, ParamInfoWriter, PluginMainThreadParams, @@ -18,12 +18,13 @@ use std::{ ffi::CStr, fmt::Write as _, io::{Read, Write as _}, - sync::atomic::{AtomicBool, AtomicUsize, Ordering}, + sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, }; /// The default value of the order parameter. const DEFAULT_ORDER: usize = 1; pub const DEFAULT_WINDOW_SIZE: WindowSize = WindowSize::Size4096; +const DEFAULT_WINDOW_TYPE: WindowType = WindowType::Hann; /// A struct that manages the parameters for our plugin. /// @@ -35,6 +36,8 @@ pub struct RetainParamsShared { order: AtomicUsize, /// Window size of FFT window_size: AtomicUsize, + /// Type of window function of FFT + window_type: AtomicU8, /// Whether a gesture is currently active or not on the parameters has_gesture: AtomicBool, } @@ -43,12 +46,14 @@ impl RetainParamsShared { /// The unique identifier for the order parameter. pub const PARAM_ORDER_ID: ClapId = ClapId::new(1); pub const PARAM_WINDOW_SIZE_ID: ClapId = ClapId::new(2); + pub const PARAM_WINDOW_TYPE_ID: ClapId = ClapId::new(3); /// Initializes the shared parameter value. pub fn new() -> Self { Self { order: AtomicUsize::new(DEFAULT_ORDER), window_size: AtomicUsize::new(DEFAULT_WINDOW_SIZE.inner()), + window_type: AtomicU8::new(DEFAULT_WINDOW_TYPE.as_bits()), has_gesture: AtomicBool::new(false), } } @@ -81,6 +86,8 @@ pub struct RetainParamsLocal { order: usize, /// The local value of the window size paramter. window_size: usize, + /// The local value of the type of window function parameter. + window_type: WindowType, /// Whether the user is currently changing the parameter, that we know of pub has_gesture: bool, } @@ -91,6 +98,7 @@ impl RetainParamsLocal { Self { order: shared.order.load(Ordering::Relaxed), window_size: shared.window_size.load(Ordering::Relaxed), + window_type: WindowType::from_bits(shared.window_type.load(Ordering::Relaxed)), has_gesture: shared.has_gesture.load(Ordering::Relaxed), } } @@ -121,6 +129,18 @@ impl RetainParamsLocal { self.window_size = value; } + /// Returns the current local window type. + #[inline] + pub fn get_window_type(&self) -> &WindowType { + &self.window_type + } + + /// Sets the current local window type from `bits`. + #[inline] + pub fn set_window_type_from_bits(&mut self, bits: u8) { + self.window_type = WindowType::from_bits(bits); + } + /// Fetch updates from the `shared` state. /// /// If any of the parameters have been updated, this returns `true`. @@ -128,10 +148,15 @@ impl RetainParamsLocal { pub fn fetch_updates(&mut self, shared: &RetainParamsShared) -> bool { let latest_order = shared.order.load(Ordering::Relaxed); let latest_window_size = shared.window_size.load(Ordering::Relaxed); + let latest_window_type = WindowType::from_bits(shared.window_type.load(Ordering::Relaxed)); - if latest_order != self.order || latest_window_size != self.order { + if latest_order != self.order + || latest_window_size != self.order + || latest_window_type != self.window_type + { self.order = latest_order; self.window_size = latest_window_size; + self.window_type = latest_window_type; true } else { @@ -146,8 +171,13 @@ impl RetainParamsLocal { pub fn push_updates(&self, shared: &RetainParamsShared) -> bool { let previous_order = shared.order.swap(self.order, Ordering::Relaxed); let previous_window_size = shared.window_size.swap(self.window_size, Ordering::Relaxed); + let previous_window_type = shared + .window_type + .swap(self.window_type.as_bits(), Ordering::Relaxed); - previous_order != self.order || previous_window_size != self.window_size + previous_order != self.order + || previous_window_size != self.window_size + || previous_window_type != self.window_type.as_bits() } /// Pushes the local gesture state to the `shared` state. @@ -232,15 +262,18 @@ impl RetainParamsLocal { struct PluginState { #[prost(uint64, tag = "1")] order: u64, - #[prost(uint64, tag = "2")] - window_size: u64, + #[prost(uint32, tag = "2")] + window_size: u32, + #[prost(uint32, tag = "3")] + window_type: u32, } impl PluginState { fn copied(local_params: &RetainParamsLocal) -> Self { Self { order: local_params.get_order() as u64, - window_size: local_params.get_window_size() as u64, + window_size: local_params.get_window_size() as u32, + window_type: local_params.get_window_type().as_bits() as u32, } } @@ -248,9 +281,13 @@ impl PluginState { self.order } - fn get_window_size(&self) -> u64 { + fn get_window_size(&self) -> u32 { self.window_size } + + fn get_window_type_bits(&self) -> u8 { + self.window_type as u8 + } } /// Implementation of the State extension. @@ -277,6 +314,8 @@ impl PluginStateImpl for RetainPluginMainThread<'_> { self.params.set_order(data.get_order() as usize); self.params.set_window_size(data.get_window_size() as usize); + self.params + .set_window_type_from_bits(data.get_window_type_bits()); self.params.push_updates(&self.shared.params); @@ -296,7 +335,7 @@ impl PluginMainThreadParams for RetainPluginMainThread<'_> { info.set(&ParamInfo { id: RetainParamsShared::PARAM_ORDER_ID, - flags: ParamInfoFlags::IS_AUTOMATABLE, + flags: ParamInfoFlags::IS_AUTOMATABLE | ParamInfoFlags::IS_STEPPED, cookie: Cookie::default(), name: b"Order", module: b"", @@ -307,7 +346,7 @@ impl PluginMainThreadParams for RetainPluginMainThread<'_> { info.set(&ParamInfo { id: RetainParamsShared::PARAM_WINDOW_SIZE_ID, - flags: ParamInfoFlags::IS_AUTOMATABLE, + flags: ParamInfoFlags::IS_READONLY, cookie: Cookie::default(), name: b"Window Size", module: b"", @@ -315,12 +354,26 @@ impl PluginMainThreadParams for RetainPluginMainThread<'_> { max_value: usize::MAX as f64, default_value: DEFAULT_WINDOW_SIZE.inner() as f64, }); + + info.set(&ParamInfo { + id: RetainParamsShared::PARAM_WINDOW_TYPE_ID, + flags: ParamInfoFlags::IS_READONLY, + cookie: Cookie::default(), + name: b"Window Function", + module: b"", + min_value: 0.0, + max_value: 4.0, + default_value: DEFAULT_WINDOW_TYPE.as_bits() as f64, + }); } fn get_value(&mut self, param_id: ClapId) -> Option { match param_id { RetainParamsShared::PARAM_ORDER_ID => Some(self.params.get_order() as f64), RetainParamsShared::PARAM_WINDOW_SIZE_ID => Some(self.params.get_window_size() as f64), + RetainParamsShared::PARAM_WINDOW_TYPE_ID => { + Some(self.params.get_window_type().as_bits() as f64) + } _ => None, } } diff --git a/src/window_function.rs b/src/window_function.rs index 09c5d31..3565ea7 100644 --- a/src/window_function.rs +++ b/src/window_function.rs @@ -11,6 +11,8 @@ pub trait WindowFunction: Send { fn reverse(&mut self, data: &mut [f32]); /// Yields the amount of samples still required to apply the window fn needed(&self) -> usize; + /// Take the steps necessary to handle a resize + fn resize(&mut self, window_size: &WindowSize); } /// Effectively a chunking function @@ -29,11 +31,15 @@ impl RectangularWindow { impl WindowFunction for RectangularWindow { fn apply(&mut self, _data: &mut VecDeque) {} - fn reverse(&mut self, _data: &mut [f32]) {} + fn reverse(&mut self, data: &mut [f32]) {} fn needed(&self) -> usize { self.window_size } + + fn resize(&mut self, window_size: &WindowSize) { + *self = Self::new(&window_size); + } } /// Hann function @@ -152,6 +158,10 @@ impl WindowFunction for HannWindow { self.window_size / 2 } } + + fn resize(&mut self, window_size: &WindowSize) { + *self = Self::new(&window_size); + } } /// Hamming function @@ -180,8 +190,8 @@ impl HammingWindow { 0.53836 - (0.46164 * f32::cos((2.0 * PI * i) / (window_size_f32))) }) .collect::>(); - - let normalize = (0..half_window_size) + + let normalize = (0..half_window_size) .map(|i| { (function[i] * function[i]) + (function[i + half_window_size] * function[i + half_window_size]) @@ -258,6 +268,10 @@ impl WindowFunction for HammingWindow { self.window_size / 2 } } + + fn resize(&mut self, window_size: &WindowSize) { + *self = Self::new(&window_size); + } } /// 4-term Blackman-Harris window function @@ -378,6 +392,10 @@ impl WindowFunction for BlackmanHarrisWindow { self.window_size / 4 } } + + fn resize(&mut self, window_size: &WindowSize) { + *self = Self::new(&window_size); + } } /// 4th power of sine window function @@ -496,4 +514,8 @@ impl WindowFunction for Sine4Window { self.window_size / 4 } } + + fn resize(&mut self, window_size: &WindowSize) { + *self = Self::new(&window_size); + } } diff --git a/src/window_type.rs b/src/window_type.rs index acbf727..be0e4df 100644 --- a/src/window_type.rs +++ b/src/window_type.rs @@ -3,12 +3,13 @@ use crate::{ window_function::{ - BlackmanHarrisWindow, HammingWindow, HannWindow, RectangularWindow, - Sine4Window, WindowFunction, + BlackmanHarrisWindow, HammingWindow, HannWindow, RectangularWindow, Sine4Window, + WindowFunction, }, window_size::WindowSize, }; +#[derive(PartialEq)] pub enum WindowType { Rectangular, Hann, @@ -18,7 +19,7 @@ pub enum WindowType { } impl WindowType { - fn into_function(self, window_size: &WindowSize) -> Box { + pub fn new_function(&self, window_size: &WindowSize) -> Box { match self { Self::Rectangular => Box::new(RectangularWindow::new(window_size)), Self::Hann => Box::new(HannWindow::new(window_size)), @@ -48,4 +49,25 @@ impl WindowType { ] .into_iter() } + + pub fn from_bits(bits: u8) -> Self { + match bits { + 0b00000000 => Self::Rectangular, + 0b00000001 => Self::Hann, + 0b00000010 => Self::Hamming, + 0b00000011 => Self::BlackmanHarris, + 0b00000100 => Self::Sine4, + _ => panic!("Invalid window type"), + } + } + + pub fn as_bits(&self) -> u8 { + match self { + Self::Rectangular => 0b00000000, + Self::Hann => 0b00000001, + Self::Hamming => 0b00000010, + Self::BlackmanHarris => 0b00000011, + Self::Sine4 => 0b00000100, + } + } } diff --git a/src/windowed_fft.rs b/src/windowed_fft.rs index 3e4e648..2cdc471 100644 --- a/src/windowed_fft.rs +++ b/src/windowed_fft.rs @@ -5,10 +5,11 @@ use crate::{ window_function::{ - BlackmanHarrisWindow, HannWindow, RectangularWindow, Sine4Window, HammingWindow, + BlackmanHarrisWindow, HammingWindow, HannWindow, RectangularWindow, Sine4Window, WindowFunction, }, window_size::WindowSize, + window_type::WindowType, }; use num_complex::Complex; use realfft::{ComplexToReal, RealFftPlanner, RealToComplex}; @@ -56,14 +57,21 @@ impl WindowedRealFft { } } - pub fn window_function(&mut self, window_function: Box) {} + pub fn window_function(&mut self, window_type: &WindowType) { + self.input.clear(); + self.output.clear(); + + let window_function = window_type.new_function(&WindowSize::from(self.window_size)); + + self.window_function = window_function; + } pub fn window_size(&mut self, window_size: WindowSize) { if window_size == self.window_size.into() { return; } - self.window_function = Box::new(HannWindow::new(&window_size)); + self.window_function.resize(&window_size); self.window_size = window_size.inner(); self.forward = self.planner.plan_fft_forward(self.window_size); -- cgit v1.2.3