Implement basic CLAP audio processing
This commit is contained in:
parent
a28e553fd2
commit
57ed66f8f6
2 changed files with 139 additions and 14 deletions
|
@ -1,6 +1,6 @@
|
||||||
use clap_sys::events::{
|
use clap_sys::events::{
|
||||||
clap_event_header, clap_event_param_mod, clap_event_param_value, clap_input_events,
|
clap_event_header, clap_event_param_mod, clap_event_param_value, clap_input_events,
|
||||||
clap_output_events, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE,
|
clap_output_events, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE,
|
||||||
};
|
};
|
||||||
use clap_sys::ext::params::{
|
use clap_sys::ext::params::{
|
||||||
clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS,
|
clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS,
|
||||||
|
@ -10,11 +10,15 @@ use clap_sys::ext::thread_check::{clap_host_thread_check, CLAP_EXT_THREAD_CHECK}
|
||||||
use clap_sys::host::clap_host;
|
use clap_sys::host::clap_host;
|
||||||
use clap_sys::id::clap_id;
|
use clap_sys::id::clap_id;
|
||||||
use clap_sys::plugin::clap_plugin;
|
use clap_sys::plugin::clap_plugin;
|
||||||
use clap_sys::process::{clap_process, clap_process_status};
|
use clap_sys::process::{
|
||||||
|
clap_process, clap_process_status, CLAP_PROCESS_CONTINUE, CLAP_PROCESS_CONTINUE_IF_NOT_QUIET,
|
||||||
|
CLAP_PROCESS_ERROR,
|
||||||
|
};
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use crossbeam::queue::ArrayQueue;
|
use crossbeam::queue::ArrayQueue;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use std::cmp;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::ffi::{c_void, CStr};
|
use std::ffi::{c_void, CStr};
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
@ -25,11 +29,11 @@ use std::thread::{self, ThreadId};
|
||||||
use super::context::WrapperProcessContext;
|
use super::context::WrapperProcessContext;
|
||||||
use super::descriptor::PluginDescriptor;
|
use super::descriptor::PluginDescriptor;
|
||||||
use super::util::ClapPtr;
|
use super::util::ClapPtr;
|
||||||
|
use crate::buffer::Buffer;
|
||||||
use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
|
use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
|
||||||
use crate::param::internals::ParamPtr;
|
use crate::param::internals::ParamPtr;
|
||||||
use crate::plugin::{BufferConfig, BusConfig, ClapPlugin};
|
use crate::plugin::{BufferConfig, BusConfig, ClapPlugin, NoteEvent, ProcessStatus};
|
||||||
use crate::wrapper::util::{hash_param_id, strlcpy};
|
use crate::wrapper::util::{hash_param_id, process_wrapper, strlcpy};
|
||||||
use crate::NoteEvent;
|
|
||||||
|
|
||||||
/// Right now the wrapper adds its own bypass parameter.
|
/// Right now the wrapper adds its own bypass parameter.
|
||||||
///
|
///
|
||||||
|
@ -67,6 +71,11 @@ pub struct Wrapper<P: ClapPlugin> {
|
||||||
///
|
///
|
||||||
/// TODO: Implement the latency extension.
|
/// TODO: Implement the latency extension.
|
||||||
pub current_latency: AtomicU32,
|
pub current_latency: AtomicU32,
|
||||||
|
/// Contains slices for the plugin's outputs. You can't directly create a nested slice form
|
||||||
|
/// apointer to pointers, so this needs to be preallocated in the setup call and kept around
|
||||||
|
/// between process calls. This buffer owns the vector, because otherwise it would need to store
|
||||||
|
/// a mutable reference to the data contained in this mutex.
|
||||||
|
pub output_buffer: RwLock<Buffer<'static>>,
|
||||||
|
|
||||||
// We'll query all of the host's extensions upfront
|
// We'll query all of the host's extensions upfront
|
||||||
host_callback: ClapPtr<clap_host>,
|
host_callback: ClapPtr<clap_host>,
|
||||||
|
@ -206,6 +215,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
bypass_state: AtomicBool::new(false),
|
bypass_state: AtomicBool::new(false),
|
||||||
input_events: RwLock::new(VecDeque::with_capacity(512)),
|
input_events: RwLock::new(VecDeque::with_capacity(512)),
|
||||||
current_latency: AtomicU32::new(0),
|
current_latency: AtomicU32::new(0),
|
||||||
|
output_buffer: RwLock::new(Buffer::default()),
|
||||||
|
|
||||||
host_callback,
|
host_callback,
|
||||||
thread_check,
|
thread_check,
|
||||||
|
@ -339,8 +349,11 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
/// from the process function.
|
/// from the process function.
|
||||||
pub unsafe fn handle_event(&self, event: *const clap_event_header) {
|
pub unsafe fn handle_event(&self, event: *const clap_event_header) {
|
||||||
let raw_event = &*event;
|
let raw_event = &*event;
|
||||||
match raw_event.type_ {
|
match (raw_event.space_id, raw_event.type_) {
|
||||||
CLAP_EVENT_PARAM_VALUE => {
|
// TODO: Implement the event filter
|
||||||
|
// TODO: Handle sample accurate parameter changes, possibly in a similar way to the
|
||||||
|
// smoothing
|
||||||
|
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE) => {
|
||||||
let event = &*(event as *const clap_event_param_value);
|
let event = &*(event as *const clap_event_param_value);
|
||||||
self.update_plain_value_by_hash(
|
self.update_plain_value_by_hash(
|
||||||
event.param_id,
|
event.param_id,
|
||||||
|
@ -348,7 +361,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
self.current_buffer_config.load().map(|c| c.sample_rate),
|
self.current_buffer_config.load().map(|c| c.sample_rate),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CLAP_EVENT_PARAM_MOD => {
|
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD) => {
|
||||||
let event = &*(event as *const clap_event_param_mod);
|
let event = &*(event as *const clap_event_param_mod);
|
||||||
self.update_plain_value_by_hash(
|
self.update_plain_value_by_hash(
|
||||||
event.param_id,
|
event.param_id,
|
||||||
|
@ -356,7 +369,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
self.current_buffer_config.load().map(|c| c.sample_rate),
|
self.current_buffer_config.load().map(|c| c.sample_rate),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO: Handle MIDI
|
// TODO: Handle MIDI if `P::ACCEPTS_MIDI` is true
|
||||||
// TODO: Make sure this only gets logged in debug mode
|
// TODO: Make sure this only gets logged in debug mode
|
||||||
_ => nih_log!(
|
_ => nih_log!(
|
||||||
"Unhandled CLAP event type {} for namespace {}",
|
"Unhandled CLAP event type {} for namespace {}",
|
||||||
|
@ -389,14 +402,21 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
max_buffer_size: max_frames_count,
|
max_buffer_size: max_frames_count,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Reset smoothers
|
// Befure initializing the plugin, make sure all smoothers are set the the default values
|
||||||
|
for param in wrapper.param_by_hash.values() {
|
||||||
|
param.update_smoother(buffer_config.sample_rate, true);
|
||||||
|
}
|
||||||
|
|
||||||
if wrapper.plugin.write().initialize(
|
if wrapper.plugin.write().initialize(
|
||||||
&bus_config,
|
&bus_config,
|
||||||
&buffer_config,
|
&buffer_config,
|
||||||
&mut wrapper.make_process_context(),
|
&mut wrapper.make_process_context(),
|
||||||
) {
|
) {
|
||||||
// TODO: Allocate buffer slices
|
// Preallocate enough room in the output slices vector so we can convert a `*mut *mut
|
||||||
|
// f32` to a `&mut [&mut f32]` in the process call
|
||||||
|
wrapper.output_buffer.write().with_raw_vec(|output_slices| {
|
||||||
|
output_slices.resize_with(bus_config.num_output_channels as usize, || &mut [])
|
||||||
|
});
|
||||||
|
|
||||||
// Also store this for later, so we can reinitialize the plugin after restoring state
|
// Also store this for later, so we can reinitialize the plugin after restoring state
|
||||||
wrapper.current_buffer_config.store(Some(buffer_config));
|
wrapper.current_buffer_config.store(Some(buffer_config));
|
||||||
|
@ -424,7 +444,111 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
plugin: *const clap_plugin,
|
plugin: *const clap_plugin,
|
||||||
process: *const clap_process,
|
process: *const clap_process,
|
||||||
) -> clap_process_status {
|
) -> clap_process_status {
|
||||||
todo!();
|
let wrapper = &*(plugin as *const Self);
|
||||||
|
|
||||||
|
if process.is_null() {
|
||||||
|
return CLAP_PROCESS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
|
||||||
|
// sure that FTZ is set up correctly
|
||||||
|
process_wrapper(|| {
|
||||||
|
// We need to handle incoming automation and MIDI events. Since we don't support sample
|
||||||
|
// accuration automation yet and there's no way to get the last event for a parameter,
|
||||||
|
// we'll process every incomingevent.
|
||||||
|
let process = &*process;
|
||||||
|
if !process.in_events.is_null() {
|
||||||
|
let num_events = ((*process.in_events).size)(&*process.in_events);
|
||||||
|
for event_idx in 0..num_events {
|
||||||
|
let event = ((*process.in_events).get)(&*process.in_events, event_idx);
|
||||||
|
wrapper.handle_event(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// I don't think this is a thing for CLAP since there's a dedicated flush function, but
|
||||||
|
// might as well protect against this
|
||||||
|
// TOOD: Send the output events when doing a flush
|
||||||
|
if process.audio_outputs_count == 0 || process.frames_count == 0 {
|
||||||
|
nih_log!("CLAP process call event flush");
|
||||||
|
return CLAP_PROCESS_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The setups we suppport are:
|
||||||
|
// - 1 input bus
|
||||||
|
// - 1 output bus
|
||||||
|
// - 1 input bus and 1 output bus
|
||||||
|
nih_debug_assert!(
|
||||||
|
process.audio_inputs_count <= 1 && process.audio_outputs_count <= 1,
|
||||||
|
"The host provides more than one input or output bus"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Right now we don't handle any auxiliary outputs
|
||||||
|
nih_debug_assert!(!process.audio_outputs.is_null());
|
||||||
|
let audio_outputs = &*process.audio_outputs;
|
||||||
|
let num_output_channels = audio_outputs.channel_count as usize;
|
||||||
|
|
||||||
|
// This vector has been preallocated to contain enough slices as there are output
|
||||||
|
// channels
|
||||||
|
// TODO: The audio buffers have a latency field, should we use those?
|
||||||
|
// TODO: Like with VST3, should we expose some way to access or set the silence/constant
|
||||||
|
// flags?
|
||||||
|
let mut output_buffer = wrapper.output_buffer.write();
|
||||||
|
output_buffer.with_raw_vec(|output_slices| {
|
||||||
|
nih_debug_assert!(!audio_outputs.data32.is_null());
|
||||||
|
nih_debug_assert_eq!(num_output_channels, output_slices.len());
|
||||||
|
for (output_channel_idx, output_channel_slice) in
|
||||||
|
output_slices.iter_mut().enumerate()
|
||||||
|
{
|
||||||
|
// SAFETY: These pointers may not be valid outside of this function even though
|
||||||
|
// their lifetime is equal to this structs. This is still safe because they are
|
||||||
|
// only dereferenced here later as part of this process function.
|
||||||
|
*output_channel_slice = std::slice::from_raw_parts_mut(
|
||||||
|
*(audio_outputs.data32 as *mut *mut f32).add(output_channel_idx),
|
||||||
|
process.frames_count as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Most hosts process data in place, in which case we don't need to do any copying
|
||||||
|
// ourselves. If the pointers do not alias, then we'll do the copy here and then the
|
||||||
|
// plugin can just do normal in place processing.
|
||||||
|
if !process.audio_inputs.is_null() {
|
||||||
|
// We currently don't support sidechain inputs
|
||||||
|
let audio_inputs = &*process.audio_inputs;
|
||||||
|
let num_input_channels = audio_inputs.channel_count as usize;
|
||||||
|
nih_debug_assert!(
|
||||||
|
num_input_channels <= num_output_channels,
|
||||||
|
"Stereo to mono and similar configurations are not supported"
|
||||||
|
);
|
||||||
|
for input_channel_idx in 0..cmp::min(num_input_channels, num_output_channels) {
|
||||||
|
let output_channel_ptr =
|
||||||
|
*(audio_outputs.data32 as *mut *mut f32).add(input_channel_idx);
|
||||||
|
let input_channel_ptr = *(audio_inputs.data32).add(input_channel_idx);
|
||||||
|
if input_channel_ptr != output_channel_ptr {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
input_channel_ptr,
|
||||||
|
output_channel_ptr,
|
||||||
|
process.frames_count as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut plugin = wrapper.plugin.write();
|
||||||
|
let mut context = wrapper.make_process_context();
|
||||||
|
match plugin.process(&mut output_buffer, &mut context) {
|
||||||
|
ProcessStatus::Error(err) => {
|
||||||
|
nih_debug_assert_failure!("Process error: {}", err);
|
||||||
|
|
||||||
|
CLAP_PROCESS_ERROR
|
||||||
|
}
|
||||||
|
ProcessStatus::Normal => CLAP_PROCESS_CONTINUE_IF_NOT_QUIET,
|
||||||
|
ProcessStatus::Tail(_) => CLAP_PROCESS_CONTINUE,
|
||||||
|
ProcessStatus::KeepAlive => CLAP_PROCESS_CONTINUE,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle parameter outputs/automation
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn get_extension(
|
unsafe extern "C" fn get_extension(
|
||||||
|
|
|
@ -797,6 +797,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible the host only wanted to send new parameter values
|
// It's possible the host only wanted to send new parameter values
|
||||||
|
// TOOD: Send the output events when doing a flush
|
||||||
if data.num_outputs == 0 {
|
if data.num_outputs == 0 {
|
||||||
nih_log!("VST3 parameter flush");
|
nih_log!("VST3 parameter flush");
|
||||||
return kResultOk;
|
return kResultOk;
|
||||||
|
@ -826,7 +827,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
(*data.outputs).buffers,
|
(*data.outputs).buffers,
|
||||||
);
|
);
|
||||||
|
|
||||||
// This vector has been reallocated to contain enough slices as there are output
|
// This vector has been preallocated to contain enough slices as there are output
|
||||||
// channels
|
// channels
|
||||||
let mut output_buffer = self.inner.output_buffer.write();
|
let mut output_buffer = self.inner.output_buffer.write();
|
||||||
output_buffer.with_raw_vec(|output_slices| {
|
output_buffer.with_raw_vec(|output_slices| {
|
||||||
|
@ -868,7 +869,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let plugin = &mut *self.inner.plugin.data_ptr();
|
let mut plugin = self.inner.plugin.write();
|
||||||
let mut context = self.inner.make_process_context();
|
let mut context = self.inner.make_process_context();
|
||||||
match plugin.process(&mut output_buffer, &mut context) {
|
match plugin.process(&mut output_buffer, &mut context) {
|
||||||
ProcessStatus::Error(err) => {
|
ProcessStatus::Error(err) => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue