diff --git a/Cargo.lock b/Cargo.lock index b2dc5cfb..cc3e39d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3756,10 +3756,12 @@ dependencies = [ name = "spectral_compressor" version = "0.3.0" dependencies = [ + "crossbeam", "nih_plug", "nih_plug_vizia", "open", "realfft", + "serde", ] [[package]] diff --git a/plugins/spectral_compressor/Cargo.toml b/plugins/spectral_compressor/Cargo.toml index 428e8415..b2715678 100644 --- a/plugins/spectral_compressor/Cargo.toml +++ b/plugins/spectral_compressor/Cargo.toml @@ -16,4 +16,6 @@ nih_plug_vizia = { path = "../../nih_plug_vizia" } realfft = "3.0" # For the GUI +crossbeam = "0.8" open = "3.0" +serde = { version = "1.0", features = ["derive"] } diff --git a/plugins/spectral_compressor/src/editor.rs b/plugins/spectral_compressor/src/editor.rs index 09148e55..4e80bc4e 100644 --- a/plugins/spectral_compressor/src/editor.rs +++ b/plugins/spectral_compressor/src/editor.rs @@ -18,15 +18,36 @@ use nih_plug::prelude::*; use nih_plug_vizia::vizia::prelude::*; use nih_plug_vizia::widgets::*; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState, ViziaTheming}; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use crate::{SpectralCompressor, SpectralCompressorParams}; +/// The entire GUI's width, in logical pixels. +const EXPANDED_GUI_WIDTH: u32 = 1360; +/// The width of the GUI's main part containing the controls. +const COLLAPSED_GUI_WIDTH: u32 = 680; +/// The entire GUI's height, in logical pixels. +const GUI_HEIGHT: u32 = 535; // I couldn't get `LayoutType::Grid` to work as expected, so we'll fake a 4x4 grid with // hardcoded column widths const COLUMN_WIDTH: Units = Pixels(330.0); + const DARKER_GRAY: Color = Color::rgb(0x69, 0x69, 0x69); +/// The editor's mode. Essentially just a boolean to indicate whether the visualizer is shown or +/// not. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum EditorMode { + // These serialization names are hardcoded so the variants can be renamed them later without + // breaking preset compatibility + #[serde(rename = "collapsed")] + Collapsed, + #[default] + #[serde(rename = "visualizer-shown")] + Visualizer, +} + #[derive(Lens)] struct Data { params: Arc, @@ -36,7 +57,7 @@ impl Model for Data {} // Makes sense to also define this here, makes it a bit easier to keep track of pub(crate) fn default_state() -> Arc { - ViziaState::new(|| (680, 535)) + ViziaState::new(|| (EXPANDED_GUI_WIDTH, GUI_HEIGHT)) } pub(crate) fn create( @@ -54,117 +75,123 @@ pub(crate) fn create( ResizeHandle::new(cx); - VStack::new(cx, |cx| { - HStack::new(cx, |cx| { - Label::new(cx, "Spectral Compressor") - .font_family(vec![FamilyOwned::Name(String::from( - assets::NOTO_SANS_THIN, - ))]) - .font_size(30.0) - .on_mouse_down(|_, _| { - // Try to open the plugin's page when clicking on the title. If this fails - // then that's not a problem - let result = open::that(SpectralCompressor::URL); - if cfg!(debug) && result.is_err() { - nih_debug_assert_failure!("Failed to open web browser: {:?}", result); - } - }); - Label::new(cx, SpectralCompressor::VERSION) - .color(DARKER_GRAY) - .top(Stretch(1.0)) - .bottom(Pixels(4.0)) - .left(Pixels(2.0)); - }) - .height(Pixels(30.0)) - .right(Pixels(-17.0)) - .bottom(Pixels(-5.0)) - .top(Pixels(10.0)); - - HStack::new(cx, |cx| { - make_column(cx, "Globals", |cx| { - GenericUi::new(cx, Data::params.map(|p| p.global.clone())); - }); - - make_column(cx, "Threshold", |cx| { - GenericUi::new(cx, Data::params.map(|p| p.threshold.clone())); - - Label::new( - cx, - "Parameter ranges and overal gain staging are still subject to change. If \ - you use this in a project, make sure to bounce things to audio just in \ - case they'll sound different later.", - ) - .font_size(11.0) - .left(Pixels(15.0)) - .right(Pixels(8.0)) - // The column isn't tall enough without this, for some reason - .bottom(Pixels(20.0)) - .width(Stretch(1.0)); - }); - }) - .height(Auto) - .width(Stretch(1.0)); - - HStack::new(cx, |cx| { - make_column(cx, "Upwards", |cx| { - // We don't want to show the 'Upwards' prefix here, but it should still be in - // the parameter name so the parameter list makes sense - let upwards_compressor_params = - Data::params.map(|p| p.compressors.upwards.clone()); - GenericUi::new_custom( - cx, - upwards_compressor_params.clone(), - move |cx, param_ptr| { - let upwards_compressor_params = upwards_compressor_params.clone(); - HStack::new(cx, move |cx| { - Label::new( - cx, - unsafe { param_ptr.name() } - .strip_prefix("Upwards ") - .expect("Expected parameter name prefix, this is a bug"), - ) - .class("label"); - - GenericUi::draw_widget(cx, upwards_compressor_params, param_ptr); - }) - .class("row"); - }, - ); - }); - - make_column(cx, "Downwards", |cx| { - let downwards_compressor_params = - Data::params.map(|p| p.compressors.downwards.clone()); - GenericUi::new_custom( - cx, - downwards_compressor_params.clone(), - move |cx, param_ptr| { - let downwards_compressor_params = downwards_compressor_params.clone(); - HStack::new(cx, move |cx| { - Label::new( - cx, - unsafe { param_ptr.name() } - .strip_prefix("Downwards ") - .expect("Expected parameter name prefix, this is a bug"), - ) - .class("label"); - - GenericUi::draw_widget(cx, downwards_compressor_params, param_ptr); - }) - .class("row"); - }, - ); - }); - }) - .height(Auto) - .width(Stretch(1.0)); - }) - .row_between(Pixels(15.0)) - .child_left(Stretch(1.0)) - .child_right(Stretch(1.0)); + HStack::new(cx, |cx| { + main_column(cx); + }); }) } +fn main_column(cx: &mut Context) { + VStack::new(cx, |cx| { + HStack::new(cx, |cx| { + Label::new(cx, "Spectral Compressor") + .font_family(vec![FamilyOwned::Name(String::from( + assets::NOTO_SANS_THIN, + ))]) + .font_size(30.0) + .on_mouse_down(|_, _| { + // Try to open the plugin's page when clicking on the title. If this fails + // then that's not a problem + let result = open::that(SpectralCompressor::URL); + if cfg!(debug) && result.is_err() { + nih_debug_assert_failure!("Failed to open web browser: {:?}", result); + } + }); + Label::new(cx, SpectralCompressor::VERSION) + .color(DARKER_GRAY) + .top(Stretch(1.0)) + .bottom(Pixels(4.0)) + .left(Pixels(2.0)); + }) + .height(Pixels(30.0)) + .right(Pixels(-22.0)) + .bottom(Pixels(-5.0)) + .top(Pixels(10.0)); + + HStack::new(cx, |cx| { + make_column(cx, "Globals", |cx| { + GenericUi::new(cx, Data::params.map(|p| p.global.clone())); + }); + + make_column(cx, "Threshold", |cx| { + GenericUi::new(cx, Data::params.map(|p| p.threshold.clone())); + + Label::new( + cx, + "Parameter ranges and overal gain staging are still subject to change. If you \ + use this in a project, make sure to bounce things to audio just in case \ + they'll sound different later.", + ) + .font_size(11.0) + .left(Pixels(15.0)) + .right(Pixels(8.0)) + // The column isn't tall enough without this, for some reason + .bottom(Pixels(20.0)) + .width(Stretch(1.0)); + }); + }) + .height(Auto) + .width(Stretch(1.0)); + + HStack::new(cx, |cx| { + make_column(cx, "Upwards", |cx| { + // We don't want to show the 'Upwards' prefix here, but it should still be in + // the parameter name so the parameter list makes sense + let upwards_compressor_params = Data::params.map(|p| p.compressors.upwards.clone()); + GenericUi::new_custom( + cx, + upwards_compressor_params.clone(), + move |cx, param_ptr| { + let upwards_compressor_params = upwards_compressor_params.clone(); + HStack::new(cx, move |cx| { + Label::new( + cx, + unsafe { param_ptr.name() } + .strip_prefix("Upwards ") + .expect("Expected parameter name prefix, this is a bug"), + ) + .class("label"); + + GenericUi::draw_widget(cx, upwards_compressor_params, param_ptr); + }) + .class("row"); + }, + ); + }); + + make_column(cx, "Downwards", |cx| { + let downwards_compressor_params = + Data::params.map(|p| p.compressors.downwards.clone()); + GenericUi::new_custom( + cx, + downwards_compressor_params.clone(), + move |cx, param_ptr| { + let downwards_compressor_params = downwards_compressor_params.clone(); + HStack::new(cx, move |cx| { + Label::new( + cx, + unsafe { param_ptr.name() } + .strip_prefix("Downwards ") + .expect("Expected parameter name prefix, this is a bug"), + ) + .class("label"); + + GenericUi::draw_widget(cx, downwards_compressor_params, param_ptr); + }) + .class("row"); + }, + ); + }); + }) + .height(Auto) + .width(Stretch(1.0)); + }) + .width(Pixels(COLLAPSED_GUI_WIDTH as f32)) + .row_between(Pixels(15.0)) + .child_left(Stretch(1.0)) + .child_right(Stretch(1.0)); +} + fn make_column(cx: &mut Context, title: &str, contents: impl FnOnce(&mut Context)) { VStack::new(cx, |cx| { Label::new(cx, title) diff --git a/plugins/spectral_compressor/src/lib.rs b/plugins/spectral_compressor/src/lib.rs index 8f6c4282..4147fb60 100644 --- a/plugins/spectral_compressor/src/lib.rs +++ b/plugins/spectral_compressor/src/lib.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use crossbeam::atomic::AtomicCell; +use editor::EditorMode; use nih_plug::prelude::*; use nih_plug_vizia::ViziaState; use realfft::num_complex::Complex32; @@ -82,6 +84,10 @@ pub struct SpectralCompressorParams { /// restored. #[persist = "editor-state"] pub editor_state: Arc, + /// The mode the editor is currently in. Essentially just a fancy boolean to indicate whether + /// it's expanded or not. + #[persist = "editor-mode"] + pub editor_mode: Arc>, // NOTE: These `Arc`s are only here temporarily to work around Vizia's Lens requirements so we // can use the generic UIs @@ -239,6 +245,7 @@ impl SpectralCompressorParams { pub fn new(compressor_bank: &compressor_bank::CompressorBank) -> Self { SpectralCompressorParams { editor_state: editor::default_state(), + editor_mode: Arc::default(), // TODO: Do still enable per-block smoothing for these settings, because why not. This // will require updating the compressor bank.