summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorozpv <39195175+ozpv@users.noreply.github.com>2026-05-18 15:20:06 -0500
committerGitHub <noreply@github.com>2026-05-18 15:20:06 -0500
commit88bd6307ee43f3c3609d5b2f49d440d285185765 (patch)
tree70b96bac6e1c4734f31b90e24526ea49b67fcc48 /src
parent13e872165ed41e032246eddbe1b0750154ce7259 (diff)
add window function parameter
Diffstat (limited to 'src')
-rw-r--r--src/audio.rs8
-rw-r--r--src/gui.rs103
-rw-r--r--src/params.rs73
-rw-r--r--src/window_function.rs28
-rw-r--r--src/window_type.rs28
-rw-r--r--src/windowed_fft.rs14
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<f64> {
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<f32>) {}
- 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::<Vec<f32>>();
-
- 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<dyn WindowFunction> {
+ pub fn new_function(&self, window_size: &WindowSize) -> Box<dyn WindowFunction> {
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<dyn WindowFunction>) {}
+ 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);