Add AsyncExecutor support to ProcessContext
This commit is contained in:
parent
ec8e99cf44
commit
84f834abb6
|
@ -8,6 +8,8 @@ code then it will not be listed here.
|
||||||
|
|
||||||
## [2022-10-22]
|
## [2022-10-22]
|
||||||
|
|
||||||
|
- The `&mut impl ProcessContext` argument to `Plugin::process()` needs to be
|
||||||
|
changed to `&mut impl ProcessContext<Self>`.
|
||||||
- The `&mut impl InitContext` argument to `Plugin::initialize()` needs to be
|
- The `&mut impl InitContext` argument to `Plugin::initialize()` needs to be
|
||||||
changed to `&mut impl InitContext<Self>`.
|
changed to `&mut impl InitContext<Self>`.
|
||||||
- NIH-plug has gained support for asynchronously running background tasks in a
|
- NIH-plug has gained support for asynchronously running background tasks in a
|
||||||
|
|
|
@ -83,7 +83,7 @@ Scroll down for more information on the underlying plugin framework.
|
||||||
- Standalone binaries can be made by calling `nih_export_standalone(Foo)` from
|
- Standalone binaries can be made by calling `nih_export_standalone(Foo)` from
|
||||||
your `main()` function. Standalones come with a CLI for configuration and full
|
your `main()` function. Standalones come with a CLI for configuration and full
|
||||||
JACK audio, MIDI, and transport support.
|
JACK audio, MIDI, and transport support.
|
||||||
- Declarative parameter handling without any boilerplate.
|
- Rich declarative parameter system without any boilerplate.
|
||||||
- Define parameters for your plugin by adding `FloatParam`, `IntParam`,
|
- Define parameters for your plugin by adding `FloatParam`, `IntParam`,
|
||||||
`BoolParam`, and `EnumParam<T>` fields to your parameter struct, assign
|
`BoolParam`, and `EnumParam<T>` fields to your parameter struct, assign
|
||||||
stable IDs to them with the `#[id = "foobar"]`, and a `#[derive(Params)]`
|
stable IDs to them with the `#[id = "foobar"]`, and a `#[derive(Params)]`
|
||||||
|
@ -107,6 +107,8 @@ Scroll down for more information on the underlying plugin framework.
|
||||||
trait to enable compile time generated parameters and other bespoke
|
trait to enable compile time generated parameters and other bespoke
|
||||||
functionality.
|
functionality.
|
||||||
- Stateful. Behaves mostly like JUCE, just without all of the boilerplate.
|
- Stateful. Behaves mostly like JUCE, just without all of the boilerplate.
|
||||||
|
- Comes with a simple yet powerful way to asynchronously run background tasks
|
||||||
|
from a plugin that's both type-safe and realtime-safe.
|
||||||
- Does not make any assumptions on how you want to process audio, but does come
|
- Does not make any assumptions on how you want to process audio, but does come
|
||||||
with utilities and adapters to help with common access patterns.
|
with utilities and adapters to help with common access patterns.
|
||||||
- Efficiently iterate over an audio buffer either per-sample per-channel,
|
- Efficiently iterate over an audio buffer either per-sample per-channel,
|
||||||
|
|
|
@ -356,7 +356,7 @@ impl Plugin for Crisp {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
for (_, mut block) in buffer.iter_blocks(BLOCK_SIZE) {
|
for (_, mut block) in buffer.iter_blocks(BLOCK_SIZE) {
|
||||||
let mut rm_outputs = [[0.0; NUM_CHANNELS as usize]; BLOCK_SIZE];
|
let mut rm_outputs = [[0.0; NUM_CHANNELS as usize]; BLOCK_SIZE];
|
||||||
|
|
|
@ -228,7 +228,7 @@ impl Plugin for Crossover {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
aux: &mut AuxiliaryBuffers,
|
aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// Right now both crossover types only do 24 dB/octave Linkwitz-Riley style crossovers
|
// Right now both crossover types only do 24 dB/octave Linkwitz-Riley style crossovers
|
||||||
match self.params.crossover_type.value() {
|
match self.params.crossover_type.value() {
|
||||||
|
|
|
@ -288,7 +288,7 @@ impl Plugin for Diopser {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// Since this is an expensive operation, only update the filters when it's actually
|
// Since this is an expensive operation, only update the filters when it's actually
|
||||||
// necessary, and allow smoothing only every n samples using the automation precision
|
// necessary, and allow smoothing only every n samples using the automation precision
|
||||||
|
|
|
@ -156,7 +156,7 @@ impl Plugin for Gain {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
for channel_samples in buffer.iter_samples() {
|
for channel_samples in buffer.iter_samples() {
|
||||||
// Smoothing is optionally built into the parameters themselves
|
// Smoothing is optionally built into the parameters themselves
|
||||||
|
|
|
@ -174,7 +174,7 @@ impl Plugin for Gain {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
for channel_samples in buffer.iter_samples() {
|
for channel_samples in buffer.iter_samples() {
|
||||||
let mut amplitude = 0.0;
|
let mut amplitude = 0.0;
|
||||||
|
|
|
@ -119,7 +119,7 @@ impl Plugin for Gain {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
for channel_samples in buffer.iter_samples() {
|
for channel_samples in buffer.iter_samples() {
|
||||||
let mut amplitude = 0.0;
|
let mut amplitude = 0.0;
|
||||||
|
|
|
@ -118,7 +118,7 @@ impl Plugin for Gain {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
for channel_samples in buffer.iter_samples() {
|
for channel_samples in buffer.iter_samples() {
|
||||||
let mut amplitude = 0.0;
|
let mut amplitude = 0.0;
|
||||||
|
|
|
@ -44,7 +44,7 @@ impl Plugin for MidiInverter {
|
||||||
&mut self,
|
&mut self,
|
||||||
_buffer: &mut Buffer,
|
_buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// We'll invert the channel, note index, velocity, pressure, CC value, pitch bend, and
|
// We'll invert the channel, note index, velocity, pressure, CC value, pitch bend, and
|
||||||
// anything else that is invertable for all events we receive
|
// anything else that is invertable for all events we receive
|
||||||
|
|
|
@ -177,7 +177,7 @@ impl Plugin for PolyModSynth {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// NIH-plug has a block-splitting adapter for `Buffer`. While this works great for effect
|
// NIH-plug has a block-splitting adapter for `Buffer`. While this works great for effect
|
||||||
// plugins, for polyphonic synths the block size should be `min(MAX_BLOCK_SIZE,
|
// plugins, for polyphonic synths the block size should be `min(MAX_BLOCK_SIZE,
|
||||||
|
@ -443,7 +443,7 @@ impl PolyModSynth {
|
||||||
/// voice will be stolen. Returns a reference to the new voice.
|
/// voice will be stolen. Returns a reference to the new voice.
|
||||||
fn start_voice(
|
fn start_voice(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
sample_offset: u32,
|
sample_offset: u32,
|
||||||
voice_id: Option<i32>,
|
voice_id: Option<i32>,
|
||||||
channel: u8,
|
channel: u8,
|
||||||
|
@ -544,7 +544,7 @@ impl PolyModSynth {
|
||||||
/// matching voices.
|
/// matching voices.
|
||||||
fn choke_voices(
|
fn choke_voices(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
sample_offset: u32,
|
sample_offset: u32,
|
||||||
voice_id: Option<i32>,
|
voice_id: Option<i32>,
|
||||||
channel: u8,
|
channel: u8,
|
||||||
|
|
|
@ -145,7 +145,7 @@ impl Plugin for Sine {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
let mut next_event = context.next_event();
|
let mut next_event = context.next_event();
|
||||||
for (sample_id, channel_samples) in buffer.iter_samples().enumerate() {
|
for (sample_id, channel_samples) in buffer.iter_samples().enumerate() {
|
||||||
|
|
|
@ -132,7 +132,7 @@ impl Plugin for Stft {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
self.stft
|
self.stft
|
||||||
.process_overlap_add(buffer, 1, |_channel_idx, real_fft_buffer| {
|
.process_overlap_add(buffer, 1, |_channel_idx, real_fft_buffer| {
|
||||||
|
|
|
@ -174,7 +174,7 @@ impl Plugin for LoudnessWarWinner {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
for mut channel_samples in buffer.iter_samples() {
|
for mut channel_samples in buffer.iter_samples() {
|
||||||
let output_gain = self.params.output_gain.smoothed.next();
|
let output_gain = self.params.output_gain.smoothed.next();
|
||||||
|
|
|
@ -224,7 +224,7 @@ impl Plugin for PubertySimulator {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// Compensate for the window function, the overlap, and the extra gain introduced by the
|
// Compensate for the window function, the overlap, and the extra gain introduced by the
|
||||||
// IDFT operation
|
// IDFT operation
|
||||||
|
|
|
@ -200,7 +200,7 @@ impl Plugin for SafetyLimiter {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// Don't do anything when bouncing
|
// Don't do anything when bouncing
|
||||||
if self.buffer_config.process_mode == ProcessMode::Offline {
|
if self.buffer_config.process_mode == ProcessMode::Offline {
|
||||||
|
|
|
@ -346,7 +346,7 @@ impl Plugin for SpectralCompressor {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
aux: &mut AuxiliaryBuffers,
|
aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// If the window size has changed since the last process call, reset the buffers and chance
|
// If the window size has changed since the last process call, reset the buffers and chance
|
||||||
// our latency. All of these buffers already have enough capacity so this won't allocate.
|
// our latency. All of these buffers already have enough capacity so this won't allocate.
|
||||||
|
|
|
@ -50,7 +50,11 @@ pub trait InitContext<P: Plugin> {
|
||||||
//
|
//
|
||||||
// The implementing wrapper needs to be able to handle concurrent requests, and it should perform
|
// The implementing wrapper needs to be able to handle concurrent requests, and it should perform
|
||||||
// the actual callback within [MainThreadQueue::do_maybe_async].
|
// the actual callback within [MainThreadQueue::do_maybe_async].
|
||||||
pub trait ProcessContext {
|
pub trait ProcessContext<P: Plugin> {
|
||||||
|
/// Run a task on a background thread. This allows defering expensive background tasks for
|
||||||
|
/// alter. As long as creating the `task` is realtime-safe, this operation is too.
|
||||||
|
fn execute_async(&self, task: <P::AsyncExecutor as AsyncExecutor>::Task);
|
||||||
|
|
||||||
/// Get the current plugin API.
|
/// Get the current plugin API.
|
||||||
fn plugin_api(&self) -> PluginApi;
|
fn plugin_api(&self) -> PluginApi;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ mod linux;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows;
|
mod windows;
|
||||||
|
|
||||||
|
use crate::prelude::AsyncExecutor;
|
||||||
|
|
||||||
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
||||||
pub(crate) use self::linux::LinuxEventLoop as OsEventLoop;
|
pub(crate) use self::linux::LinuxEventLoop as OsEventLoop;
|
||||||
// For now, also use the Linux event loop on macOS so it at least compiles
|
// For now, also use the Linux event loop on macOS so it at least compiles
|
||||||
|
@ -38,6 +40,9 @@ where
|
||||||
{
|
{
|
||||||
/// Create and start a new event loop. The thread this is called on will be designated as the
|
/// Create and start a new event loop. The thread this is called on will be designated as the
|
||||||
/// main thread, so this should be called when constructing the wrapper.
|
/// main thread, so this should be called when constructing the wrapper.
|
||||||
|
//
|
||||||
|
// TODO: This use of `Weak` is a bit weird outside the context of the VST3 wrapper, but the
|
||||||
|
// alternatives didn't feel right either.
|
||||||
fn new_and_spawn(executor: Weak<E>) -> Self;
|
fn new_and_spawn(executor: Weak<E>) -> Self;
|
||||||
|
|
||||||
/// Either post the function to the task queue so it can be delegated to the main thread, or
|
/// Either post the function to the task queue so it can be delegated to the main thread, or
|
||||||
|
@ -63,3 +68,11 @@ pub(crate) trait MainThreadExecutor<T>: Send + Sync {
|
||||||
/// assume (and can only assume) that this is called from the main thread.
|
/// assume (and can only assume) that this is called from the main thread.
|
||||||
unsafe fn execute(&self, task: T);
|
unsafe fn execute(&self, task: T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An adapter implementation to allow any [`AsyncExecutor`] to function as a
|
||||||
|
/// [`MainThreadExecutor`]. Used only in the standalone wrapper.
|
||||||
|
impl<T: Send, E: AsyncExecutor<Task = T>> MainThreadExecutor<T> for E {
|
||||||
|
unsafe fn execute(&self, task: T) {
|
||||||
|
AsyncExecutor::execute(self, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -195,7 +195,7 @@ pub trait Plugin: Default + Send + 'static {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
aux: &mut AuxiliaryBuffers,
|
aux: &mut AuxiliaryBuffers,
|
||||||
context: &mut impl ProcessContext,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus;
|
) -> ProcessStatus;
|
||||||
|
|
||||||
/// Called when the plugin is deactivated. The host will call
|
/// Called when the plugin is deactivated. The host will call
|
||||||
|
|
|
@ -2,9 +2,10 @@ use atomic_refcell::AtomicRefMut;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::wrapper::{OutputParamEvent, Wrapper};
|
use super::wrapper::{OutputParamEvent, Task, Wrapper};
|
||||||
use crate::async_executor::AsyncExecutor;
|
use crate::async_executor::AsyncExecutor;
|
||||||
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
|
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
|
||||||
|
use crate::event_loop::EventLoop;
|
||||||
use crate::midi::NoteEvent;
|
use crate::midi::NoteEvent;
|
||||||
use crate::params::internals::ParamPtr;
|
use crate::params::internals::ParamPtr;
|
||||||
use crate::plugin::ClapPlugin;
|
use crate::plugin::ClapPlugin;
|
||||||
|
@ -131,7 +132,15 @@ impl<P: ClapPlugin> InitContext<P> for WrapperInitContext<'_, P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
|
impl<P: ClapPlugin> ProcessContext<P> for WrapperProcessContext<'_, P> {
|
||||||
|
fn execute_async(
|
||||||
|
&self,
|
||||||
|
task: <<P as crate::prelude::Plugin>::AsyncExecutor as AsyncExecutor>::Task,
|
||||||
|
) {
|
||||||
|
let task_posted = self.wrapper.do_maybe_async(Task::PluginTask(task));
|
||||||
|
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
|
||||||
|
}
|
||||||
|
|
||||||
fn plugin_api(&self) -> PluginApi {
|
fn plugin_api(&self) -> PluginApi {
|
||||||
PluginApi::Clap
|
PluginApi::Clap
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ use std::time::Duration;
|
||||||
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
|
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
|
||||||
use super::descriptor::PluginDescriptor;
|
use super::descriptor::PluginDescriptor;
|
||||||
use super::util::ClapPtr;
|
use super::util::ClapPtr;
|
||||||
|
use crate::async_executor::AsyncExecutor;
|
||||||
use crate::buffer::Buffer;
|
use crate::buffer::Buffer;
|
||||||
use crate::context::Transport;
|
use crate::context::Transport;
|
||||||
use crate::editor::{Editor, ParentWindowHandle};
|
use crate::editor::{Editor, ParentWindowHandle};
|
||||||
|
@ -84,7 +85,7 @@ use crate::midi::{MidiConfig, NoteEvent};
|
||||||
use crate::params::internals::ParamPtr;
|
use crate::params::internals::ParamPtr;
|
||||||
use crate::params::{ParamFlags, Params};
|
use crate::params::{ParamFlags, Params};
|
||||||
use crate::plugin::{
|
use crate::plugin::{
|
||||||
AuxiliaryBuffers, BufferConfig, BusConfig, ClapPlugin, ProcessMode, ProcessStatus,
|
AuxiliaryBuffers, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessMode, ProcessStatus,
|
||||||
};
|
};
|
||||||
use crate::util::permit_alloc;
|
use crate::util::permit_alloc;
|
||||||
use crate::wrapper::clap::util::{read_stream, write_stream};
|
use crate::wrapper::clap::util::{read_stream, write_stream};
|
||||||
|
@ -257,7 +258,7 @@ pub struct Wrapper<P: ClapPlugin> {
|
||||||
/// implementations. Instead, we'll post tasks to this queue, ask the host to call
|
/// implementations. Instead, we'll post tasks to this queue, ask the host to call
|
||||||
/// [`on_main_thread()`][Self::on_main_thread()] on the main thread, and then continue to pop
|
/// [`on_main_thread()`][Self::on_main_thread()] on the main thread, and then continue to pop
|
||||||
/// tasks off this queue there until it is empty.
|
/// tasks off this queue there until it is empty.
|
||||||
tasks: ArrayQueue<Task>,
|
tasks: ArrayQueue<Task<P>>,
|
||||||
/// The ID of the main thread. In practice this is the ID of the thread that created this
|
/// The ID of the main thread. In practice this is the ID of the thread that created this
|
||||||
/// object. If the host supports the thread check extension (and
|
/// object. If the host supports the thread check extension (and
|
||||||
/// [`host_thread_check`][Self::host_thread_check] thus contains a value), then that extension
|
/// [`host_thread_check`][Self::host_thread_check] thus contains a value), then that extension
|
||||||
|
@ -268,8 +269,10 @@ pub struct Wrapper<P: ClapPlugin> {
|
||||||
/// Tasks that can be sent from the plugin to be executed on the main thread in a non-blocking
|
/// 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
|
/// 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.
|
/// implementation, this uses [`clap_host::request_callback()`] instead.
|
||||||
#[derive(Debug)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum Task {
|
pub enum Task<P: Plugin> {
|
||||||
|
/// Execute one of the plugin's background tasks.
|
||||||
|
PluginTask(<P::AsyncExecutor as AsyncExecutor>::Task),
|
||||||
/// Inform the host that the latency has changed.
|
/// Inform the host that the latency has changed.
|
||||||
LatencyChanged,
|
LatencyChanged,
|
||||||
/// Inform the host that the voice info has changed.
|
/// Inform the host that the voice info has changed.
|
||||||
|
@ -311,12 +314,12 @@ pub enum OutputParamEvent {
|
||||||
|
|
||||||
/// Because CLAP has this [`clap_host::request_host_callback()`] function, we don't need to use
|
/// 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.
|
/// `OsEventLoop` and can instead just request a main thread callback directly.
|
||||||
impl<P: ClapPlugin> EventLoop<Task, Wrapper<P>> for Wrapper<P> {
|
impl<P: ClapPlugin> EventLoop<Task<P>, Wrapper<P>> for Wrapper<P> {
|
||||||
fn new_and_spawn(_executor: std::sync::Weak<Self>) -> Self {
|
fn new_and_spawn(_executor: std::sync::Weak<Self>) -> Self {
|
||||||
panic!("What are you doing");
|
panic!("What are you doing");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_maybe_async(&self, task: Task) -> bool {
|
fn do_maybe_async(&self, task: Task<P>) -> bool {
|
||||||
if self.is_main_thread() {
|
if self.is_main_thread() {
|
||||||
unsafe { self.execute(task) };
|
unsafe { self.execute(task) };
|
||||||
true
|
true
|
||||||
|
@ -346,10 +349,11 @@ impl<P: ClapPlugin> EventLoop<Task, Wrapper<P>> for Wrapper<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: ClapPlugin> MainThreadExecutor<Task> for Wrapper<P> {
|
impl<P: ClapPlugin> MainThreadExecutor<Task<P>> for Wrapper<P> {
|
||||||
unsafe fn execute(&self, task: Task) {
|
unsafe fn execute(&self, task: Task<P>) {
|
||||||
// This function is always called from the main thread, from [Self::on_main_thread].
|
// This function is always called from the main thread, from [Self::on_main_thread].
|
||||||
match task {
|
match task {
|
||||||
|
Task::PluginTask(task) => AsyncExecutor::execute(&self.async_executor, task),
|
||||||
Task::LatencyChanged => match &*self.host_latency.borrow() {
|
Task::LatencyChanged => match &*self.host_latency.borrow() {
|
||||||
Some(host_latency) => {
|
Some(host_latency) => {
|
||||||
// XXX: The CLAP docs mention that you should request a restart if this happens
|
// XXX: The CLAP docs mention that you should request a restart if this happens
|
||||||
|
|
|
@ -5,6 +5,7 @@ use super::backend::Backend;
|
||||||
use super::wrapper::{GuiTask, Wrapper};
|
use super::wrapper::{GuiTask, Wrapper};
|
||||||
use crate::async_executor::AsyncExecutor;
|
use crate::async_executor::AsyncExecutor;
|
||||||
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
|
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
|
||||||
|
use crate::event_loop::EventLoop;
|
||||||
use crate::midi::NoteEvent;
|
use crate::midi::NoteEvent;
|
||||||
use crate::params::internals::ParamPtr;
|
use crate::params::internals::ParamPtr;
|
||||||
use crate::plugin::Plugin;
|
use crate::plugin::Plugin;
|
||||||
|
@ -96,7 +97,12 @@ impl<P: Plugin, B: Backend> InitContext<P> for WrapperInitContext<'_, P, B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Plugin, B: Backend> ProcessContext for WrapperProcessContext<'_, P, B> {
|
impl<P: Plugin, B: Backend> ProcessContext<P> for WrapperProcessContext<'_, P, B> {
|
||||||
|
fn execute_async(&self, task: <P::AsyncExecutor as AsyncExecutor>::Task) {
|
||||||
|
let task_posted = self.wrapper.event_loop.do_maybe_async(task);
|
||||||
|
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
|
||||||
|
}
|
||||||
|
|
||||||
fn plugin_api(&self) -> PluginApi {
|
fn plugin_api(&self) -> PluginApi {
|
||||||
PluginApi::Standalone
|
PluginApi::Standalone
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,10 @@ use std::thread;
|
||||||
use super::backend::Backend;
|
use super::backend::Backend;
|
||||||
use super::config::WrapperConfig;
|
use super::config::WrapperConfig;
|
||||||
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
|
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
|
||||||
|
use crate::async_executor::AsyncExecutor;
|
||||||
use crate::context::Transport;
|
use crate::context::Transport;
|
||||||
use crate::editor::{Editor, ParentWindowHandle};
|
use crate::editor::{Editor, ParentWindowHandle};
|
||||||
|
use crate::event_loop::{EventLoop, OsEventLoop};
|
||||||
use crate::midi::NoteEvent;
|
use crate::midi::NoteEvent;
|
||||||
use crate::params::internals::ParamPtr;
|
use crate::params::internals::ParamPtr;
|
||||||
use crate::params::{ParamFlags, Params};
|
use crate::params::{ParamFlags, Params};
|
||||||
|
@ -36,7 +38,7 @@ pub struct Wrapper<P: Plugin, B: Backend> {
|
||||||
/// The wrapped plugin instance.
|
/// The wrapped plugin instance.
|
||||||
plugin: Mutex<P>,
|
plugin: Mutex<P>,
|
||||||
/// The plugin's background task executor.
|
/// The plugin's background task executor.
|
||||||
pub async_executor: P::AsyncExecutor,
|
pub async_executor: Arc<P::AsyncExecutor>,
|
||||||
/// The plugin's parameters. These are fetched once during initialization. That way the
|
/// The plugin's parameters. These are fetched once during initialization. That way the
|
||||||
/// `ParamPtr`s are guaranteed to live at least as long as this object and we can interact with
|
/// `ParamPtr`s are guaranteed to live at least as long as this object and we can interact with
|
||||||
/// the `Params` object without having to acquire a lock on `plugin`.
|
/// the `Params` object without having to acquire a lock on `plugin`.
|
||||||
|
@ -52,6 +54,14 @@ pub struct Wrapper<P: Plugin, B: Backend> {
|
||||||
/// creating an editor.
|
/// creating an editor.
|
||||||
pub editor: Option<Arc<Mutex<Box<dyn Editor>>>>,
|
pub editor: Option<Arc<Mutex<Box<dyn Editor>>>>,
|
||||||
|
|
||||||
|
/// A realtime-safe task queue so the plugin can schedule tasks that need to be run later on the
|
||||||
|
/// GUI thread. See the same field in the VST3 wrapper for more information on why this looks
|
||||||
|
/// the way it does.
|
||||||
|
///
|
||||||
|
/// This is only used for executing [`AsyncExecutor`] tasks, so it's parameterized directly over
|
||||||
|
/// that using a special `MainThreadExecutor` wrapper around `AsyncExecutor`.
|
||||||
|
pub(crate) event_loop: OsEventLoop<<P::AsyncExecutor as AsyncExecutor>::Task, P::AsyncExecutor>,
|
||||||
|
|
||||||
config: WrapperConfig,
|
config: WrapperConfig,
|
||||||
|
|
||||||
/// The bus and buffer configurations are static for the standalone target.
|
/// The bus and buffer configurations are static for the standalone target.
|
||||||
|
@ -134,7 +144,7 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
|
||||||
/// not accept the IO configuration from the wrapper config.
|
/// not accept the IO configuration from the wrapper config.
|
||||||
pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
|
pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
|
||||||
let plugin = P::default();
|
let plugin = P::default();
|
||||||
let async_executor = plugin.async_executor();
|
let async_executor = Arc::new(plugin.async_executor());
|
||||||
let params = plugin.params();
|
let params = plugin.params();
|
||||||
let editor = plugin.editor().map(|editor| Arc::new(Mutex::new(editor)));
|
let editor = plugin.editor().map(|editor| Arc::new(Mutex::new(editor)));
|
||||||
|
|
||||||
|
@ -181,7 +191,7 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
|
||||||
backend: AtomicRefCell::new(backend),
|
backend: AtomicRefCell::new(backend),
|
||||||
|
|
||||||
plugin: Mutex::new(plugin),
|
plugin: Mutex::new(plugin),
|
||||||
async_executor,
|
async_executor: async_executor.clone(),
|
||||||
params,
|
params,
|
||||||
known_parameters: param_map.iter().map(|(_, ptr, _)| *ptr).collect(),
|
known_parameters: param_map.iter().map(|(_, ptr, _)| *ptr).collect(),
|
||||||
param_map: param_map
|
param_map: param_map
|
||||||
|
@ -190,6 +200,8 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
|
||||||
.collect(),
|
.collect(),
|
||||||
editor,
|
editor,
|
||||||
|
|
||||||
|
event_loop: OsEventLoop::new_and_spawn(Arc::downgrade(&async_executor)),
|
||||||
|
|
||||||
bus_config: BusConfig {
|
bus_config: BusConfig {
|
||||||
num_input_channels: config.input_channels.unwrap_or(P::DEFAULT_INPUT_CHANNELS),
|
num_input_channels: config.input_channels.unwrap_or(P::DEFAULT_INPUT_CHANNELS),
|
||||||
num_output_channels: config.output_channels.unwrap_or(P::DEFAULT_OUTPUT_CHANNELS),
|
num_output_channels: config.output_channels.unwrap_or(P::DEFAULT_OUTPUT_CHANNELS),
|
||||||
|
|
|
@ -134,7 +134,12 @@ impl<P: Vst3Plugin> InitContext<P> for WrapperInitContext<'_, P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
|
impl<P: Vst3Plugin> ProcessContext<P> for WrapperProcessContext<'_, P> {
|
||||||
|
fn execute_async(&self, task: <P::AsyncExecutor as AsyncExecutor>::Task) {
|
||||||
|
let task_posted = self.inner.do_maybe_async(Task::PluginTask(task));
|
||||||
|
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
|
||||||
|
}
|
||||||
|
|
||||||
fn plugin_api(&self) -> PluginApi {
|
fn plugin_api(&self) -> PluginApi {
|
||||||
PluginApi::Vst3
|
PluginApi::Vst3
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use super::note_expressions::NoteExpressionController;
|
||||||
use super::param_units::ParamUnits;
|
use super::param_units::ParamUnits;
|
||||||
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
|
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
|
||||||
use super::view::WrapperView;
|
use super::view::WrapperView;
|
||||||
|
use crate::async_executor::AsyncExecutor;
|
||||||
use crate::buffer::Buffer;
|
use crate::buffer::Buffer;
|
||||||
use crate::context::Transport;
|
use crate::context::Transport;
|
||||||
use crate::editor::Editor;
|
use crate::editor::Editor;
|
||||||
|
@ -21,7 +22,7 @@ use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
|
||||||
use crate::midi::{MidiConfig, NoteEvent};
|
use crate::midi::{MidiConfig, NoteEvent};
|
||||||
use crate::params::internals::ParamPtr;
|
use crate::params::internals::ParamPtr;
|
||||||
use crate::params::{ParamFlags, Params};
|
use crate::params::{ParamFlags, Params};
|
||||||
use crate::plugin::{BufferConfig, BusConfig, ProcessMode, ProcessStatus, Vst3Plugin};
|
use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessMode, ProcessStatus, Vst3Plugin};
|
||||||
use crate::wrapper::state::{self, PluginState};
|
use crate::wrapper::state::{self, PluginState};
|
||||||
use crate::wrapper::util::{hash_param_id, process_wrapper};
|
use crate::wrapper::util::{hash_param_id, process_wrapper};
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
|
||||||
/// reason to mutably borrow the event loop, so reads will never be contested.
|
/// reason to mutably borrow the event loop, so reads will never be contested.
|
||||||
///
|
///
|
||||||
/// TODO: Is there a better type for Send+Sync late initialization?
|
/// TODO: Is there a better type for Send+Sync late initialization?
|
||||||
pub event_loop: AtomicRefCell<Option<OsEventLoop<Task, Self>>>,
|
pub event_loop: AtomicRefCell<Option<OsEventLoop<Task<P>, Self>>>,
|
||||||
|
|
||||||
/// Whether the plugin is currently processing audio. In other words, the last state
|
/// Whether the plugin is currently processing audio. In other words, the last state
|
||||||
/// `IAudioProcessor::setActive()` has been called with.
|
/// `IAudioProcessor::setActive()` has been called with.
|
||||||
|
@ -150,8 +151,10 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
|
||||||
/// Tasks that can be sent from the plugin to be executed on the main thread in a non-blocking
|
/// Tasks that can be sent from the plugin to be executed on the main thread in a non-blocking
|
||||||
/// realtime-safe way (either a random thread or `IRunLoop` on Linux, the OS' message loop on
|
/// realtime-safe way (either a random thread or `IRunLoop` on Linux, the OS' message loop on
|
||||||
/// Windows and macOS).
|
/// Windows and macOS).
|
||||||
#[derive(Debug, Clone)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum Task {
|
pub enum Task<P: Plugin> {
|
||||||
|
/// Execute one of the plugin's background tasks.
|
||||||
|
PluginTask(<P::AsyncExecutor as AsyncExecutor>::Task),
|
||||||
/// Trigger a restart with the given restart flags. This is a bit set of the flags from
|
/// Trigger a restart with the given restart flags. This is a bit set of the flags from
|
||||||
/// [`vst3_sys::vst::RestartFlags`].
|
/// [`vst3_sys::vst::RestartFlags`].
|
||||||
TriggerRestart(i32),
|
TriggerRestart(i32),
|
||||||
|
@ -353,7 +356,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
///
|
///
|
||||||
/// If the task queue is full, then this will return false.
|
/// If the task queue is full, then this will return false.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn do_maybe_async(&self, task: Task) -> bool {
|
pub fn do_maybe_async(&self, task: Task<P>) -> bool {
|
||||||
let event_loop = self.event_loop.borrow();
|
let event_loop = self.event_loop.borrow();
|
||||||
let event_loop = event_loop.as_ref().unwrap();
|
let event_loop = event_loop.as_ref().unwrap();
|
||||||
if event_loop.is_main_thread() {
|
if event_loop.is_main_thread() {
|
||||||
|
@ -514,8 +517,8 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Vst3Plugin> MainThreadExecutor<Task> for WrapperInner<P> {
|
impl<P: Vst3Plugin> MainThreadExecutor<Task<P>> for WrapperInner<P> {
|
||||||
unsafe fn execute(&self, task: Task) {
|
unsafe fn execute(&self, task: Task<P>) {
|
||||||
// This function is always called from the main thread
|
// This function is always called from the main thread
|
||||||
// TODO: When we add GUI resizing and context menus, this should propagate those events to
|
// TODO: When we add GUI resizing and context menus, this should propagate those events to
|
||||||
// `IRunLoop` on Linux to keep REAPER happy. That does mean a double spool, but we can
|
// `IRunLoop` on Linux to keep REAPER happy. That does mean a double spool, but we can
|
||||||
|
@ -523,6 +526,7 @@ impl<P: Vst3Plugin> MainThreadExecutor<Task> for WrapperInner<P> {
|
||||||
// function for checking if a to be scheduled task can be handled right there and
|
// function for checking if a to be scheduled task can be handled right there and
|
||||||
// then).
|
// then).
|
||||||
match task {
|
match task {
|
||||||
|
Task::PluginTask(task) => AsyncExecutor::execute(&self.async_executor, task),
|
||||||
Task::TriggerRestart(flags) => match &*self.component_handler.borrow() {
|
Task::TriggerRestart(flags) => match &*self.component_handler.borrow() {
|
||||||
Some(handler) => {
|
Some(handler) => {
|
||||||
handler.restart_component(flags);
|
handler.restart_component(flags);
|
||||||
|
|
|
@ -96,7 +96,7 @@ struct RunLoopEventHandler<P: Vst3Plugin> {
|
||||||
/// implementations. Instead, we'll post tasks to this queue, ask the host to call
|
/// implementations. Instead, we'll post tasks to this queue, ask the host to call
|
||||||
/// [`on_main_thread()`][Self::on_main_thread()] on the main thread, and then continue to pop
|
/// [`on_main_thread()`][Self::on_main_thread()] on the main thread, and then continue to pop
|
||||||
/// tasks off this queue there until it is empty.
|
/// tasks off this queue there until it is empty.
|
||||||
tasks: ArrayQueue<Task>,
|
tasks: ArrayQueue<Task<P>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Vst3Plugin> WrapperView<P> {
|
impl<P: Vst3Plugin> WrapperView<P> {
|
||||||
|
@ -162,7 +162,7 @@ impl<P: Vst3Plugin> WrapperView<P> {
|
||||||
/// run on the host's UI thread. If not, then this will return an `Err` value containing the
|
/// run on the host's UI thread. If not, then this will return an `Err` value containing the
|
||||||
/// task so it can be run elsewhere.
|
/// task so it can be run elsewhere.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn do_maybe_in_run_loop(&self, task: Task) -> Result<(), Task> {
|
pub fn do_maybe_in_run_loop(&self, task: Task<P>) -> Result<(), Task<P>> {
|
||||||
match &*self.run_loop_event_handler.0.read() {
|
match &*self.run_loop_event_handler.0.read() {
|
||||||
Some(run_loop) => run_loop.post_task(task),
|
Some(run_loop) => run_loop.post_task(task),
|
||||||
None => Err(task),
|
None => Err(task),
|
||||||
|
@ -174,7 +174,7 @@ impl<P: Vst3Plugin> WrapperView<P> {
|
||||||
/// task so it can be run elsewhere.
|
/// task so it can be run elsewhere.
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn do_maybe_in_run_loop(&self, task: Task) -> Result<(), Task> {
|
pub fn do_maybe_in_run_loop(&self, task: Task<P>) -> Result<(), Task<P>> {
|
||||||
Err(task)
|
Err(task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ impl<P: Vst3Plugin> RunLoopEventHandler<P> {
|
||||||
|
|
||||||
/// Post a task to the tasks queue so it will be run on the host's GUI thread later. Returns the
|
/// Post a task to the tasks queue so it will be run on the host's GUI thread later. Returns the
|
||||||
/// task if the queue is full and the task could not be posted.
|
/// task if the queue is full and the task could not be posted.
|
||||||
pub fn post_task(&self, task: Task) -> Result<(), Task> {
|
pub fn post_task(&self, task: Task<P>) -> Result<(), Task<P>> {
|
||||||
self.tasks.push(task)?;
|
self.tasks.push(task)?;
|
||||||
|
|
||||||
// We need to use a Unix domain socket to let the host know to call our event handler. In
|
// We need to use a Unix domain socket to let the host know to call our event handler. In
|
||||||
|
|
Loading…
Reference in a new issue