diff --git a/plugins/diopser/src/editor.rs b/plugins/diopser/src/editor.rs index 36ca3202..7f3f3e8c 100644 --- a/plugins/diopser/src/editor.rs +++ b/plugins/diopser/src/editor.rs @@ -20,7 +20,6 @@ use nih_plug::prelude::{Editor, Plugin}; use nih_plug_vizia::vizia::prelude::*; use nih_plug_vizia::widgets::*; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState, ViziaTheming}; -use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use self::button::SafeModeButton; @@ -30,8 +29,11 @@ use crate::Diopser; mod analyzer; mod button; +mod safe_mode; mod xy_pad; +pub use safe_mode::SafeModeClamper; + const EDITOR_WIDTH: u32 = 600; const EDITOR_HEIGHT: u32 = 490; @@ -49,8 +51,7 @@ pub(crate) struct Data { pub(crate) spectrum: Arc>, /// Whether the safe mode button is enabled. The number of filter stages is capped at 40 while /// this is active. - /// TODO: Actually hook up safe mode - pub(crate) safe_mode: Arc, + pub(crate) safe_mode_clamper: SafeModeClamper, } impl Model for Data {} @@ -106,7 +107,7 @@ fn top_bar(cx: &mut Context) { .with_label("Automation Precision") .id("automation-precision"); - SafeModeButton::new(cx, Data::safe_mode, "Safe mode").left(Pixels(10.0)); + SafeModeButton::new(cx, Data::safe_mode_clamper, "Safe mode").left(Pixels(10.0)); ParamButton::new(cx, Data::params, |params| ¶ms.bypass) .for_bypass() diff --git a/plugins/diopser/src/editor/button.rs b/plugins/diopser/src/editor/button.rs index e59a9602..0f65de1b 100644 --- a/plugins/diopser/src/editor/button.rs +++ b/plugins/diopser/src/editor/button.rs @@ -15,13 +15,13 @@ // along with this program. If not, see . use nih_plug_vizia::vizia::prelude::*; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -/// A custom toggleable button coupled to an `Arc>> { +pub struct SafeModeButton> { lens: L, /// The number of (fractional) scrolled lines that have not yet been turned into parameter @@ -29,8 +29,8 @@ pub struct SafeModeButton>> { scrolled_lines: f32, } -impl>> SafeModeButton { - /// Creates a new button bound to the `Arc`. +impl> SafeModeButton { + /// Creates a new button bound to the [`SafeModeClamper`]. pub fn new(cx: &mut Context, lens: L, label: impl Res) -> Handle where T: ToString, @@ -42,13 +42,13 @@ impl>> SafeModeButton { .build(cx, move |cx| { Label::new(cx, label); }) - .checked(lens.map(|v| v.load(Ordering::Relaxed))) + .checked(lens.map(|v| v.status())) // We'll pretend this is a param-button, so this class is used for assigning a unique color .class("safe-mode") } } -impl>> View for SafeModeButton { +impl> View for SafeModeButton { fn element(&self) -> Option<&'static str> { // Reuse the styling from param-button Some("param-button") @@ -60,9 +60,10 @@ impl>> View for SafeModeButton { WindowEvent::MouseDown(MouseButton::Left) | WindowEvent::MouseDoubleClick(MouseButton::Left) | WindowEvent::MouseTripleClick(MouseButton::Left) => { - // We can just unconditionally toggle the boolean here - let atomic = self.lens.get(cx); - atomic.fetch_xor(true, Ordering::AcqRel); + // We can just unconditionally toggle the boolean here. When safe mode is enabled + // this immediately clamps the affected parameters to their new range. + let safe_mode_clamper = self.lens.get(cx); + safe_mode_clamper.toggle(cx); meta.consume(); } @@ -70,13 +71,13 @@ impl>> View for SafeModeButton { self.scrolled_lines += scroll_y; if self.scrolled_lines.abs() >= 1.0 { - let atomic = self.lens.get(cx); + let safe_mode_clamper = self.lens.get(cx); if self.scrolled_lines >= 1.0 { - atomic.store(true, Ordering::SeqCst); + safe_mode_clamper.enable(cx); self.scrolled_lines -= 1.0; } else { - atomic.store(false, Ordering::SeqCst); + safe_mode_clamper.disable(cx); self.scrolled_lines += 1.0; } } diff --git a/plugins/diopser/src/editor/safe_mode.rs b/plugins/diopser/src/editor/safe_mode.rs new file mode 100644 index 00000000..240a654e --- /dev/null +++ b/plugins/diopser/src/editor/safe_mode.rs @@ -0,0 +1,48 @@ +//! Utilities for Diopser's safe-mode mechanism. + +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use nih_plug_vizia::vizia::prelude::EventContext; + +use crate::params::DiopserParams; + +/// Restricts the ranges of several parameters when enabled. This makes it more difficult to +/// generate load resonances with Diopser's default settings. +#[derive(Debug, Clone)] +pub struct SafeModeClamper { + /// Whether the safe mode toggle has been enabled. + enabled: Arc, +} + +impl SafeModeClamper { + pub fn new(params: Arc) -> Self { + Self { + enabled: params.safe_mode.clone(), + } + } + + /// Return the current status of the safe mode swtich. + pub fn status(&self) -> bool { + self.enabled.load(Ordering::Relaxed) + } + + /// Enable or disable safe mode. Enabling safe mode immediately clamps the parameters to their + /// new restricted ranges. + pub fn toggle(&self, cx: &mut EventContext) { + // TODO: Restrict the parameter ranges when the button is enabled + self.enabled.fetch_xor(true, Ordering::Relaxed); + } + + /// Enable safe mode. Enabling safe mode immediately clamps the parameters to their new + /// restricted ranges. + pub fn enable(&self, cx: &mut EventContext) { + // TODO: Restrict the parameter ranges when the button is enabled + self.enabled.store(true, Ordering::Relaxed); + } + + /// Disable safe mode. + pub fn disable(&self, cx: &mut EventContext) { + self.enabled.store(false, Ordering::Relaxed); + } +} diff --git a/plugins/diopser/src/lib.rs b/plugins/diopser/src/lib.rs index ac464a4d..b6d6f9b4 100644 --- a/plugins/diopser/src/lib.rs +++ b/plugins/diopser/src/lib.rs @@ -20,6 +20,7 @@ compile_error!("Compiling without SIMD support is currently not supported"); use atomic_float::AtomicF32; +use editor::SafeModeClamper; use nih_plug::prelude::*; use std::simd::f32x2; use std::sync::atomic::{AtomicBool, Ordering}; @@ -130,7 +131,7 @@ impl Plugin for Diopser { sample_rate: self.sample_rate.clone(), spectrum: self.spectrum_output.clone(), - safe_mode: self.params.safe_mode.clone(), + safe_mode_clamper: SafeModeClamper::new(self.params.clone()), }, self.params.editor_state.clone(), )