diff --git a/Cargo.lock b/Cargo.lock index 542a8fb1..9971bacc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "gain" version = "0.1.0" @@ -154,6 +160,7 @@ dependencies = [ "lazy_static", "nih_plug_derive", "parking_lot", + "raw-window-handle", "serde", "serde_json", "vst3-sys", @@ -209,6 +216,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "raw-window-handle" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" +dependencies = [ + "cty", +] + [[package]] name = "redox_syscall" version = "0.2.10" diff --git a/Cargo.toml b/Cargo.toml index 09dee7d7..6b79be1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,11 @@ members = [ [dependencies] nih_plug_derive = { path = "nih_plug_derive" } -crossbeam = "0.8" cfg-if = "1.0" +crossbeam = "0.8" lazy_static = "1.4" parking_lot = "0.12" +raw-window-handle = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/atomic-reference-count" } diff --git a/plugins/examples/gain/src/lib.rs b/plugins/examples/gain/src/lib.rs index 164eac6f..1c28a521 100644 --- a/plugins/examples/gain/src/lib.rs +++ b/plugins/examples/gain/src/lib.rs @@ -18,8 +18,8 @@ extern crate nih_plug; use nih_plug::{ - formatters, util, Buffer, BufferConfig, BusConfig, Plugin, ProcessContext, ProcessStatus, - Vst3Plugin, + formatters, util, Buffer, BufferConfig, BusConfig, NoEditor, Plugin, ProcessContext, + ProcessStatus, Vst3Plugin, }; use nih_plug::{BoolParam, FloatParam, Param, Params, Range, Smoother, SmoothingStyle}; use parking_lot::RwLock; @@ -86,6 +86,8 @@ impl Default for GainParams { } impl Plugin for Gain { + type Editor = NoEditor; + const NAME: &'static str = "Gain"; const VENDOR: &'static str = "Moist Plugins GmbH"; const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; diff --git a/plugins/examples/sine/src/lib.rs b/plugins/examples/sine/src/lib.rs index 08d54dd5..b54a8fda 100644 --- a/plugins/examples/sine/src/lib.rs +++ b/plugins/examples/sine/src/lib.rs @@ -18,8 +18,8 @@ extern crate nih_plug; use nih_plug::{ - formatters, util, Buffer, BufferConfig, BusConfig, Plugin, ProcessContext, ProcessStatus, - Vst3Plugin, + formatters, util, Buffer, BufferConfig, BusConfig, NoEditor, Plugin, ProcessContext, + ProcessStatus, Vst3Plugin, }; use nih_plug::{BoolParam, FloatParam, Param, Params, Range, Smoother, SmoothingStyle}; use std::f32::consts; @@ -34,6 +34,8 @@ struct Sine { /// The current phase of the sine wave, always kept between in `[0, 1]`. phase: f32, + samples: f32, + /// The frequency if the active note, if triggered by MIDI. midi_note_freq: f32, /// A simple attack and release envelope to avoid clicks. @@ -62,6 +64,7 @@ impl Default for Sine { sample_rate: 1.0, phase: 0.0, + samples: 0.0, midi_note_freq: 1.0, midi_note_gain: Smoother::new(SmoothingStyle::Linear(5.0)), } @@ -120,6 +123,8 @@ impl Sine { } impl Plugin for Sine { + type Editor = NoEditor; + const NAME: &'static str = "Sine Test Tone"; const VENDOR: &'static str = "Moist Plugins GmbH"; const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; @@ -158,6 +163,13 @@ impl Plugin for Sine { // Smoothing is optionally built into the parameters themselves let gain = self.params.gain.smoothed.next(); + let offset = + ((self.samples / 100.0) + 1.0).log2() / (100.0f32 * 500.0 + 1.0).log2() * 500.0; + self.samples += 1.0; + if self.samples / 100.0 > 500.0 { + self.samples = 0.0; + } + // This plugin can be either triggered by MIDI or controleld by a parameter let sine = if self.params.use_midi.value { // Act on the next MIDI event @@ -184,7 +196,7 @@ impl Plugin for Sine { self.calculate_sine(self.midi_note_freq) * self.midi_note_gain.next() } else { let frequency = self.params.frequency.smoothed.next(); - self.calculate_sine(frequency) + self.calculate_sine(frequency + offset) }; for sample in samples { diff --git a/src/context.rs b/src/context.rs index ce2f0892..cf3e9f15 100644 --- a/src/context.rs +++ b/src/context.rs @@ -24,15 +24,15 @@ mod linux; #[cfg(all(target_family = "unix", not(target_os = "macos")))] pub(crate) use linux::LinuxEventLoop as OsEventLoop; +use crate::param::Param; use crate::plugin::NoteEvent; pub(crate) const TASK_QUEUE_CAPACITY: usize = 512; // TODO: ProcessContext for parameter automation and sending events -// TODO: GuiContext for GUI parameter automation and resizing /// General callbacks the plugin can make during its lifetime. This is passed to the plugin during -/// [crate::plugin::Plugin::initialize]. +/// [crate::plugin::Plugin::initialize] and as part of [crate::plugin::Plugin::process]. // // # Safety // @@ -50,10 +50,37 @@ pub trait ProcessContext { /// here) fn next_midi_event(&mut self) -> Option; - // // TODO: Add this next - // fn set_parameter

(&self, param: &P, value: P::Plain) - // where - // P: Param; + // TODO: Add this, this works similar to [GuiContext::set_parameter] but it adds the parameter + // change to a queue (or directly to the VST3 plugin's parameter output queues) instead of + // using main thread host automation (and all the locks involved there). + // fn set_parameter(&self, param: &P, value: P::Plain); +} + +/// Callbacks the plugin can make while handling its GUI, such as updating parameter values. This is +/// passed to the plugin during [crate::plugin::Plugin::create_editor]. +// +// # Safety +// +// The implementing wrapper needs to be able to handle concurrent requests, and it should perform +// the actual callback within [MainThreadQueue::do_maybe_async]. +pub trait GuiContext { + /// Inform the host that you will start automating a parmater. This needs to be called before + /// calling [set_parameter] for the specified parameter. + fn begin_set_parameter(&self, param: &P); + + /// Set a parameter to the specified parameter value. You will need to call + /// [begin_set_parameter] before and [end_set_parameter] after calling this so the host can + /// properly record automation for the parameter. This can be called multiple times in a row + /// before calling [end_set_parameter], for instance when moving a slider around. + /// + /// This function assumes you're already calling this from a GUI thread. Calling any of these + /// functions from any other thread may result in unexpected behavior. + fn set_parameter(&self, param: &P, value: P::Plain); + + /// Inform the host that you are done automating a parameter. This needs to be called after one + /// or more [set_parameter] calls for a parameter so the host knows the automation gesture has + /// finished. + fn end_set_parameter(&self, param: &P); } /// A trait describing the functionality of the platform-specific event loop that can execute tasks diff --git a/src/lib.rs b/src/lib.rs index 65af4b8b..d32c254d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,9 @@ pub use param::internals::Params; pub use param::range::Range; pub use param::smoothing::{Smoother, SmoothingStyle}; pub use param::{BoolParam, FloatParam, IntParam, Param}; -pub use plugin::{BufferConfig, BusConfig, NoteEvent, Plugin, ProcessStatus, Vst3Plugin}; +pub use plugin::{ + BufferConfig, BusConfig, Editor, NoEditor, NoteEvent, Plugin, ProcessStatus, Vst3Plugin, +}; // The rest is either internal or already re-exported mod buffer; diff --git a/src/plugin.rs b/src/plugin.rs index f4789c97..f7efdc4f 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -14,10 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use raw_window_handle::RawWindowHandle; use std::pin::Pin; use crate::buffer::Buffer; -use crate::context::ProcessContext; +use crate::context::{GuiContext, ProcessContext}; use crate::param::internals::Params; /// Basic functionality that needs to be implemented by a plugin. The wrappers will use this to @@ -39,6 +40,11 @@ use crate::param::internals::Params; /// - Outputting MIDI events /// - GUIs pub trait Plugin: Default + Send + Sync { + /// The type of the GUI editor instance belonging to this plugin. Use [NoEditor] when you don't + /// need an editor. Make sure to implement both the [create_editor] and [editor_size] functions + /// when you do add an editor. + type Editor: Editor; + const NAME: &'static str; const VENDOR: &'static str; const URL: &'static str; @@ -64,6 +70,31 @@ pub trait Plugin: Default + Send + Sync { /// plugin receives an update. fn params(&self) -> Pin<&dyn Params>; + /// Create an editor for this plugin and embed it in the parent window. The idea is that you + /// take a reference to your [Params] in your editor to be able to read the current values. Then + /// whenever you need to change any of those values, you can use the methods on the [GuiContext] + /// that's passed to this function. When you change a parameter value there it will be + /// broadcasted to the host and also updated in your [Params] struct. + // + // TODO: Think of how this would work with the event loop. On Linux the wrapper must provide a + // timer using VST3's `IRunLoop` interface, but on Window and macOS the window would + // normally register its own timer. Right now we just ignore this because it would + // otherwise be basically impossible to have this still be GUI-framework agnostic. Any + // callback that deos involve actual GUI operations will still be spooled to the IRunLoop + // instance. + fn create_editor( + &self, + _parent: RawWindowHandle, + _context: &impl GuiContext, + ) -> Option { + None + } + + /// Return the current size of the plugin's editor, if it has one. + fn editor_size(&self) -> Option<(u32, u32)> { + None + } + // // The following functions follow the lifetime of the plugin. // @@ -117,6 +148,29 @@ pub trait Vst3Plugin: Plugin { const VST3_CATEGORIES: &'static str; } +/// An editor for a [Plugin]. If you don't have or need an editor, then you can use the [NoEditor] +/// struct as a placeholder. +pub trait Editor { + /// Called just before the window gets closed. + fn close(&self); + + /// Return the (currnent) size of the editor in pixels as a `(width, height)` pair. + fn size(&self) -> (u32, u32); + + // TODO: Add the things needed for DPI scaling + // TODO: Resizing +} + +pub struct NoEditor; + +impl Editor for NoEditor { + fn close(&self) {} + + fn size(&self) -> (u32, u32) { + (0, 0) + } +} + /// We only support a single main input and output bus at the moment. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BusConfig {