1
0
Fork 0

Initialize the standalone target

This commit is contained in:
Robbert van der Helm 2022-04-22 18:29:00 +02:00
parent cf3745a4e1
commit 261594a478
4 changed files with 135 additions and 8 deletions

View file

@ -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"),
}
}

View file

@ -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<P: Plugin>() {
pub fn nih_export_standalone_with_args<P: Plugin, Args: IntoIterator<Item = String>>(args: Args) {
// TODO: Do something with the arguments
Wrapper::<P>::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::<P>::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

View file

@ -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<P>,
// TODO: Events
// pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
// pub(super) output_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
pub(super) transport: Transport,
}
impl<P: Plugin> ProcessContext for WrapperProcessContext<'_, P> {
fn plugin_api(&self) -> PluginApi {
PluginApi::Standalone
}
fn transport(&self) -> &Transport {
&self.transport
}
fn next_event(&mut self) -> Option<NoteEvent> {
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()");
}
}

View file

@ -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<P: Plugin> {
/// The wrapped plugin instance.
plugin: RwLock<P>,
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<P: Plugin> Wrapper<P> {
/// 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<Arc<Self>, 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,
}
}
}