diff --git a/src/context.rs b/src/context.rs index b573cee3..bfd01812 100644 --- a/src/context.rs +++ b/src/context.rs @@ -200,6 +200,7 @@ pub struct ParamSetter<'a> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PluginApi { Clap, + Standalone, Vst3, } @@ -207,6 +208,7 @@ impl Display for PluginApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PluginApi::Clap => write!(f, "CLAP"), + PluginApi::Standalone => write!(f, "standalone"), PluginApi::Vst3 => write!(f, "VST3"), } } diff --git a/src/wrapper/standalone.rs b/src/wrapper/standalone.rs index 2304ce13..d4a32a71 100644 --- a/src/wrapper/standalone.rs +++ b/src/wrapper/standalone.rs @@ -1,9 +1,10 @@ //! A standalone plugin target that directly connects to the system's audio and MIDI ports instead //! of relying on a plugin host. This is mostly useful for quickly testing GUI changes. -use self::wrapper::Wrapper; +use self::wrapper::{Wrapper, WrapperConfig}; use crate::plugin::Plugin; +mod context; mod wrapper; /// Open an NIH-plug plugin as a standalone application. If the plugin has an editor, this will open @@ -50,7 +51,19 @@ pub fn nih_export_standalone() { pub fn nih_export_standalone_with_args>(args: Args) { // TODO: Do something with the arguments - Wrapper::

::new(); + // FIXME: The configuration should be set based on the command line arguments + let config = WrapperConfig { + input_channels: 2, + output_channels: 2, + sample_rate: 44100.0, + period_size: 512, + + tempo: 120.0, + timesig_num: 4, + timesig_denom: 4, + }; + + Wrapper::

::new(config); // TODO: Open the editor if available, do IO things // TODO: If the plugin has an editor, block until the editor is closed. Otherwise block diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs new file mode 100644 index 00000000..24bf0110 --- /dev/null +++ b/src/wrapper/standalone/context.rs @@ -0,0 +1,42 @@ +use super::wrapper::Wrapper; +use crate::context::{PluginApi, ProcessContext, Transport}; +use crate::midi::NoteEvent; +use crate::plugin::Plugin; + +/// A [`ProcessContext`] implementation for the standalone wrapper. This is a separate object so it +/// can hold on to lock guards for event queues. Otherwise reading these events would require +/// constant unnecessary atomic operations to lock the uncontested RwLocks. +pub(crate) struct WrapperProcessContext<'a, P: Plugin> { + pub(super) wrapper: &'a Wrapper

, + // TODO: Events + // pub(super) input_events_guard: AtomicRefMut<'a, VecDeque>, + // pub(super) output_events_guard: AtomicRefMut<'a, VecDeque>, + pub(super) transport: Transport, +} + +impl ProcessContext for WrapperProcessContext<'_, P> { + fn plugin_api(&self) -> PluginApi { + PluginApi::Standalone + } + + fn transport(&self) -> &Transport { + &self.transport + } + + fn next_event(&mut self) -> Option { + nih_debug_assert_failure!("TODO: WrapperProcessContext::next_event()"); + + // self.input_events_guard.pop_front() + None + } + + fn send_event(&mut self, event: NoteEvent) { + nih_debug_assert_failure!("TODO: WrapperProcessContext::send_event()"); + + // self.output_events_guard.push_back(event); + } + + fn set_latency_samples(&self, samples: u32) { + nih_debug_assert_failure!("TODO: WrapperProcessContext::set_latency_samples()"); + } +} diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index c6c7eb84..4c4812c6 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -1,19 +1,89 @@ use parking_lot::RwLock; +use std::sync::Arc; -use crate::plugin::Plugin; +use super::context::WrapperProcessContext; +use crate::context::Transport; +use crate::plugin::{BufferConfig, BusConfig, Plugin}; + +/// Configuration for a standalone plugin that would normally be provided by the DAW. +pub struct WrapperConfig { + /// The number of input channels. + pub input_channels: u32, + /// The number of output channels. + pub output_channels: u32, + /// The audio backend's sample rate. + pub sample_rate: f32, + /// The audio backend's period size. + pub period_size: u32, + + /// The current tempo. + pub tempo: f32, + /// The time signature's numerator. + pub timesig_num: u32, + /// The time signature's denominator. + pub timesig_denom: u32, +} pub struct Wrapper { /// The wrapped plugin instance. plugin: RwLock

, + + config: WrapperConfig, + + /// The bus and buffer configurations are static for the standalone target. + bus_config: BusConfig, + buffer_config: BufferConfig, +} + +/// Errors that may arise while initializing the wrapped plugins. +pub enum WrapperError { + /// The plugin does not accept the IO configuration from the config. + IncompatibleConfig, + /// The plugin returned `false` during initialization. + InitializationFailed, } impl Wrapper

{ - /// Instantiate a new instance of the standalone wrapper. - // - // TODO: This should take some arguments for the audio and MIDI IO. - pub fn new() -> Self { - Wrapper { + /// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does + /// not accept the IO configuration from the wrapper config. + pub fn new(config: WrapperConfig) -> Result, WrapperError> { + let wrapper = Arc::new(Wrapper { plugin: RwLock::new(P::default()), + bus_config: BusConfig { + num_input_channels: config.input_channels, + num_output_channels: config.output_channels, + }, + buffer_config: BufferConfig { + sample_rate: config.sample_rate, + max_buffer_size: config.period_size, + }, + config, + }); + + // Right now the IO configuration is fixed in the standalone target, so if the plugin cannot + // work with this then we cannot initialize the plugin at all. + { + let mut plugin = wrapper.plugin.write(); + if !plugin.accepts_bus_config(&wrapper.bus_config) { + return Err(WrapperError::IncompatibleConfig); + } + + if !plugin.initialize( + &wrapper.bus_config, + &wrapper.buffer_config, + &mut wrapper.make_process_context(Transport::new(wrapper.config.sample_rate)), + ) { + return Err(WrapperError::InitializationFailed); + } + } + + Ok(wrapper) + } + + fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> { + WrapperProcessContext { + wrapper: self, + transport, } } }