summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorozpv <39195175+ozpv@users.noreply.github.com>2026-06-22 04:26:18 +0000
committerGitHub <noreply@github.com>2026-06-22 04:26:18 +0000
commitb9604f08d2a329cc810ab62706f598d5fcc0da7d (patch)
treef68fbcdd3ece7588a464887aa3a501b2bb1e3e58 /src
parent6ca86f2c3f8bb4531aa623927765f4e676fe46ae (diff)
Fix duplicating the plugin instancemain
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs99
-rw-r--r--src/retain.rs109
-rw-r--r--src/window_function.rs2
-rw-r--r--src/windowed_fft.rs10
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<RetainParams>,
@@ -31,7 +30,23 @@ pub struct Retain {
initial_gui_state: Option<GuiState>,
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<WindowType>,
/// 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<Self>,
) -> 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<Self>) -> Option<Box<dyn Editor>> {
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<Self>,
) -> 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<f32>)>,
- top_indices: FxHashSet<usize>,
+ indexed_spectrum: Vec<(usize, Complex<f32>)>,
+ top_indices: FxHashSet<usize>,
}
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<f32>], 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<f32>], 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;