1use std::{
2 ffi::CStr,
3 fmt::{Display, Formatter},
4};
5
6use crate::{
7 events::{InputEvents, OutputEvents},
8 ext,
9 ffi::{
10 CLAP_PARAM_CLEAR_ALL, CLAP_PARAM_CLEAR_AUTOMATIONS, CLAP_PARAM_CLEAR_MODULATIONS,
11 CLAP_PARAM_IS_AUTOMATABLE, CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL,
12 CLAP_PARAM_IS_AUTOMATABLE_PER_KEY, CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID,
13 CLAP_PARAM_IS_AUTOMATABLE_PER_PORT, CLAP_PARAM_IS_BYPASS, CLAP_PARAM_IS_ENUM,
14 CLAP_PARAM_IS_HIDDEN, CLAP_PARAM_IS_MODULATABLE, CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL,
15 CLAP_PARAM_IS_MODULATABLE_PER_KEY, CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID,
16 CLAP_PARAM_IS_MODULATABLE_PER_PORT, CLAP_PARAM_IS_PERIODIC, CLAP_PARAM_IS_READONLY,
17 CLAP_PARAM_IS_STEPPED, CLAP_PARAM_REQUIRES_PROCESS, CLAP_PARAM_RESCAN_ALL,
18 CLAP_PARAM_RESCAN_INFO, CLAP_PARAM_RESCAN_TEXT, CLAP_PARAM_RESCAN_VALUES, clap_host_params,
19 },
20 host::Host,
21 id,
22 id::ClapId,
23 impl_flags_u32,
24 plugin::Plugin,
25};
26
27#[derive(Debug, Copy, Clone, PartialEq, Eq)]
28#[repr(u32)]
29pub enum InfoFlags {
30 Stepped = CLAP_PARAM_IS_STEPPED,
34 Periodic = CLAP_PARAM_IS_PERIODIC,
36 Hidden = CLAP_PARAM_IS_HIDDEN,
40 Readonly = CLAP_PARAM_IS_READONLY,
42 Bypass = CLAP_PARAM_IS_BYPASS,
47 Automatable = CLAP_PARAM_IS_AUTOMATABLE,
60 AutomatablePerNoteId = CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID,
62 AutomatablePerKey = CLAP_PARAM_IS_AUTOMATABLE_PER_KEY,
64 AutomatablePerChannel = CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL,
66 AutomatablePerPort = CLAP_PARAM_IS_AUTOMATABLE_PER_PORT,
68 Modulatable = CLAP_PARAM_IS_MODULATABLE,
70 ModulatablePerNoteId = CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID,
72 ModulatablePerKey = CLAP_PARAM_IS_MODULATABLE_PER_KEY,
74 ModulatablePerChannel = CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL,
76 ModulatablePerPort = CLAP_PARAM_IS_MODULATABLE_PER_PORT,
78 RequiresProcess = CLAP_PARAM_REQUIRES_PROCESS,
84 Enum = CLAP_PARAM_IS_ENUM,
88}
89
90impl_flags_u32!(InfoFlags);
91
92#[derive(Debug, Clone, PartialEq)]
94pub struct ParamInfo {
95 pub id: ClapId,
97
98 pub flags: u32,
99
100 pub name: String,
105
106 pub module: String,
109
110 pub min_value: f64,
112 pub max_value: f64,
114 pub default_value: f64,
116}
117
118impl ParamInfo {
119 pub unsafe fn try_from_unchecked(value: clap_param_info) -> Result<Self, Error> {
124 Ok(Self {
125 id: value.id.try_into().unwrap_or(ClapId::invalid_id()),
126 flags: value.flags,
127 name: unsafe { CStr::from_ptr(value.name.as_ptr()) }
129 .to_str()?
130 .to_owned(),
131 module: unsafe { CStr::from_ptr(value.module.as_ptr()) }
133 .to_str()?
134 .to_owned(),
135 min_value: value.min_value,
136 max_value: value.max_value,
137 default_value: value.default_value,
138 })
139 }
140}
141
142pub trait Params<P: Plugin> {
143 fn count(plugin: &P) -> u32;
144
145 fn get_info(plugin: &P, param_index: u32) -> Option<ParamInfo>;
146
147 fn get_value(plugin: &P, param_id: ClapId) -> Option<f64>;
148
149 fn value_to_text(
154 plugin: &P,
155 param_id: ClapId,
156 value: f64,
157 out_buf: &mut [u8],
158 ) -> Result<(), crate::Error>;
159
160 fn text_to_value(
164 plugin: &P,
165 param_id: ClapId,
166 param_value_text: &str,
167 ) -> Result<f64, crate::Error>;
168
169 fn flush_inactive(plugin: &P, in_events: &InputEvents, out_events: &OutputEvents);
178
179 fn flush(audio_thread: &P::AudioThread, in_events: &InputEvents, out_events: &OutputEvents);
180}
181
182impl<P: Plugin> Params<P> for () {
183 fn count(_: &P) -> u32 {
184 0
185 }
186
187 fn get_info(_: &P, _: u32) -> Option<ParamInfo> {
188 None
189 }
190
191 fn get_value(_: &P, _: ClapId) -> Option<f64> {
192 None
193 }
194
195 fn value_to_text(_: &P, _: ClapId, _: f64, _: &mut [u8]) -> Result<(), crate::Error> {
196 Ok(())
197 }
198
199 fn text_to_value(_: &P, _: ClapId, _: &str) -> Result<f64, crate::Error> {
200 Ok(0.0)
201 }
202
203 fn flush_inactive(_: &P, _: &InputEvents, _: &OutputEvents) {}
204
205 fn flush(_: &P::AudioThread, _: &InputEvents, _: &OutputEvents) {}
206}
207
208pub(crate) use ffi::PluginParams;
209
210use crate::ffi::clap_param_info;
211
212mod ffi {
213 use std::{
214 ffi::{CStr, c_char},
215 marker::PhantomData,
216 ptr::{copy_nonoverlapping, slice_from_raw_parts_mut},
217 };
218
219 use crate::{
220 events::{InputEvents, OutputEvents},
221 ext::params::{Error, Params},
222 ffi::{
223 clap_id, clap_input_events, clap_output_events, clap_param_info, clap_plugin,
224 clap_plugin_params,
225 },
226 plugin::{ClapPlugin, Plugin},
227 };
228
229 unsafe fn copy_utf8_to_cstr<const N: usize>(src: &str, dst: &mut [c_char; N]) {
236 let n = src.len().min(N - 1);
238 unsafe {
240 copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr() as *mut _, n);
241 }
242 dst[n] = b'\0' as _;
244 }
245
246 extern "C-unwind" fn count<E, P>(plugin: *const clap_plugin) -> u32
247 where
248 P: Plugin,
249 E: Params<P>,
250 {
251 if plugin.is_null() {
252 return 0;
253 }
254 let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
257
258 let plugin = unsafe { clap_plugin.plugin() };
263
264 E::count(plugin)
265 }
266
267 extern "C-unwind" fn get_info<E, P>(
268 plugin: *const clap_plugin,
269 param_index: u32,
270 param_info: *mut clap_param_info,
271 ) -> bool
272 where
273 P: Plugin,
274 E: Params<P>,
275 {
276 if plugin.is_null() {
277 return false;
278 }
279 let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
282
283 let plugin = unsafe { clap_plugin.plugin() };
288
289 let Some(param_info) = (unsafe { param_info.as_mut() }) else {
291 return false;
292 };
293 let Some(info) = E::get_info(plugin, param_index) else {
294 return false;
295 };
296
297 param_info.id = info.id.into();
298 param_info.flags = info.flags;
299
300 unsafe { copy_utf8_to_cstr(&info.name, &mut param_info.name) };
302 unsafe { copy_utf8_to_cstr(&info.module, &mut param_info.module) };
304
305 param_info.min_value = info.min_value;
306 param_info.max_value = info.max_value;
307 param_info.default_value = info.default_value;
308 true
309 }
310
311 extern "C-unwind" fn get_value<E, P>(
312 plugin: *const clap_plugin,
313 param_id: clap_id,
314 out_value: *mut f64,
315 ) -> bool
316 where
317 P: Plugin,
318 E: Params<P>,
319 {
320 if plugin.is_null() {
321 return false;
322 }
323 let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
326
327 let plugin = unsafe { clap_plugin.plugin() };
332
333 let Ok(param_id) = param_id.try_into() else {
334 return false;
335 };
336 let Some(value) = E::get_value(plugin, param_id) else {
337 return false;
338 };
339
340 unsafe { out_value.as_mut() }.map(|v| *v = value).is_some()
341 }
342
343 extern "C-unwind" fn value_to_text<E, P>(
344 plugin: *const clap_plugin,
345 param_id: clap_id,
346 value: f64,
347 out_buffer: *mut c_char,
348 out_buffer_capacity: u32,
349 ) -> bool
350 where
351 P: Plugin,
352 E: Params<P>,
353 {
354 if plugin.is_null() {
355 return false;
356 }
357 let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
360
361 let plugin = unsafe { clap_plugin.plugin() };
366
367 let out_buffer_capacity = if out_buffer_capacity > 0 {
368 debug_assert!(usize::try_from(out_buffer_capacity).is_ok());
369 out_buffer_capacity as usize
370 } else {
371 return true;
372 };
373 let buf = if !out_buffer.is_null() {
374 unsafe { &mut *slice_from_raw_parts_mut(out_buffer as *mut u8, out_buffer_capacity) }
375 } else {
376 return false;
377 };
378 buf.fill(b'\0');
381
382 let Ok(param_id) = param_id.try_into() else {
383 return false;
384 };
385 E::value_to_text(
386 plugin,
387 param_id,
388 value,
389 &mut buf[0..out_buffer_capacity - 1],
390 )
391 .is_ok()
392 }
393
394 extern "C-unwind" fn text_to_value<E, P>(
395 plugin: *const clap_plugin,
396 param_id: clap_id,
397 param_value_text: *const c_char,
398 out_value: *mut f64,
399 ) -> bool
400 where
401 P: Plugin,
402 E: Params<P>,
403 {
404 if plugin.is_null() {
405 return false;
406 }
407 let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
410
411 let plugin = unsafe { clap_plugin.plugin() };
416
417 let write_value = || -> Result<(), Error> {
418 let text = unsafe { param_value_text.as_ref() }
419 .map(|p| unsafe { CStr::from_ptr(p) }.to_str())
420 .ok_or(Error::Nullptr)??;
421 let value = E::text_to_value(plugin, param_id.try_into()?, text)
422 .map_err(|_| Error::ParseFloat(None))?;
423 unsafe { out_value.as_mut() }
424 .map(|v| *v = value)
425 .ok_or(Error::Nullptr)
426 };
427
428 write_value().is_ok()
429 }
430
431 extern "C-unwind" fn flush<E, P>(
432 plugin: *const clap_plugin,
433 r#in: *const clap_input_events,
434 out: *const clap_output_events,
435 ) where
436 P: Plugin,
437 E: Params<P>,
438 {
439 let Some(r#in) = (unsafe { r#in.as_ref() }) else {
440 return;
441 };
442 let in_events = if r#in.size.is_some() && r#in.get.is_some() {
443 unsafe { InputEvents::new_unchecked(r#in) }
444 } else {
445 return;
446 };
447
448 let Some(r#out) = (unsafe { out.as_ref() }) else {
449 return;
450 };
451 let out_events = if out.try_push.is_some() {
452 unsafe { OutputEvents::new_unchecked(out) }
453 } else {
454 return;
455 };
456
457 if plugin.is_null() {
458 return;
459 }
460 let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
463
464 if clap_plugin.is_active() {
465 let audio_thread = unsafe { clap_plugin.audio_thread() }.unwrap();
469 E::flush(audio_thread, &in_events, &out_events)
470 } else {
471 let plugin = unsafe { clap_plugin.plugin() };
476 E::flush_inactive(plugin, &in_events, &out_events);
477 }
478 }
479
480 pub struct PluginParams<P> {
481 #[allow(unused)]
482 clap_plugin_params: clap_plugin_params,
483 _marker: PhantomData<P>,
484 }
485
486 impl<P: Plugin> PluginParams<P> {
487 pub fn new<E: Params<P>>(_: E) -> Self {
488 Self {
489 clap_plugin_params: clap_plugin_params {
490 count: Some(count::<E, P>),
491 get_info: Some(get_info::<E, P>),
492 get_value: Some(get_value::<E, P>),
493 value_to_text: Some(value_to_text::<E, P>),
494 text_to_value: Some(text_to_value::<E, P>),
495 flush: Some(flush::<E, P>),
496 },
497 _marker: PhantomData,
498 }
499 }
500 }
501}
502
503#[derive(Debug, Copy, Clone, PartialEq, Eq)]
504#[repr(u32)]
505pub enum RescanFlags {
506 Values = CLAP_PARAM_RESCAN_VALUES,
511 Text = CLAP_PARAM_RESCAN_TEXT,
514 Info = CLAP_PARAM_RESCAN_INFO,
522 All = CLAP_PARAM_RESCAN_ALL,
542}
543
544impl_flags_u32!(RescanFlags);
545
546#[derive(Debug, Copy, Clone, PartialEq, Eq)]
547#[repr(u32)]
548pub enum ClearFlags {
549 All = CLAP_PARAM_CLEAR_ALL,
551 Automations = CLAP_PARAM_CLEAR_AUTOMATIONS,
553 Modulations = CLAP_PARAM_CLEAR_MODULATIONS,
555}
556
557impl_flags_u32!(ClearFlags);
558
559#[derive(Debug)]
560pub struct HostParams<'a> {
561 host: &'a Host,
562 clap_host_params: &'a clap_host_params,
563}
564
565impl<'a> HostParams<'a> {
566 pub(crate) const unsafe fn new_unchecked(
571 host: &'a Host,
572 clap_host_params: &'a clap_host_params,
573 ) -> Self {
574 Self {
575 host,
576 clap_host_params,
577 }
578 }
579
580 pub fn rescan(&self, flags: u32) {
582 unsafe { self.clap_host_params.rescan.unwrap()(self.host.clap_host(), flags) }
585 }
586
587 pub fn clear(&self, param_id: ClapId, flags: u32) {
589 unsafe {
592 self.clap_host_params.clear.unwrap()(self.host.clap_host(), param_id.into(), flags)
593 }
594 }
595
596 pub fn request_flush(&self) {
606 unsafe { self.clap_host_params.request_flush.unwrap()(self.host.clap_host()) }
609 }
610}
611
612#[derive(Debug, PartialEq)]
613pub enum Error {
614 ConvertToText(f64),
615 ParseFloat(Option<std::num::ParseFloatError>),
616 IdError(id::Error),
617 Nullptr,
618 Utf8Error(std::str::Utf8Error),
619}
620
621impl Display for Error {
622 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
623 match self {
624 Error::ConvertToText(val) => write!(f, "conversion from value to text: {val}"),
625 Error::ParseFloat(e) => write!(f, "float conversion: {:?}", e),
626 Error::IdError(e) => write!(f, "ClapId error: {e}"),
627 Error::Nullptr => write!(f, "null pointer"),
628 Error::Utf8Error(e) => write!(f, "UTF-8 encoding error: {e}"),
629 }
630 }
631}
632
633impl std::error::Error for Error {}
634
635impl From<std::str::Utf8Error> for Error {
636 fn from(value: std::str::Utf8Error) -> Self {
637 Self::Utf8Error(value)
638 }
639}
640
641impl From<id::Error> for Error {
642 fn from(value: id::Error) -> Self {
643 Self::IdError(value)
644 }
645}
646
647impl From<std::num::ParseFloatError> for Error {
648 fn from(value: std::num::ParseFloatError) -> Self {
649 Self::ParseFloat(Some(value))
650 }
651}
652
653impl From<Error> for crate::Error {
654 fn from(value: Error) -> Self {
655 ext::Error::Params(value).into()
656 }
657}