Allow declaratively defining CLAP remote controls
This commit is contained in:
parent
841fe2424c
commit
911c0d57d5
|
@ -12,6 +12,14 @@ state is to list breaking changes.
|
||||||
|
|
||||||
## [2023-04-22]
|
## [2023-04-22]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- CLAP plugins can optionally declare pages of [remote
|
||||||
|
controls](https://github.com/free-audio/clap/blob/main/include/clap/ext/draft/remote-controls.h)
|
||||||
|
so DAWs can more automatically map pages of the plugin's parameters to
|
||||||
|
hardware controllers. This is currently a draft extension, so until the
|
||||||
|
extension is finalized host support may break at any moment.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- The CLAP version has been updated to 1.1.8.
|
- The CLAP version has been updated to 1.1.8.
|
||||||
|
|
|
@ -134,7 +134,6 @@ Scroll down for more information on the underlying plugin framework.
|
||||||
- Optional sample accurate automation support for VST3 and CLAP that can be
|
- Optional sample accurate automation support for VST3 and CLAP that can be
|
||||||
enabled by setting the `Plugin::SAMPLE_ACCURATE_AUTOMATION` constant to
|
enabled by setting the `Plugin::SAMPLE_ACCURATE_AUTOMATION` constant to
|
||||||
`true`.
|
`true`.
|
||||||
- Support for CLAP's polyphonic modulation on a per-parameter basis.
|
|
||||||
- Optional support for compressing the human readable JSON state files using
|
- Optional support for compressing the human readable JSON state files using
|
||||||
[Zstandard](https://en.wikipedia.org/wiki/Zstd).
|
[Zstandard](https://en.wikipedia.org/wiki/Zstd).
|
||||||
- Comes with adapters for popular Rust GUI frameworks as well as some basic
|
- Comes with adapters for popular Rust GUI frameworks as well as some basic
|
||||||
|
@ -152,6 +151,10 @@ Scroll down for more information on the underlying plugin framework.
|
||||||
byte buffers in the process function.
|
byte buffers in the process function.
|
||||||
- Support for flexible dynamic buffer configurations, including variable numbers
|
- Support for flexible dynamic buffer configurations, including variable numbers
|
||||||
of input and output ports.
|
of input and output ports.
|
||||||
|
- First-class support several more exotic CLAP features:
|
||||||
|
- Both monophonic and polyphonic parameter modulation are supported.
|
||||||
|
- Plugins can declaratively define pages of remote controls that DAWs can bind
|
||||||
|
to hardware controllers.
|
||||||
- A plugin bundler accessible through the
|
- A plugin bundler accessible through the
|
||||||
`cargo xtask bundle <package> <build_arguments>` command that automatically
|
`cargo xtask bundle <package> <build_arguments>` command that automatically
|
||||||
detects which plugin targets your plugin exposes and creates the correct
|
detects which plugin targets your plugin exposes and creates the correct
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
use atomic_refcell::AtomicRefMut;
|
use atomic_refcell::AtomicRefMut;
|
||||||
|
use clap_sys::ext::draft::remote_controls::{
|
||||||
|
clap_remote_controls_page, CLAP_REMOTE_CONTROLS_COUNT,
|
||||||
|
};
|
||||||
|
use clap_sys::id::{clap_id, CLAP_INVALID_ID};
|
||||||
|
use clap_sys::string_sizes::CLAP_NAME_SIZE;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::VecDeque;
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::wrapper::{OutputParamEvent, Task, Wrapper};
|
use super::wrapper::{OutputParamEvent, Task, Wrapper};
|
||||||
use crate::event_loop::EventLoop;
|
use crate::event_loop::EventLoop;
|
||||||
use crate::prelude::{
|
use crate::prelude::{
|
||||||
ClapPlugin, GuiContext, InitContext, ParamPtr, PluginApi, PluginNoteEvent, ProcessContext,
|
ClapPlugin, GuiContext, InitContext, ParamPtr, PluginApi, PluginNoteEvent, ProcessContext,
|
||||||
Transport,
|
RemoteControlsContext, RemoteControlsPage, RemoteControlsSection, Transport,
|
||||||
};
|
};
|
||||||
|
use crate::wrapper::util::strlcpy;
|
||||||
|
|
||||||
/// An [`InitContext`] implementation for the wrapper.
|
/// An [`InitContext`] implementation for the wrapper.
|
||||||
///
|
///
|
||||||
|
@ -49,6 +55,16 @@ pub(crate) struct WrapperGuiContext<P: ClapPlugin> {
|
||||||
atomic_refcell::AtomicRefCell<crate::wrapper::util::context_checks::ParamGestureChecker>,
|
atomic_refcell::AtomicRefCell<crate::wrapper::util::context_checks::ParamGestureChecker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`RemoteControlsContext`] implementation for the wrapper. This is used during initialization
|
||||||
|
/// to allow the plugin to declare remote control pages. This struct defines the pages in the
|
||||||
|
/// correct format.
|
||||||
|
pub(crate) struct RemoteControlPages<'a> {
|
||||||
|
param_ptr_to_hash: &'a HashMap<ParamPtr, u32>,
|
||||||
|
/// The remote control pages, as defined by the plugin. These don't reference any heap data so
|
||||||
|
/// we can store them directly.
|
||||||
|
pages: &'a mut Vec<clap_remote_controls_page>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: ClapPlugin> Drop for WrapperInitContext<'_, P> {
|
impl<P: ClapPlugin> Drop for WrapperInitContext<'_, P> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(samples) = self.pending_requests.latency_changed.take() {
|
if let Some(samples) = self.pending_requests.latency_changed.take() {
|
||||||
|
@ -225,3 +241,138 @@ impl<P: ClapPlugin> GuiContext for WrapperGuiContext<P> {
|
||||||
self.wrapper.set_state_object_from_gui(state)
|
self.wrapper.set_state_object_from_gui(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A remote control section. The plugin can fill this with information for one or more pages.
|
||||||
|
pub(crate) struct Section {
|
||||||
|
pages: Vec<Page>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A remote control page. These are automatically split into multiple pages if the number of
|
||||||
|
/// controls exceeds 8.
|
||||||
|
pub(crate) struct Page {
|
||||||
|
name: String,
|
||||||
|
params: Vec<Option<ParamPtr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RemoteControlPages<'a> {
|
||||||
|
/// Allow the plugin to define remote control pages and add them to `pages`. This does not clear
|
||||||
|
/// `pages` first.
|
||||||
|
pub fn define_remote_control_pages<P: ClapPlugin>(
|
||||||
|
plugin: &P,
|
||||||
|
pages: &'a mut Vec<clap_remote_controls_page>,
|
||||||
|
param_ptr_to_hash: &'a HashMap<ParamPtr, u32>,
|
||||||
|
) {
|
||||||
|
// The magic happens in the `add_section()` function defined below
|
||||||
|
plugin.remote_controls(&mut Self {
|
||||||
|
pages,
|
||||||
|
param_ptr_to_hash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform the boilerplate needed for creating and adding a new [`clap_remote_controls_page`].
|
||||||
|
/// If `params` contains more than eight parameters then any further parameters will be lost.
|
||||||
|
fn add_clap_page(
|
||||||
|
&mut self,
|
||||||
|
section: &str,
|
||||||
|
page_name: &str,
|
||||||
|
params: impl IntoIterator<Item = Option<ParamPtr>>,
|
||||||
|
) {
|
||||||
|
let mut page = clap_remote_controls_page {
|
||||||
|
section_name: [0; CLAP_NAME_SIZE],
|
||||||
|
// Pages are numbered sequentially
|
||||||
|
page_id: self.pages.len() as clap_id,
|
||||||
|
page_name: [0; CLAP_NAME_SIZE],
|
||||||
|
param_ids: [CLAP_INVALID_ID; CLAP_REMOTE_CONTROLS_COUNT],
|
||||||
|
is_for_preset: false,
|
||||||
|
};
|
||||||
|
strlcpy(&mut page.section_name, section);
|
||||||
|
strlcpy(&mut page.page_name, page_name);
|
||||||
|
|
||||||
|
let mut params = params.into_iter();
|
||||||
|
for (param_id, param_ptr) in page.param_ids.iter_mut().zip(&mut params) {
|
||||||
|
// `param_id` already has the correct value if `param_ptr` is empty/a spacer
|
||||||
|
if let Some(param_ptr) = param_ptr {
|
||||||
|
*param_id = self.param_ptr_to_id(param_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nih_debug_assert!(
|
||||||
|
params.next().is_none(),
|
||||||
|
"More than eight parameters were passed to 'RemoteControlPages::add_page()', this is \
|
||||||
|
a NIH-plug bug."
|
||||||
|
);
|
||||||
|
|
||||||
|
self.pages.push(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform a `ParamPtr` to the associated CLAP parameter ID/hash. Returns -1/invalid
|
||||||
|
/// parameter and triggers a debug assertion when the parameter is not known.
|
||||||
|
fn param_ptr_to_id(&self, ptr: ParamPtr) -> clap_id {
|
||||||
|
match self.param_ptr_to_hash.get(&ptr) {
|
||||||
|
Some(id) => *id,
|
||||||
|
None => {
|
||||||
|
nih_debug_assert_failure!(
|
||||||
|
"An unknown parameter was added to a remote control page, ignoring..."
|
||||||
|
);
|
||||||
|
|
||||||
|
CLAP_INVALID_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteControlsContext for RemoteControlPages<'_> {
|
||||||
|
type Section = Section;
|
||||||
|
|
||||||
|
fn add_section(&mut self, name: impl Into<String>, f: impl FnOnce(&mut Self::Section)) {
|
||||||
|
let section_name = name.into();
|
||||||
|
let mut section = Section {
|
||||||
|
pages: Vec::with_capacity(1),
|
||||||
|
};
|
||||||
|
f(&mut section);
|
||||||
|
|
||||||
|
// The pages in the section may need to be split up into multiple pages if it defines more
|
||||||
|
// than eight parameters. This keeps the interface flexible for potential future expansion
|
||||||
|
// and makes manual paging unnecessary in some situations.
|
||||||
|
for page in section.pages {
|
||||||
|
if page.params.len() > CLAP_REMOTE_CONTROLS_COUNT {
|
||||||
|
for (subpage_idx, subpage_params) in
|
||||||
|
page.params.chunks(CLAP_REMOTE_CONTROLS_COUNT).enumerate()
|
||||||
|
{
|
||||||
|
let subpage_name = format!("{} {}", page.name, subpage_idx + 1);
|
||||||
|
self.add_clap_page(
|
||||||
|
§ion_name,
|
||||||
|
&subpage_name,
|
||||||
|
subpage_params.iter().copied(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.add_clap_page(§ion_name, &page.name, page.params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteControlsSection for Section {
|
||||||
|
type Page = Page;
|
||||||
|
|
||||||
|
fn add_page(&mut self, name: impl Into<String>, f: impl FnOnce(&mut Self::Page)) {
|
||||||
|
let mut page = Page {
|
||||||
|
name: name.into(),
|
||||||
|
params: Vec::with_capacity(CLAP_REMOTE_CONTROLS_COUNT),
|
||||||
|
};
|
||||||
|
f(&mut page);
|
||||||
|
|
||||||
|
self.pages.push(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteControlsPage for Page {
|
||||||
|
fn add_param(&mut self, param: &impl crate::prelude::Param) {
|
||||||
|
self.params.push(Some(param.as_ptr()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_spacer(&mut self) {
|
||||||
|
self.params.push(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ use clap_sys::ext::audio_ports::{
|
||||||
use clap_sys::ext::audio_ports_config::{
|
use clap_sys::ext::audio_ports_config::{
|
||||||
clap_audio_ports_config, clap_plugin_audio_ports_config, CLAP_EXT_AUDIO_PORTS_CONFIG,
|
clap_audio_ports_config, clap_plugin_audio_ports_config, CLAP_EXT_AUDIO_PORTS_CONFIG,
|
||||||
};
|
};
|
||||||
|
use clap_sys::ext::draft::remote_controls::{
|
||||||
|
clap_plugin_remote_controls, clap_remote_controls_page, CLAP_EXT_REMOTE_CONTROLS,
|
||||||
|
};
|
||||||
use clap_sys::ext::gui::{
|
use clap_sys::ext::gui::{
|
||||||
clap_gui_resize_hints, clap_host_gui, clap_plugin_gui, clap_window, CLAP_EXT_GUI,
|
clap_gui_resize_hints, clap_host_gui, clap_plugin_gui, clap_window, CLAP_EXT_GUI,
|
||||||
CLAP_WINDOW_API_COCOA, CLAP_WINDOW_API_WIN32, CLAP_WINDOW_API_X11,
|
CLAP_WINDOW_API_COCOA, CLAP_WINDOW_API_WIN32, CLAP_WINDOW_API_X11,
|
||||||
|
@ -86,6 +89,7 @@ use crate::prelude::{
|
||||||
ProcessMode, ProcessStatus, SysExMessage, TaskExecutor, Transport,
|
ProcessMode, ProcessStatus, SysExMessage, TaskExecutor, Transport,
|
||||||
};
|
};
|
||||||
use crate::util::permit_alloc;
|
use crate::util::permit_alloc;
|
||||||
|
use crate::wrapper::clap::context::RemoteControlPages;
|
||||||
use crate::wrapper::clap::util::{read_stream, write_stream};
|
use crate::wrapper::clap::util::{read_stream, write_stream};
|
||||||
use crate::wrapper::state::{self, PluginState};
|
use crate::wrapper::state::{self, PluginState};
|
||||||
use crate::wrapper::util::buffer_management::{BufferManager, ChannelPointers};
|
use crate::wrapper::util::buffer_management::{BufferManager, ChannelPointers};
|
||||||
|
@ -222,6 +226,10 @@ pub struct Wrapper<P: ClapPlugin> {
|
||||||
|
|
||||||
host_thread_check: AtomicRefCell<Option<ClapPtr<clap_host_thread_check>>>,
|
host_thread_check: AtomicRefCell<Option<ClapPtr<clap_host_thread_check>>>,
|
||||||
|
|
||||||
|
clap_plugin_remote_controls: clap_plugin_remote_controls,
|
||||||
|
/// The plugin's remote control pages, if it defines any. Filled when initializing the plugin.
|
||||||
|
remote_control_pages: Vec<clap_remote_controls_page>,
|
||||||
|
|
||||||
clap_plugin_render: clap_plugin_render,
|
clap_plugin_render: clap_plugin_render,
|
||||||
|
|
||||||
clap_plugin_state: clap_plugin_state,
|
clap_plugin_state: clap_plugin_state,
|
||||||
|
@ -513,6 +521,14 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support for the remote controls extension
|
||||||
|
let mut remote_control_pages = Vec::new();
|
||||||
|
RemoteControlPages::define_remote_control_pages(
|
||||||
|
&plugin,
|
||||||
|
&mut remote_control_pages,
|
||||||
|
¶m_ptr_to_hash,
|
||||||
|
);
|
||||||
|
|
||||||
let wrapper = Self {
|
let wrapper = Self {
|
||||||
this: AtomicRefCell::new(Weak::new()),
|
this: AtomicRefCell::new(Weak::new()),
|
||||||
|
|
||||||
|
@ -626,6 +642,12 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
|
|
||||||
host_thread_check: AtomicRefCell::new(None),
|
host_thread_check: AtomicRefCell::new(None),
|
||||||
|
|
||||||
|
clap_plugin_remote_controls: clap_plugin_remote_controls {
|
||||||
|
count: Some(Self::ext_remote_controls_count),
|
||||||
|
get: Some(Self::ext_remote_controls_get),
|
||||||
|
},
|
||||||
|
remote_control_pages,
|
||||||
|
|
||||||
clap_plugin_render: clap_plugin_render {
|
clap_plugin_render: clap_plugin_render {
|
||||||
has_hard_realtime_requirement: Some(Self::ext_render_has_hard_realtime_requirement),
|
has_hard_realtime_requirement: Some(Self::ext_render_has_hard_realtime_requirement),
|
||||||
set: Some(Self::ext_render_set),
|
set: Some(Self::ext_render_set),
|
||||||
|
@ -2282,6 +2304,8 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
&wrapper.clap_plugin_note_ports as *const _ as *const c_void
|
&wrapper.clap_plugin_note_ports as *const _ as *const c_void
|
||||||
} else if id == CLAP_EXT_PARAMS {
|
} else if id == CLAP_EXT_PARAMS {
|
||||||
&wrapper.clap_plugin_params as *const _ as *const c_void
|
&wrapper.clap_plugin_params as *const _ as *const c_void
|
||||||
|
} else if id == CLAP_EXT_REMOTE_CONTROLS {
|
||||||
|
&wrapper.clap_plugin_remote_controls as *const _ as *const c_void
|
||||||
} else if id == CLAP_EXT_RENDER {
|
} else if id == CLAP_EXT_RENDER {
|
||||||
&wrapper.clap_plugin_render as *const _ as *const c_void
|
&wrapper.clap_plugin_render as *const _ as *const c_void
|
||||||
} else if id == CLAP_EXT_STATE {
|
} else if id == CLAP_EXT_STATE {
|
||||||
|
@ -2997,6 +3021,31 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn ext_remote_controls_count(plugin: *const clap_plugin) -> u32 {
|
||||||
|
check_null_ptr!(0, plugin, (*plugin).plugin_data);
|
||||||
|
let wrapper = &*((*plugin).plugin_data as *const Self);
|
||||||
|
|
||||||
|
wrapper.remote_control_pages.len() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn ext_remote_controls_get(
|
||||||
|
plugin: *const clap_plugin,
|
||||||
|
page_index: u32,
|
||||||
|
page: *mut clap_remote_controls_page,
|
||||||
|
) -> bool {
|
||||||
|
check_null_ptr!(false, plugin, (*plugin).plugin_data, page);
|
||||||
|
let wrapper = &*((*plugin).plugin_data as *const Self);
|
||||||
|
|
||||||
|
nih_debug_assert!(page_index as usize <= wrapper.remote_control_pages.len());
|
||||||
|
match wrapper.remote_control_pages.get(page_index as usize) {
|
||||||
|
Some(p) => {
|
||||||
|
*page = *p;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn ext_render_has_hard_realtime_requirement(
|
unsafe extern "C" fn ext_render_has_hard_realtime_requirement(
|
||||||
_plugin: *const clap_plugin,
|
_plugin: *const clap_plugin,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
|
Loading…
Reference in a new issue