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]
|
||||
|
||||
### 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
|
||||
|
||||
- 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
|
||||
enabled by setting the `Plugin::SAMPLE_ACCURATE_AUTOMATION` constant to
|
||||
`true`.
|
||||
- Support for CLAP's polyphonic modulation on a per-parameter basis.
|
||||
- Optional support for compressing the human readable JSON state files using
|
||||
[Zstandard](https://en.wikipedia.org/wiki/Zstd).
|
||||
- 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.
|
||||
- Support for flexible dynamic buffer configurations, including variable numbers
|
||||
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
|
||||
`cargo xtask bundle <package> <build_arguments>` command that automatically
|
||||
detects which plugin targets your plugin exposes and creates the correct
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
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::collections::VecDeque;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::wrapper::{OutputParamEvent, Task, Wrapper};
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::prelude::{
|
||||
ClapPlugin, GuiContext, InitContext, ParamPtr, PluginApi, PluginNoteEvent, ProcessContext,
|
||||
Transport,
|
||||
RemoteControlsContext, RemoteControlsPage, RemoteControlsSection, Transport,
|
||||
};
|
||||
use crate::wrapper::util::strlcpy;
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
fn drop(&mut self) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::{
|
||||
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::{
|
||||
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,
|
||||
|
@ -86,6 +89,7 @@ use crate::prelude::{
|
|||
ProcessMode, ProcessStatus, SysExMessage, TaskExecutor, Transport,
|
||||
};
|
||||
use crate::util::permit_alloc;
|
||||
use crate::wrapper::clap::context::RemoteControlPages;
|
||||
use crate::wrapper::clap::util::{read_stream, write_stream};
|
||||
use crate::wrapper::state::{self, PluginState};
|
||||
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>>>,
|
||||
|
||||
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_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 {
|
||||
this: AtomicRefCell::new(Weak::new()),
|
||||
|
||||
|
@ -626,6 +642,12 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
|
||||
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 {
|
||||
has_hard_realtime_requirement: Some(Self::ext_render_has_hard_realtime_requirement),
|
||||
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
|
||||
} else if id == CLAP_EXT_PARAMS {
|
||||
&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 {
|
||||
&wrapper.clap_plugin_render as *const _ as *const c_void
|
||||
} 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(
|
||||
_plugin: *const clap_plugin,
|
||||
) -> bool {
|
||||
|
|
Loading…
Reference in a new issue