diff --git a/src/context.rs b/src/context.rs index 51cbfad2..555658e1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -24,6 +24,9 @@ pub trait ProcessContext { /// Get information about the current transport position and status. fn transport(&self) -> &Transport; + /// The current processing mode. The host will reinitialize the plugin any time this changes. + fn process_mode(&self) -> ProcessMode; + /// Returns the next note event, if there is one. Use [`NoteEvent::timing()`] to get the event's /// timing within the buffer. Only available when /// [`Plugin::MIDI_INPUT`][crate::prelude::Plugin::MIDI_INPUT] is set. @@ -204,6 +207,22 @@ pub enum PluginApi { Vst3, } +/// The plugin's current processing mode. Can be queried through [`ProcessContext::process_mode()`]. +/// The host will reinitialize the plugin whenever this changes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProcessMode { + /// The plugin is processing audio in real time at a fixed rate. + Realtime, + /// The plugin is processing audio at a real time-like pace, but at irregular intervals. The + /// host may do this to process audio ahead of time to loosen realtime constraints and to reduce + /// the chance of xruns happening. This is only used by VST3. + Buffered, + /// The plugin is rendering audio offline, potentially faster than realtime ('freewheeling'). + /// The host will continuously call the process function back to back until all audio has been + /// processed. + Offline, +} + impl Display for PluginApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/prelude.rs b/src/prelude.rs index e84c10fd..38c52260 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -11,7 +11,7 @@ pub use crate::formatters; pub use crate::util; pub use crate::buffer::Buffer; -pub use crate::context::{GuiContext, ParamSetter, PluginApi, ProcessContext}; +pub use crate::context::{GuiContext, ParamSetter, PluginApi, ProcessContext, ProcessMode}; // This also includes the derive macro pub use crate::midi::{control_change, MidiConfig, NoteEvent}; pub use crate::param::enums::{Enum, EnumParam}; diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs index 3947f70a..5b383705 100644 --- a/src/wrapper/clap/context.rs +++ b/src/wrapper/clap/context.rs @@ -4,7 +4,7 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use super::wrapper::{OutputParamEvent, Task, Wrapper}; -use crate::context::{GuiContext, PluginApi, ProcessContext, Transport}; +use crate::context::{GuiContext, PluginApi, ProcessContext, ProcessMode, Transport}; use crate::event_loop::EventLoop; use crate::midi::NoteEvent; use crate::param::internals::ParamPtr; @@ -104,6 +104,10 @@ impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> { &self.transport } + fn process_mode(&self) -> ProcessMode { + self.wrapper.current_process_mode.load() + } + fn next_event(&mut self) -> Option<NoteEvent> { self.input_events_guard.pop_front() } diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index e90ef638..13c819ab 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -41,6 +41,9 @@ use clap_sys::ext::params::{ CLAP_PARAM_IS_MODULATABLE, CLAP_PARAM_IS_READONLY, CLAP_PARAM_IS_STEPPED, CLAP_PARAM_RESCAN_VALUES, }; +use clap_sys::ext::render::{ + clap_plugin_render, clap_plugin_render_mode, CLAP_RENDER_OFFLINE, CLAP_RENDER_REALTIME, +}; use clap_sys::ext::state::{clap_plugin_state, CLAP_EXT_STATE}; use clap_sys::ext::tail::{clap_plugin_tail, CLAP_EXT_TAIL}; use clap_sys::ext::thread_check::{clap_host_thread_check, CLAP_EXT_THREAD_CHECK}; @@ -75,7 +78,7 @@ use super::context::{WrapperGuiContext, WrapperProcessContext}; use super::descriptor::PluginDescriptor; use super::util::ClapPtr; use crate::buffer::Buffer; -use crate::context::Transport; +use crate::context::{ProcessMode, Transport}; use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY}; use crate::midi::{MidiConfig, NoteEvent}; use crate::param::internals::{ParamPtr, Params}; @@ -125,6 +128,8 @@ pub struct Wrapper<P: ClapPlugin> { /// The current buffer configuration, containing the sample rate and the maximum block size. /// Will be set in `clap_plugin::activate()`. current_buffer_config: AtomicCell<Option<BufferConfig>>, + /// The current audio processing mode. Set through the render extension. Defaults to realtime. + pub current_process_mode: AtomicCell<ProcessMode>, /// The incoming events for the plugin, if `P::MIDI_INPUT` is set to `MidiConfig::Basic` or /// higher. /// @@ -217,6 +222,8 @@ pub struct Wrapper<P: ClapPlugin> { host_thread_check: AtomicRefCell<Option<ClapPtr<clap_host_thread_check>>>, + clap_plugin_render: clap_plugin_render, + clap_plugin_state: clap_plugin_state, clap_plugin_tail: clap_plugin_tail, @@ -481,6 +488,7 @@ impl<P: ClapPlugin> Wrapper<P> { num_output_channels: P::DEFAULT_NUM_OUTPUTS, }), current_buffer_config: AtomicCell::new(None), + current_process_mode: AtomicCell::new(ProcessMode::Realtime), input_events: AtomicRefCell::new(VecDeque::with_capacity(512)), output_events: AtomicRefCell::new(VecDeque::with_capacity(512)), last_process_status: AtomicCell::new(ProcessStatus::Normal), @@ -556,6 +564,11 @@ impl<P: ClapPlugin> Wrapper<P> { host_thread_check: AtomicRefCell::new(None), + clap_plugin_render: clap_plugin_render { + has_hard_realtime_requirement: Self::ext_render_has_hard_realtime_requirement, + set: Self::ext_render_set, + }, + clap_plugin_state: clap_plugin_state { save: Self::ext_state_save, load: Self::ext_state_load, @@ -2620,6 +2633,33 @@ impl<P: ClapPlugin> Wrapper<P> { } } + unsafe extern "C" fn ext_render_has_hard_realtime_requirement( + _plugin: *const clap_plugin, + ) -> bool { + // TODO: Add a constant on the CLapPlugin trait + false + } + + unsafe extern "C" fn ext_render_set( + plugin: *const clap_plugin, + mode: clap_plugin_render_mode, + ) -> bool { + check_null_ptr!(false, plugin); + let wrapper = &*(plugin as *const Self); + + let mode = match mode { + CLAP_RENDER_REALTIME => ProcessMode::Realtime, + CLAP_RENDER_OFFLINE => ProcessMode::Offline, + n => { + nih_debug_assert_failure!("Unknown rendering mode '{}', defaulting to realtime", n); + ProcessMode::Realtime + } + }; + wrapper.current_process_mode.store(mode); + + true + } + unsafe extern "C" fn ext_state_save( plugin: *const clap_plugin, stream: *const clap_ostream, diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs index 46fc0032..73cdb453 100644 --- a/src/wrapper/standalone/context.rs +++ b/src/wrapper/standalone/context.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use super::backend::Backend; use super::wrapper::{GuiTask, Wrapper}; -use crate::context::{GuiContext, PluginApi, ProcessContext, Transport}; +use crate::context::{GuiContext, PluginApi, ProcessContext, ProcessMode, Transport}; use crate::midi::NoteEvent; use crate::param::internals::ParamPtr; use crate::plugin::Plugin; @@ -81,6 +81,11 @@ impl<P: Plugin, B: Backend> ProcessContext for WrapperProcessContext<'_, P, B> { &self.transport } + fn process_mode(&self) -> ProcessMode { + // TODO: Detect JACK freewheeling and report it here + ProcessMode::Realtime + } + fn next_event(&mut self) -> Option<NoteEvent> { nih_debug_assert_failure!("TODO: WrapperProcessContext::next_event()"); diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index 36a05f30..224eec13 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -117,6 +117,10 @@ impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> { &self.transport } + fn process_mode(&self) -> crate::context::ProcessMode { + self.inner.current_process_mode.load() + } + fn next_event(&mut self) -> Option<NoteEvent> { self.input_events_guard.pop_front() } diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index 8442fd5c..20d1b62a 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -16,7 +16,7 @@ use super::param_units::ParamUnits; use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START}; use super::view::WrapperView; use crate::buffer::Buffer; -use crate::context::Transport; +use crate::context::{ProcessMode, Transport}; use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop}; use crate::midi::{MidiConfig, NoteEvent}; use crate::param::internals::{ParamPtr, Params}; @@ -67,6 +67,8 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> { /// The current buffer configuration, containing the sample rate and the maximum block size. /// Will be set in `IAudioProcessor::setupProcessing()`. pub current_buffer_config: AtomicCell<Option<BufferConfig>>, + /// The current audio processing mode. Set in `IAudioProcessor::setup_processing()`. + pub current_process_mode: AtomicCell<ProcessMode>, /// The last process status returned by the plugin. This is used for tail handling. pub last_process_status: AtomicCell<ProcessStatus>, /// The current latency in samples, as set by the plugin through the [`ProcessContext`]. @@ -277,6 +279,7 @@ impl<P: Vst3Plugin> WrapperInner<P> { num_output_channels: P::DEFAULT_NUM_OUTPUTS, }), current_buffer_config: AtomicCell::new(None), + current_process_mode: AtomicCell::new(ProcessMode::Realtime), last_process_status: AtomicCell::new(ProcessStatus::Normal), current_latency: AtomicU32::new(0), output_buffer: AtomicRefCell::new(Buffer::default()), diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index 28722dfa..8afc8dd6 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -4,7 +4,7 @@ use std::mem::{self, MaybeUninit}; use std::ptr; use std::sync::atomic::Ordering; use std::sync::Arc; -use vst3_com::vst::IProcessContextRequirementsFlags; +use vst3_com::vst::{IProcessContextRequirementsFlags, ProcessModes}; use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool}; use vst3_sys::base::{IBStream, IPluginBase}; use vst3_sys::utils::SharedVstPtr; @@ -23,7 +23,7 @@ use super::util::{ u16strlcpy, VstPtr, VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START, }; use super::view::WrapperView; -use crate::context::Transport; +use crate::context::{ProcessMode, Transport}; use crate::midi::{MidiConfig, NoteEvent}; use crate::param::ParamFlags; use crate::plugin::{BufferConfig, BusConfig, ProcessStatus, Vst3Plugin}; @@ -690,6 +690,17 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> { max_buffer_size: setup.max_samples_per_block as u32, })); + let mode = match setup.process_mode { + n if n == ProcessModes::kRealtime as i32 => ProcessMode::Realtime, + n if n == ProcessModes::kPrefetch as i32 => ProcessMode::Buffered, + n if n == ProcessModes::kOffline as i32 => ProcessMode::Offline, + n => { + nih_debug_assert_failure!("Unknown rendering mode '{}', defaulting to realtime", n); + ProcessMode::Realtime + } + }; + self.inner.current_process_mode.store(mode); + // Initializing the plugin happens in `IAudioProcessor::set_active()` because the host may // still change the channel layouts at this point