diff options
| author | ozpv <39195175+ozpv@users.noreply.github.com> | 2026-05-22 22:12:15 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-22 22:12:15 +0000 |
| commit | 2bb3f394b31655a928e18fcdea3f62cf77d5496d (patch) | |
| tree | 2cf1998f7201493239ebc27b8f14d3e9ce26c44f /src | |
| parent | 7e89201c6296583336dc201d322a5912292545c7 (diff) | |
Add window resizing and automation of parameters
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio.rs | 37 | ||||
| -rw-r--r-- | src/gui.rs | 92 | ||||
| -rw-r--r-- | src/lib.rs | 8 | ||||
| -rw-r--r-- | src/params.rs | 20 |
4 files changed, 107 insertions, 50 deletions
diff --git a/src/audio.rs b/src/audio.rs index 6ad1a86..4aaac9f 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -45,8 +45,8 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread ) -> Result<Self, PluginError> { let params = Arc::clone(&shared.params); - let fft_left = WindowedRealFft::new(params.get_window_size().into()); - let fft_right = WindowedRealFft::new(params.get_window_size().into()); + let fft_left = WindowedRealFft::new(params.get_window_size()); + let fft_right = WindowedRealFft::new(params.get_window_size()); // This is where we would allocate intermediate buffers and such if we needed them. Ok(Self { @@ -66,8 +66,7 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread mut audio: Audio, events: Events, ) -> Result<ProcessStatus, PluginError> { - // First, we have to make a few sanity checks. - // We want at least a single input/output port pair, which contains channels of `f32` + // Ensure at least a single input/output port pair, which contains channels of `f32` // audio sample data. let mut port_pair = audio .port_pair(0) @@ -78,11 +77,11 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread .into_f32() .ok_or(PluginError::Message("Expected f32 input/output"))?; - let mut channel_buffers = [None, None]; - // Extract the buffer slices that we need, while making sure they are paired correctly and // check for either in-place or separate buffers. - for (pair, buf) in output_channels.iter_mut().zip(&mut channel_buffers) { + let mut output_buffers = [None, None]; + + for (pair, buf) in output_channels.iter_mut().zip(&mut output_buffers) { *buf = match pair { ChannelPair::InputOnly(_) | ChannelPair::OutputOnly(_) => None, ChannelPair::InPlace(b) => Some(b), @@ -101,12 +100,16 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread self.fft_right.window_size(window_size.clone()); if let Some(latency) = self.shared.host.get_extension::<HostLatency>() { - // should be safe + // SAFETY: should be safe as self.shared.host is a shared + // version of the main thread handle + // no reason to propagate it through all these layers let mut main = unsafe { self.shared.host.as_main_thread_unchecked() }; latency.changed(&mut main); // self.shared.host.request_restart(); } + + self.prev_window_size = Some(window_size); } // update change in window type if needed @@ -114,23 +117,26 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread if self.prev_window_type != Some(window_type.clone()) { self.fft_left.window_function(&window_type); self.fft_right.window_function(&window_type); + self.prev_window_type = Some(window_type); } - // Now let's process the audio, while splitting the processing in batches between each - // sample-accurate event. + // process the audio for event_batch in events.input.batch() { - // Process all param events in this batch for event in event_batch.events() { self.params.handle_event(event); } // Get the parameters after all changes have been handled. let order = self.params.get_order(); + + // this parameter is read-only and can only be changed in the UI let complement = self.params.get_complement(); // process samples in place here - if let [Some(left), Some(right)] = &mut channel_buffers { - for sample in left.iter_mut() { + if let [Some(left), Some(right)] = &mut output_buffers { + let range = event_batch.sample_bounds(); + + for sample in &mut left[range] { if self.fft_left.push_back_input(*sample) { self.fft_left.forward(); @@ -143,7 +149,7 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread *sample = self.fft_left.pop_front_output(); } - for sample in right.iter_mut() { + for sample in &mut right[range] { if self.fft_right.push_back_input(*sample) { self.fft_right.forward(); @@ -158,9 +164,6 @@ impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread } } - self.prev_window_size = Some(window_size); - self.prev_window_type = Some(window_type); - // Publish any parameter changes we may have received back to the GUI. // Request the on-main-thread callback, which we use to refresh the UI if it is open self.host.request_callback(); @@ -4,26 +4,75 @@ use crate::{ RetainPluginMainThread, RetainPluginShared, params::RetainParams, window_size::WindowSize, window_type::WindowType, }; +use atomic_float::AtomicF32; 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, Align, ComboBox, Context, Layout, Slider}, + egui::{Align, CentralPanel, ComboBox, Context, Layout, Slider, Vec2, ViewportCommand}, }; -use std::sync::Arc; +use std::sync::{Arc, atomic::Ordering}; + +const DEFAULT_SIZE_X: f32 = 600.0; +const DEFAULT_SIZE_Y: f32 = 350.0; + +pub struct AtomicVec2 { + x: AtomicF32, + y: AtomicF32, +} + +impl Default for AtomicVec2 { + fn default() -> Self { + Self { + x: AtomicF32::new(DEFAULT_SIZE_X), + y: AtomicF32::new(DEFAULT_SIZE_Y), + } + } +} + +impl AtomicVec2 { + pub fn new() -> Self { + Self::default() + } + + pub fn get_x(&self) -> f32 { + self.x.load(Ordering::SeqCst) + } + + pub fn set_x(&self, x: f32) { + self.x.store(x, Ordering::SeqCst); + } + + pub fn get_y(&self) -> f32 { + self.y.load(Ordering::SeqCst) + } + + pub fn set_y(&self, x: f32) { + self.y.store(x, Ordering::SeqCst); + } + + pub fn as_vec2(&self) -> Vec2 { + Vec2 { + x: self.get_x(), + y: self.get_y(), + } + } +} /// The EGUI application state struct AppState { /// A handle to the shared params state params: Arc<RetainParams>, + gui_size: Arc<AtomicVec2>, } impl AppState { /// Initializes a new [`AppState`] from the given shared params state handle - pub fn new(params: &Arc<RetainParams>) -> Self { + pub fn new(params: &Arc<RetainParams>, gui_size: &Arc<AtomicVec2>) -> Self { Self { params: Arc::clone(params), + gui_size: Arc::clone(gui_size), } } } @@ -41,7 +90,10 @@ impl RetainPluginGui { pub fn new(parent: Window<'_>, plugin_state: &RetainPluginShared) -> Self { let settings = WindowOpenOptions { title: "Retain".to_string(), - size: Size::new(600.0, 350.0), + size: Size::new( + plugin_state.gui_size.get_x().into(), + plugin_state.gui_size.get_y().into(), + ), scale: WindowScalePolicy::SystemScaleFactor, gl_config: Some(GlConfig::default()), }; @@ -52,15 +104,15 @@ impl RetainPluginGui { &parent, settings, GraphicsConfig::default(), - AppState::new(&plugin_state.params), - move |egui_ctx: &Context, _queue: &mut Queue, _state: &mut AppState| { - tx.send(egui_ctx.clone()).unwrap(); + AppState::new(&plugin_state.params, &plugin_state.gui_size), + move |ctx: &Context, _queue: &mut Queue, _state: &mut AppState| { + tx.send(ctx.clone()).unwrap(); }, - |egui_ctx: &Context, _queue: &mut Queue, state: &mut AppState| { - egui::CentralPanel::default().show(egui_ctx, |ui| { - ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.heading("Retain"); - }); + |ctx: &Context, _queue: &mut Queue, state: &mut AppState| { + ctx.send_viewport_cmd(ViewportCommand::InnerSize(state.gui_size.as_vec2())); + + CentralPanel::default().show(ctx, |ui| { + ui.heading("Retain"); ui.horizontal(|ui| { ui.with_layout(Layout::top_down(Align::LEFT), |ui| { @@ -195,21 +247,29 @@ impl PluginGuiImpl for RetainPluginMainThread<'_> { fn get_size(&mut self) -> Option<GuiSize> { Some(GuiSize { - width: 600, - height: 350, + width: self.shared.gui_size.get_x() as u32, + height: self.shared.gui_size.get_y() as u32, }) } fn can_resize(&mut self) -> bool { - false + true } - fn set_size(&mut self, _size: GuiSize) -> Result<(), PluginError> { + fn set_size(&mut self, size: GuiSize) -> Result<(), PluginError> { + self.shared.gui_size.set_x(size.width as f32); + self.shared.gui_size.set_y(size.height as f32); + + if let Some(gui) = &self.gui { + gui.request_repaint(); + } + Ok(()) } fn set_parent(&mut self, window: Window) -> Result<(), PluginError> { self.gui = Some(RetainPluginGui::new(window, self.shared)); + Ok(()) } @@ -1,4 +1,8 @@ -use crate::{audio::RetainPluginAudioProcessor, gui::RetainPluginGui, params::RetainParams}; +use crate::{ + audio::RetainPluginAudioProcessor, + gui::{AtomicVec2, RetainPluginGui}, + params::RetainParams, +}; use clack_extensions::{ audio_ports::PluginAudioPorts, gui::PluginGui, @@ -69,6 +73,7 @@ impl DefaultPluginFactory for RetainPlugin { pub struct RetainPluginShared<'a> { /// The plugin's parameter values. params: Arc<RetainParams>, + gui_size: Arc<AtomicVec2>, host: HostSharedHandle<'a>, } @@ -76,6 +81,7 @@ impl<'a> RetainPluginShared<'a> { fn new(host: HostSharedHandle<'a>) -> Self { RetainPluginShared { params: Arc::new(RetainParams::new()), + gui_size: Arc::new(AtomicVec2::new()), host, } } diff --git a/src/params.rs b/src/params.rs index 4b57dd6..fd928e0 100644 --- a/src/params.rs +++ b/src/params.rs @@ -110,22 +110,10 @@ impl RetainParams { /// If the given event is a matching parameter change event, the order parameter will be /// updated accordingly. pub fn handle_event(&self, event: &UnknownEvent) { - if let Some(CoreEventSpace::ParamValue(event)) = event.as_core_event() { - if event.param_id() == RetainParams::PARAM_ORDER_ID { - self.set_order(event.value() as usize); - } - - if event.param_id() == RetainParams::PARAM_WINDOW_SIZE_ID { - self.set_window_size((event.value() as usize).into()); - } - - if event.param_id() == RetainParams::PARAM_WINDOW_TYPE_ID { - self.set_window_type((event.value() as u8).into()); - } - - if event.param_id() == RetainParams::PARAM_COMPLEMENT_ID { - self.set_complement(event.value() != 0.0); - } + if let Some(CoreEventSpace::ParamValue(event)) = event.as_core_event() + && event.param_id() == RetainParams::PARAM_ORDER_ID + { + self.set_order(event.value() as usize); } } } |
