From 4ad972ea23e992f4950351a4309307044b43ad2d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 28 Feb 2022 19:45:41 +0100 Subject: [PATCH] Add stubs for a CLAP ProcessContext implementation And the `clap_plugin::active()` function. --- src/wrapper/clap.rs | 1 + src/wrapper/clap/context.rs | 33 ++++++++++++ src/wrapper/clap/plugin.rs | 100 +++++++++++++++++++++++++++--------- src/wrapper/vst3/context.rs | 4 +- 4 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 src/wrapper/clap/context.rs diff --git a/src/wrapper/clap.rs b/src/wrapper/clap.rs index cc991518..8edfefa3 100644 --- a/src/wrapper/clap.rs +++ b/src/wrapper/clap.rs @@ -1,3 +1,4 @@ +mod context; mod descriptor; mod factory; mod plugin; diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs new file mode 100644 index 00000000..f9f863d2 --- /dev/null +++ b/src/wrapper/clap/context.rs @@ -0,0 +1,33 @@ +use parking_lot::RwLockWriteGuard; +use std::collections::VecDeque; +use std::sync::atomic::Ordering; + +use super::plugin::{Plugin, Task}; +use crate::context::ProcessContext; +use crate::event_loop::EventLoop; +use crate::plugin::{ClapPlugin, NoteEvent}; + +/// A [ProcessContext] implementation for the 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: ClapPlugin> { + pub(super) plugin: &'a Plugin

, + pub(super) input_events_guard: RwLockWriteGuard<'a, VecDeque>, +} + +impl ProcessContext for WrapperProcessContext<'_, P> { + fn set_latency_samples(&self, samples: u32) { + // Only make a callback if it's actually needed + // XXX: For CLAP we could move this handling to the Plugin struct, but it may be worthwhile + // to keep doing it this way to stay consistent with VST3. + let old_latency = self.plugin.current_latency.swap(samples, Ordering::SeqCst); + if old_latency != samples { + let task_posted = self.plugin.do_maybe_async(Task::LatencyChanged); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + } + + fn next_midi_event(&mut self) -> Option { + self.input_events_guard.pop_front() + } +} diff --git a/src/wrapper/clap/plugin.rs b/src/wrapper/clap/plugin.rs index 40c13bb2..26d7ebe8 100644 --- a/src/wrapper/clap/plugin.rs +++ b/src/wrapper/clap/plugin.rs @@ -3,12 +3,17 @@ use clap_sys::plugin::clap_plugin; use clap_sys::process::{clap_process, clap_process_status}; use crossbeam::atomic::AtomicCell; use parking_lot::RwLock; +use std::collections::VecDeque; use std::ffi::c_void; use std::os::raw::c_char; use std::ptr; +use std::sync::atomic::AtomicU32; +use super::context::WrapperProcessContext; use super::descriptor::PluginDescriptor; +use crate::event_loop::{EventLoop, MainThreadExecutor}; use crate::plugin::{BufferConfig, BusConfig, ClapPlugin}; +use crate::NoteEvent; #[repr(C)] pub struct Plugin { @@ -23,14 +28,62 @@ pub struct Plugin { current_bus_config: AtomicCell, /// The current buffer configuration, containing the sample rate and the maximum block size. /// Will be set in `clap_plugin::activate()`. - pub current_buffer_config: AtomicCell>, + current_buffer_config: AtomicCell>, + /// The incoming events for the plugin, if `P::ACCEPTS_MIDI` is set. + /// + /// TODO: Maybe load these lazily at some point instead of needing to spool them all to this + /// queue first + /// TODO: Read these in the process call. + input_events: RwLock>, + /// The current latency in samples, as set by the plugin through the [ProcessContext]. uses the + /// latency extnesion + /// + /// TODO: Implement the latency extension. + pub current_latency: AtomicU32, - host_callback: *const clap_host, + host_callback: HostCallback, /// Needs to be boxed because the plugin object is supposed to contain a static reference to /// this. plugin_descriptor: Box>, } +/// Send+Sync wrapper around clap_host. +struct HostCallback(*const clap_host); + +/// Tasks that can be sent from the plugin to be executed on the main thread in a non-blocking +/// realtime safe way. Instead of using a random thread or the OS' event loop like in the Linux +/// implementation, this uses [clap_host::request_callback()] instead. +#[derive(Debug, Clone)] +pub enum Task { + /// Inform the host that the latency has changed. + LatencyChanged, +} + +/// Because CLAP has this [clap_host::request_host_callback()] function, we don't need to use +/// `OsEventLoop` and can instead just request a main thread callback directly. +impl EventLoop> for Plugin

{ + fn new_and_spawn(executor: std::sync::Weak) -> Self { + panic!("What are you doing"); + } + + fn do_maybe_async(&self, task: Task) -> bool { + todo!() + } + + fn is_main_thread(&self) -> bool { + todo!() + } +} + +impl MainThreadExecutor for Plugin

{ + unsafe fn execute(&self, task: Task) { + todo!() + } +} + +unsafe impl Send for HostCallback {} +unsafe impl Sync for HostCallback {} + impl Plugin

{ pub fn new(host_callback: *const clap_host) -> Self { let plugin_descriptor = Box::new(PluginDescriptor::default()); @@ -62,20 +115,20 @@ impl Plugin

{ num_output_channels: P::DEFAULT_NUM_OUTPUTS, }), current_buffer_config: AtomicCell::new(None), + input_events: RwLock::new(VecDeque::with_capacity(512)), + current_latency: AtomicU32::new(0), - host_callback, + host_callback: HostCallback(host_callback), plugin_descriptor, } } -} -impl Plugin

{ - // pub fn make_process_context(&self) -> WrapperProcessContext<'_, P> { - // WrapperProcessContext { - // plugin: self, - // input_events_guard: self.input_events.write(), - // } - // } + fn make_process_context(&self) -> WrapperProcessContext<'_, P> { + WrapperProcessContext { + plugin: self, + input_events_guard: self.input_events.write(), + } + } unsafe extern "C" fn init(_plugin: *const clap_plugin) -> bool { // We don't need any special initialization @@ -102,21 +155,20 @@ impl Plugin

{ // TODO: Reset smoothers - todo!(); - // if plugin.plugin.write().initialize( - // &bus_config, - // &buffer_config, - // plugin.make_process_context(), - // ) { - // // TODO: Allocate buffer slices + if plugin.plugin.write().initialize( + &bus_config, + &buffer_config, + &mut plugin.make_process_context(), + ) { + // TODO: Allocate buffer slices - // // Also store this for later, so we can reinitialize the plugin after restoring state - // plugin.current_buffer_config.store(Some(buffer_config)); + // Also store this for later, so we can reinitialize the plugin after restoring state + plugin.current_buffer_config.store(Some(buffer_config)); - // true - // } else { - // false - // } + true + } else { + false + } } unsafe extern "C" fn deactivate(_plugin: *const clap_plugin) { diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index 77c2667d..bcc0d147 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -11,8 +11,8 @@ use crate::plugin::{NoteEvent, Vst3Plugin}; /// 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: Vst3Plugin> { - pub inner: &'a WrapperInner

, - pub input_events_guard: RwLockWriteGuard<'a, VecDeque>, + pub(super) inner: &'a WrapperInner

, + pub(super) input_events_guard: RwLockWriteGuard<'a, VecDeque>, } impl ProcessContext for WrapperProcessContext<'_, P> {