1
0
Fork 0

Create a separate InitContext

Only a couple of these functions would be needed during initialization.
In the next couple commits ProcessContext will get a way to access
auxiliary IO, so this really had to be separated.
This commit is contained in:
Robbert van der Helm 2022-05-27 01:17:15 +02:00
parent c555aff768
commit b2e6bd5515
22 changed files with 144 additions and 68 deletions

View file

@ -6,6 +6,14 @@ new and what's changed, this document lists all breaking changes in reverse
chronological order. If a new feature did not require any changes to existing
code then it will not be listed here.
## [2022-05-2y]
- The `Plugin::initialize()` method now takes a `&mut impl InitContext` instead
of a `&mut impl ProcessContext`. This is to avoid soundness issues when
`ProcessContext` lets you access sidechain buffer and auxiliary outputs in the
future and because most of the methods on `ProcessContext` would not be
applicable to initialization.
## [2022-05-22]
- Previously calling `param.non_automatable()` when constructing a parameter

View file

@ -315,7 +315,7 @@ impl Plugin for Crisp {
&mut self,
bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
nih_debug_assert_eq!(bus_config.num_input_channels, NUM_CHANNELS);
nih_debug_assert_eq!(bus_config.num_output_channels, NUM_CHANNELS);

View file

@ -253,7 +253,7 @@ impl Plugin for Diopser {
&mut self,
_bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
self.sample_rate = buffer_config.sample_rate;

View file

@ -144,7 +144,7 @@ impl Plugin for Gain {
&mut self,
_bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
// TODO: How do you tie this exponential decay to an actual time span?
self.peak_meter_decay_weight = 0.9992f32.powf(44_100.0 / buffer_config.sample_rate);

View file

@ -90,7 +90,7 @@ impl Plugin for Gain {
&mut self,
_bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
// TODO: How do you tie this exponential decay to an actual time span?
self.peak_meter_decay_weight = 0.9992f32.powf(44_100.0 / buffer_config.sample_rate);

View file

@ -90,7 +90,7 @@ impl Plugin for Gain {
&mut self,
_bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
// TODO: How do you tie this exponential decay to an actual time span?
self.peak_meter_decay_weight = 0.9992f32.powf(44_100.0 / buffer_config.sample_rate);

View file

@ -124,7 +124,7 @@ impl Plugin for Sine {
&mut self,
_bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
self.sample_rate = buffer_config.sample_rate;

View file

@ -108,7 +108,7 @@ impl Plugin for Stft {
&mut self,
_bus_config: &BusConfig,
_buffer_config: &BufferConfig,
context: &mut impl ProcessContext,
context: &mut impl InitContext,
) -> bool {
// The plugin's latency consists of the block size from the overlap-add procedure and half
// of the filter kernel's size (since we're using a linear phase/symmetrical convolution

View file

@ -135,7 +135,7 @@ impl Plugin for LoudnessWarWinner {
&mut self,
bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
self.sample_rate = buffer_config.sample_rate;

View file

@ -179,7 +179,7 @@ impl Plugin for PubertySimulator {
&mut self,
_bus_config: &BusConfig,
_buffer_config: &BufferConfig,
context: &mut impl ProcessContext,
context: &mut impl InitContext,
) -> bool {
// Planning with RustFFT is very fast, but it will still allocate we we'll plan all of the
// FFTs we might need in advance

View file

@ -170,7 +170,7 @@ impl Plugin for SafetyLimiter {
&mut self,
_bus_config: &BusConfig,
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
_context: &mut impl InitContext,
) -> bool {
self.buffer_config = *buffer_config;
self.morse_fadeout_samples_start =

View file

@ -7,10 +7,25 @@ use crate::param::internals::ParamPtr;
use crate::param::Param;
use crate::wrapper::state::PluginState;
// TODO: ProcessContext for parameter automation and sending events
/// Callbacks the plugin can make while it is being initialized. This is passed to the plugin during
/// [`Plugin::initialize()`][crate::plugin::Plugin::initialize()].
//
// # 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 InitContext {
/// Get the current plugin API.
fn plugin_api(&self) -> PluginApi;
/// General callbacks the plugin can make during its lifetime. This is passed to the plugin during
/// [`Plugin::initialize()`][crate::plugin::Plugin::initialize()] and as part of
/// Update the current latency of the plugin. If the plugin is currently processing audio, then
/// this may cause audio playback to be restarted.
fn set_latency_samples(&self, samples: u32);
}
/// Contains both context data and callbacks the plugin can use during processing. Most notably this
/// is how a plugin sends and receives note events, gets transport information, and accesses
/// sidechain inputs and auxiliary outputs. This is passed to the plugin during as part of
/// [`Plugin::process()`][crate::plugin::Plugin::process()].
//
// # Safety

View file

@ -5,7 +5,7 @@ use std::any::Any;
use std::sync::Arc;
use crate::buffer::Buffer;
use crate::context::{GuiContext, ProcessContext};
use crate::context::{GuiContext, InitContext, ProcessContext};
use crate::midi::MidiConfig;
use crate::param::internals::Params;
@ -111,7 +111,7 @@ pub trait Plugin: Default + Send + Sync + 'static {
&mut self,
bus_config: &BusConfig,
buffer_config: &BufferConfig,
context: &mut impl ProcessContext,
context: &mut impl InitContext,
) -> bool {
true
}

View file

@ -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, InitContext, ParamSetter, PluginApi, ProcessContext};
// This also includes the derive macro
pub use crate::midi::{control_change, MidiConfig, NoteEvent};
pub use crate::param::enums::{Enum, EnumParam};

View file

@ -253,8 +253,7 @@ impl<const NUM_SIDECHAIN_INPUTS: usize> StftHelper<NUM_SIDECHAIN_INPUTS> {
/// Process the audio in `main_buffer` in small overlapping blocks, adding up the results for
/// the main buffer so they can eventually be written back to the host one block later. This
/// means that this function will introduce one block of latency. This can be compensated by
/// calling
/// [`ProcessContext::set_latency()`][`crate::prelude::ProcessContext::set_latency_samples()`]
/// calling [`InitContext::set_latency()`][`crate::prelude::InitContext::set_latency_samples()`]
/// in your plugin's initialization function.
///
/// If a padding value was specified in [`new()`][Self::new()], then the yielded blocks will

View file

@ -1,11 +1,9 @@
use atomic_refcell::AtomicRefMut;
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use super::wrapper::{OutputParamEvent, Task, Wrapper};
use crate::context::{GuiContext, PluginApi, ProcessContext, Transport};
use crate::event_loop::EventLoop;
use super::wrapper::{OutputParamEvent, Wrapper};
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
use crate::midi::NoteEvent;
use crate::param::internals::ParamPtr;
use crate::plugin::ClapPlugin;
@ -17,6 +15,13 @@ pub(crate) struct WrapperGuiContext<P: ClapPlugin> {
pub(super) wrapper: Arc<Wrapper<P>>,
}
/// A [`InitContext`] 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 WrapperInitContext<'a, P: ClapPlugin> {
pub(super) wrapper: &'a Wrapper<P>,
}
/// 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.
@ -95,6 +100,16 @@ impl<P: ClapPlugin> GuiContext for WrapperGuiContext<P> {
}
}
impl<P: ClapPlugin> InitContext for WrapperInitContext<'_, P> {
fn plugin_api(&self) -> PluginApi {
PluginApi::Clap
}
fn set_latency_samples(&self, samples: u32) {
self.wrapper.set_latency_samples(samples)
}
}
impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
fn plugin_api(&self) -> PluginApi {
PluginApi::Clap
@ -113,13 +128,6 @@ impl<P: ClapPlugin> 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.wrapper.current_latency.swap(samples, Ordering::SeqCst);
if old_latency != samples {
let task_posted = self.wrapper.do_maybe_async(Task::LatencyChanged);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
self.wrapper.set_latency_samples(samples)
}
}

View file

@ -74,7 +74,7 @@ use std::sync::{Arc, Weak};
use std::thread::{self, ThreadId};
use std::time::Duration;
use super::context::{WrapperGuiContext, WrapperProcessContext};
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
use super::descriptor::PluginDescriptor;
use super::util::ClapPtr;
use crate::buffer::Buffer;
@ -615,6 +615,10 @@ impl<P: ClapPlugin> Wrapper<P> {
Arc::new(WrapperGuiContext { wrapper: self })
}
fn make_init_context(&self) -> WrapperInitContext<'_, P> {
WrapperInitContext { wrapper: self }
}
fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> {
WrapperProcessContext {
wrapper: self,
@ -1484,11 +1488,7 @@ impl<P: ClapPlugin> Wrapper<P> {
let bus_config = self.current_bus_config.load();
if let Some(buffer_config) = self.current_buffer_config.load() {
let mut plugin = self.plugin.write();
plugin.initialize(
&bus_config,
&buffer_config,
&mut self.make_process_context(Transport::new(buffer_config.sample_rate)),
);
plugin.initialize(&bus_config, &buffer_config, &mut self.make_init_context());
process_wrapper(|| plugin.reset());
}
@ -1501,6 +1501,17 @@ impl<P: ClapPlugin> Wrapper<P> {
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
pub 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.current_latency.swap(samples, Ordering::SeqCst);
if old_latency != samples {
let task_posted = self.do_maybe_async(Task::LatencyChanged);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
}
unsafe extern "C" fn init(plugin: *const clap_plugin) -> bool {
check_null_ptr!(false, plugin);
let wrapper = &*(plugin as *const Self);
@ -1550,7 +1561,7 @@ impl<P: ClapPlugin> Wrapper<P> {
if plugin.initialize(
&bus_config,
&buffer_config,
&mut wrapper.make_process_context(Transport::new(buffer_config.sample_rate)),
&mut wrapper.make_init_context(),
) {
// NOTE: `Plugin::reset()` is called in `clap_plugin::start_processing()` instead of in
// this function
@ -1927,8 +1938,7 @@ impl<P: ClapPlugin> Wrapper<P> {
plugin.initialize(
&bus_config,
&buffer_config,
&mut wrapper
.make_process_context(Transport::new(buffer_config.sample_rate)),
&mut wrapper.make_init_context(),
)
});
plugin.reset();
@ -2810,7 +2820,7 @@ impl<P: ClapPlugin> Wrapper<P> {
plugin.initialize(
&bus_config,
&buffer_config,
&mut wrapper.make_process_context(Transport::new(buffer_config.sample_rate)),
&mut wrapper.make_init_context(),
);
// TODO: This also goes for the VST3 version, but should we call reset here? Won't the
// host always restart playback? Check this with a couple of hosts and remove the

View file

@ -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, InitContext, PluginApi, ProcessContext, Transport};
use crate::midi::NoteEvent;
use crate::param::internals::ParamPtr;
use crate::plugin::Plugin;
@ -19,6 +19,14 @@ pub(crate) struct WrapperGuiContext<P: Plugin, B: Backend> {
pub(super) gui_task_sender: channel::Sender<GuiTask>,
}
/// A [`InitContext`] 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 WrapperInitContext<'a, P: Plugin, B: Backend> {
#[allow(dead_code)]
pub(super) wrapper: &'a Wrapper<P, B>,
}
/// 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.
@ -72,6 +80,16 @@ impl<P: Plugin, B: Backend> GuiContext for WrapperGuiContext<P, B> {
}
}
impl<P: Plugin, B: Backend> InitContext for WrapperInitContext<'_, P, B> {
fn plugin_api(&self) -> PluginApi {
PluginApi::Standalone
}
fn set_latency_samples(&self, _samples: u32) {
nih_debug_assert_failure!("TODO: WrapperInitContext::set_latency_samples()");
}
}
impl<P: Plugin, B: Backend> ProcessContext for WrapperProcessContext<'_, P, B> {
fn plugin_api(&self) -> PluginApi {
PluginApi::Standalone

View file

@ -11,7 +11,7 @@ use std::sync::Arc;
use std::thread;
use super::backend::Backend;
use super::context::{WrapperGuiContext, WrapperProcessContext};
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
use crate::context::Transport;
use crate::param::internals::{ParamPtr, Params};
use crate::param::ParamFlags;
@ -229,7 +229,7 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
if !plugin.initialize(
&wrapper.bus_config,
&wrapper.buffer_config,
&mut wrapper.make_process_context(Transport::new(wrapper.config.sample_rate)),
&mut wrapper.make_init_context(),
) {
return Err(WrapperError::InitializationFailed);
}
@ -494,6 +494,10 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
})
}
fn make_init_context(&self) -> WrapperInitContext<'_, P, B> {
WrapperInitContext { wrapper: self }
}
fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P, B> {
WrapperProcessContext {
wrapper: self,

View file

@ -2,10 +2,10 @@ use atomic_refcell::AtomicRefMut;
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use vst3_sys::vst::{IComponentHandler, RestartFlags};
use vst3_sys::vst::IComponentHandler;
use super::inner::{Task, WrapperInner};
use crate::context::{GuiContext, PluginApi, ProcessContext, Transport};
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
use crate::midi::NoteEvent;
use crate::param::internals::ParamPtr;
use crate::plugin::Vst3Plugin;
@ -18,6 +18,13 @@ pub(crate) struct WrapperGuiContext<P: Vst3Plugin> {
pub(super) inner: Arc<WrapperInner<P>>,
}
/// A [`InitContext`] 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 locks.
pub(crate) struct WrapperInitContext<'a, P: Vst3Plugin> {
pub(super) inner: &'a WrapperInner<P>,
}
/// 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 locks.
@ -108,6 +115,16 @@ impl<P: Vst3Plugin> GuiContext for WrapperGuiContext<P> {
}
}
impl<P: Vst3Plugin> InitContext for WrapperInitContext<'_, P> {
fn plugin_api(&self) -> PluginApi {
PluginApi::Vst3
}
fn set_latency_samples(&self, samples: u32) {
self.inner.set_latency_samples(samples)
}
}
impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
fn plugin_api(&self) -> PluginApi {
PluginApi::Vst3
@ -126,13 +143,6 @@ impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
}
fn set_latency_samples(&self, samples: u32) {
// Only trigger a restart if it's actually needed
let old_latency = self.inner.current_latency.swap(samples, Ordering::SeqCst);
if old_latency != samples {
let task_posted = self
.inner
.do_maybe_async(Task::TriggerRestart(RestartFlags::kLatencyChanged as i32));
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
self.inner.set_latency_samples(samples)
}
}

View file

@ -10,7 +10,7 @@ use std::time::Duration;
use vst3_sys::base::{kInvalidArgument, kResultOk, tresult};
use vst3_sys::vst::{IComponentHandler, RestartFlags};
use super::context::{WrapperGuiContext, WrapperProcessContext};
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
use super::note_expressions::NoteExpressionController;
use super::param_units::ParamUnits;
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
@ -313,6 +313,10 @@ impl<P: Vst3Plugin> WrapperInner<P> {
Arc::new(WrapperGuiContext { inner: self })
}
pub fn make_init_context(&self) -> WrapperInitContext<'_, P> {
WrapperInitContext { inner: self }
}
pub fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> {
WrapperProcessContext {
inner: self,
@ -450,11 +454,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
let bus_config = self.current_bus_config.load();
if let Some(buffer_config) = self.current_buffer_config.load() {
let mut plugin = self.plugin.write();
plugin.initialize(
&bus_config,
&buffer_config,
&mut self.make_process_context(Transport::new(buffer_config.sample_rate)),
);
plugin.initialize(&bus_config, &buffer_config, &mut self.make_init_context());
process_wrapper(|| plugin.reset());
}
@ -468,6 +468,16 @@ impl<P: Vst3Plugin> WrapperInner<P> {
);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
pub fn set_latency_samples(&self, samples: u32) {
// Only trigger a restart if it's actually needed
let old_latency = self.current_latency.swap(samples, Ordering::SeqCst);
if old_latency != samples {
let task_posted =
self.do_maybe_async(Task::TriggerRestart(RestartFlags::kLatencyChanged as i32));
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
}
}
}
impl<P: Vst3Plugin> MainThreadExecutor<Task> for WrapperInner<P> {

View file

@ -370,9 +370,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
if plugin.initialize(
&bus_config,
&buffer_config,
&mut self
.inner
.make_process_context(Transport::new(buffer_config.sample_rate)),
&mut self.inner.make_init_context(),
) {
// NOTE: We don't call `Plugin::reset()` here. The call is done in `set_process()`
// instead. Otherwise we would call the function twice, and `set_process()` needs
@ -459,9 +457,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
plugin.initialize(
&bus_config,
&buffer_config,
&mut self
.inner
.make_process_context(Transport::new(buffer_config.sample_rate)),
&mut self.inner.make_init_context(),
);
// TODO: This also goes for the CLAP version, but should we call reset here? Won't the
// host always restart playback? Check this with a couple of hosts and remove the
@ -1531,9 +1527,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
plugin.initialize(
&bus_config,
&buffer_config,
&mut self
.inner
.make_process_context(Transport::new(buffer_config.sample_rate)),
&mut self.inner.make_init_context(),
)
});
plugin.reset();