From b9604f08d2a329cc810ab62706f598d5fcc0da7d Mon Sep 17 00:00:00 2001 From: ozpv <39195175+ozpv@users.noreply.github.com> Date: Mon, 22 Jun 2026 04:26:18 +0000 Subject: Fix duplicating the plugin instance --- src/lib.rs | 99 ++++++++++++++++++++++++++++++-------------- src/retain.rs | 109 +++++++++++++++++++++++++------------------------ src/window_function.rs | 2 +- src/windowed_fft.rs | 10 ++--- 4 files changed, 130 insertions(+), 90 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 88e1df0..f011c4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,11 +19,10 @@ use rtrb::RingBuffer; use std::sync::Arc; pub const DEFAULT_WINDOW_TYPE: WindowType = WindowType::Hann; - const MIN_WINDOW_WIDTH: u32 = 600; const MIN_WINDOW_HEIGHT: u32 = 350; - -const GUI_TO_AUDIO_CHANNEL_CAPACITY: usize = 128; +const GUI_TO_AUDIO_CHANNEL_CAPACITY: usize = 32; +const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub struct Retain { params: Arc, @@ -31,7 +30,23 @@ pub struct Retain { initial_gui_state: Option, fft_left: WindowedRealFft, fft_right: WindowedRealFft, - retain_algorithm: RetainAlgorithm, + retain_algorithm: RetainAlgorithm, +} + +impl Retain { + fn window_size_update(&mut self) { + let window_size = self.params.window_size.value() as usize; + + self.fft_left.window_size(window_size.into()); + self.fft_right.window_size(window_size.into()); + } + + fn window_type_update(&mut self) { + let window_type = self.params.window_type.value(); + + self.fft_left.window_function(&window_type); + self.fft_right.window_function(&window_type); + } } impl Default for Retain { @@ -51,7 +66,7 @@ impl Default for Retain { }), fft_left, fft_right, - retain_algorithm: RetainAlgorithm::new(), + retain_algorithm: RetainAlgorithm::new(), } } } @@ -68,12 +83,12 @@ pub struct RetainParams { /// the window size of the fft /// this effects the frequency resolution and can make for cool effects - #[id = "window_size"] + #[id = "size"] pub window_size: IntParam, /// the type of window function used to reduce spectral leakage or clicking sounds /// playing with this makes a big difference in output - #[id = "window_type"] + #[id = "type"] pub window_type: EnumParam, /// whether the plugin retains or removes the highest magnitude frequencies @@ -127,7 +142,7 @@ impl Plugin for Retain { const VENDOR: &'static str = "haemolacriaa"; const URL: &'static str = "https://git.haemolacriaa.com/retain/"; const EMAIL: &'static str = ""; - const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + const VERSION: &'static str = VERSION; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout { main_input_channels: NonZeroU32::new(2), main_output_channels: NonZeroU32::new(2), @@ -144,11 +159,17 @@ impl Plugin for Retain { _buffer_config: &BufferConfig, context: &mut impl InitContext, ) -> bool { + // I figured out one must do this for parameters that only change by messages + // otherwise duplicating the instance will reset these to default and the GUI will be incorrect + self.window_size_update(); + self.window_type_update(); + context.set_latency_samples(self.params.window_size.value().cast_unsigned()); true } + // TODO add a spectrum analyzer for eye candy fn editor(&mut self, _async_executor: AsyncExecutor) -> Option> { let params = self.params.clone(); let egui_state = params.editor_state.clone(); @@ -162,7 +183,16 @@ impl Plugin for Retain { ResizableWindow::new("retainwindow") .min_size(Vec2::new(MIN_WINDOW_WIDTH as f32, MIN_WINDOW_HEIGHT as f32)) .show(ui, egui_state.as_ref(), |ui| { - CentralPanel::default().show_inside(ui, |ui| { + CentralPanel::default().show_inside(ui, |ui| { + /* TODO find a way to request the window size from the host to fix a resize glitch in reaper + let size = egui_state.size(); + let size = Vec2 { + x: size.0 as f32, + y: size.1 as f32, + }; + ui.send_viewport_cmd(ViewportCommand::InnerSize(size)); + */ + ui.heading("Retain"); ui.horizontal(|ui| { @@ -251,7 +281,11 @@ impl Plugin for Retain { }); }); }); - }); + + ui.with_layout(Layout::bottom_up(Align::LEFT), |ui| { + ui.label(format!("{VERSION} by {}", Self::VENDOR)) + }); + }); }); }, ) @@ -263,7 +297,7 @@ impl Plugin for Retain { _aux: &mut AuxiliaryBuffers, context: &mut impl ProcessContext, ) -> ProcessStatus { - // this is to prevent multiple updates from slowing this thread down if it somehow happens + // this is to prevent multiple updates from slowing this thread down if the channel is backed up let mut previously_updated_window_size = false; let mut previously_updated_window_type = false; @@ -274,12 +308,11 @@ impl Plugin for Retain { continue; } - let window_size = self.params.window_size.value() as usize; + self.window_size_update(); - self.fft_left.window_size(window_size.into()); - self.fft_right.window_size(window_size.into()); + let window_size = self.params.window_size.value().cast_unsigned(); - context.set_latency_samples(window_size as u32); + context.set_latency_samples(window_size); previously_updated_window_size = true; } @@ -288,25 +321,26 @@ impl Plugin for Retain { continue; } - let window_type = self.params.window_type.value(); - - self.fft_left.window_function(&window_type); - self.fft_right.window_function(&window_type); + self.window_type_update(); previously_updated_window_type = true; } } } - + if let [left, right] = buffer.as_slice() { for sample in left.iter_mut() { if self.fft_left.push_back_input(*sample) { self.fft_left.forward(); - - let order = self.params.order.value() as usize; - let complement = self.params.complement.value(); - self.retain_algorithm.calculate(self.fft_left.get_spectrum(), order, complement); + let order = self.params.order.value() as usize; + let complement = self.params.complement.value(); + + self.retain_algorithm.calculate( + self.fft_left.get_spectrum(), + order, + complement, + ); self.fft_left.inverse(); self.fft_left.clear_input(); @@ -318,11 +352,15 @@ impl Plugin for Retain { for sample in right.iter_mut() { if self.fft_right.push_back_input(*sample) { self.fft_right.forward(); - - let order = self.params.order.value() as usize; - let complement = self.params.complement.value(); - self.retain_algorithm.calculate(self.fft_right.get_spectrum(), order, complement); + let order = self.params.order.value() as usize; + let complement = self.params.complement.value(); + + self.retain_algorithm.calculate( + self.fft_right.get_spectrum(), + order, + complement, + ); self.fft_right.inverse(); self.fft_right.clear_input(); @@ -342,8 +380,9 @@ impl Plugin for Retain { impl ClapPlugin for Retain { const CLAP_ID: &'static str = "com.haemolacriaa.retain"; - const CLAP_DESCRIPTION: Option<&'static str> = - Some("Retain is a real-time audio plugin that retains the nth largest magnitude frequencies in a signal"); + const CLAP_DESCRIPTION: Option<&'static str> = Some( + "Retain is a real-time audio plugin that retains the nth largest magnitude frequencies in a signal", + ); const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); const CLAP_SUPPORT_URL: Option<&'static str> = None; const CLAP_FEATURES: &'static [ClapFeature] = &[ diff --git a/src/retain.rs b/src/retain.rs index 8e11164..ad166be 100644 --- a/src/retain.rs +++ b/src/retain.rs @@ -3,68 +3,69 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; /// I thought about doing this to avoid allocating new memory in the audio thread pub struct RetainAlgorithm { - indexed_spectrum: Vec<(usize, Complex)>, - top_indices: FxHashSet, + indexed_spectrum: Vec<(usize, Complex)>, + top_indices: FxHashSet, } impl RetainAlgorithm { - pub fn new() -> Self { - let max_size = (32768 / 2) + 1; - - Self { - indexed_spectrum: Vec::with_capacity(max_size), - top_indices: FxHashSet::with_capacity_and_hasher(max_size, FxBuildHasher::default()), - } - } - - pub fn calculate(&mut self, spectrum: &mut [Complex], n: usize, complement: bool) { - // you'd be keeping the entire signal - if n >= spectrum.len() && !complement { - return; - } + pub fn new() -> Self { + let max_size = (32768 / 2) + 1; - if n == 0 && complement { - return; - } + Self { + indexed_spectrum: Vec::with_capacity(max_size), + top_indices: FxHashSet::with_capacity_and_hasher(max_size, FxBuildHasher::default()), + } + } - // keeping nothing at all - if n == 0 && !complement { - for phasor in spectrum { - *phasor = Complex::ZERO; - } + pub fn calculate(&mut self, spectrum: &mut [Complex], n: usize, complement: bool) { + // you'd be keeping the entire signal + if n >= spectrum.len() && !complement { + return; + } - return; - } + if n == 0 && complement { + return; + } - if n >= spectrum.len() && complement { - for phasor in spectrum { - *phasor = Complex::ZERO; - } + // keeping nothing at all + if n == 0 && !complement { + for phasor in spectrum { + *phasor = Complex::ZERO; + } - return; - } - - self.indexed_spectrum.clear(); - for (i, phasor) in spectrum.iter().copied().enumerate() { - self.indexed_spectrum.push((i, phasor)); - } + return; + } - let target = spectrum.len() - n; - self.indexed_spectrum.select_nth_unstable_by(target, |(_, c0), (_, c1)| c0.norm().total_cmp(&c1.norm())); - - self.top_indices.clear(); - for i in self.indexed_spectrum[target..].iter().map(|&(i, _)| i) { - self.top_indices.insert(i); - } + if n >= spectrum.len() && complement { + for phasor in spectrum { + *phasor = Complex::ZERO; + } - for (i, phasor) in spectrum.iter_mut().enumerate() { - if self.top_indices.contains(&i) && complement { - *phasor = Complex::ZERO; - } + return; + } - if !self.top_indices.contains(&i) && !complement { - *phasor = Complex::ZERO; - } - } - } -} \ No newline at end of file + self.indexed_spectrum.clear(); + for (i, phasor) in spectrum.iter().copied().enumerate() { + self.indexed_spectrum.push((i, phasor)); + } + + let target = spectrum.len() - n; + self.indexed_spectrum + .select_nth_unstable_by(target, |(_, c0), (_, c1)| c0.norm().total_cmp(&c1.norm())); + + self.top_indices.clear(); + for i in self.indexed_spectrum[target..].iter().map(|&(i, _)| i) { + self.top_indices.insert(i); + } + + for (i, phasor) in spectrum.iter_mut().enumerate() { + if self.top_indices.contains(&i) && complement { + *phasor = Complex::ZERO; + } + + if !self.top_indices.contains(&i) && !complement { + *phasor = Complex::ZERO; + } + } + } +} diff --git a/src/window_function.rs b/src/window_function.rs index c6aef77..98b034b 100644 --- a/src/window_function.rs +++ b/src/window_function.rs @@ -103,7 +103,7 @@ impl WindowFunction for HannWindow { // set the output samples // in other functions this will also include normalizing in a loop - data[..half_window_size].copy_from_slice(&self.overlap_add[..half_window_size]); + data[..half_window_size].copy_from_slice(&self.overlap_add[..half_window_size]); // shift the saved overlap to become the next overlap self.overlap_add.rotate_left(half_window_size); diff --git a/src/windowed_fft.rs b/src/windowed_fft.rs index b3a6bfa..0f1fae8 100644 --- a/src/windowed_fft.rs +++ b/src/windowed_fft.rs @@ -22,8 +22,8 @@ pub struct WindowedRealFft { } impl WindowedRealFft { - // clippy is very wrong here and passing window_size by ref will result in errors - #[allow(clippy::needless_pass_by_value)] + // clippy is very wrong here and passing window_size by ref will result in errors + #[allow(clippy::needless_pass_by_value)] pub fn new(window_size: WindowSize) -> Self { let window_function = DEFAULT_WINDOW_TYPE.new_function(&window_size); let window_size = window_size.inner(); @@ -38,7 +38,7 @@ impl WindowedRealFft { let spectrum = vec![Complex::ZERO; (window_size / 2) + 1]; // scratches should theoretically be the same length for forward and inverse operations - // So I reuse it on to save on allocations + // So I reuse it on to save on allocations let scratch = forward.make_scratch_vec(); Self { @@ -63,8 +63,8 @@ impl WindowedRealFft { self.window_function = window_function; } - // clippy is very wrong here and passing window_size by ref will result in errors - #[allow(clippy::needless_pass_by_value)] + // clippy is very wrong here and passing window_size by ref will result in errors + #[allow(clippy::needless_pass_by_value)] pub fn window_size(&mut self, window_size: WindowSize) { if window_size == self.window_size.into() { return; -- cgit v1.2.3