1use std::cell::Cell;
2use std::ffi::{CStr, CString, c_char, c_ulong, c_void};
3use std::path::Path;
4use std::ptr;
5use std::sync::{Mutex, OnceLock};
6use std::time::{Duration, Instant};
7
8#[derive(Clone, Copy, PartialEq)]
11pub enum ThreadType {
12 MainThread,
13 AudioThread,
14 AudioThreadPool,
15}
16
17thread_local! {
18 static CURRENT_THREAD: Cell<ThreadType> = const { Cell::new(ThreadType::MainThread) };
19}
20
21pub fn set_thread_type(ty: ThreadType) {
22 CURRENT_THREAD.with(|t| t.set(ty));
23}
24
25pub fn current_thread_type() -> ThreadType {
26 CURRENT_THREAD.with(|t| t.get())
27}
28
29pub struct HostTimer {
32 pub id: u32,
33 pub period_ms: u32,
34 pub deadline: Instant,
35}
36
37pub struct HostFd {
38 pub fd: i32,
39 pub flags: u32,
40}
41
42pub fn host_timers() -> &'static Mutex<Vec<HostTimer>> {
43 static TIMERS: OnceLock<Mutex<Vec<HostTimer>>> = OnceLock::new();
44 TIMERS.get_or_init(|| Mutex::new(Vec::new()))
45}
46
47pub fn host_fds() -> &'static Mutex<Vec<HostFd>> {
48 static FDS: OnceLock<Mutex<Vec<HostFd>>> = OnceLock::new();
49 FDS.get_or_init(|| Mutex::new(Vec::new()))
50}
51
52pub fn next_timer_id() -> u32 {
53 use std::sync::atomic::{AtomicU32, Ordering};
54 static NEXT: AtomicU32 = AtomicU32::new(1);
55 NEXT.fetch_add(1, Ordering::Relaxed)
56}
57
58#[repr(C)]
61#[derive(Clone, Copy)]
62pub struct ClapVersion {
63 pub major: u32,
64 pub minor: u32,
65 pub revision: u32,
66}
67
68pub const CLAP_VERSION: ClapVersion = ClapVersion {
69 major: 1,
70 minor: 2,
71 revision: 5,
72};
73
74#[repr(C)]
77pub struct ClapHost {
78 pub clap_version: ClapVersion,
79 pub host_data: *mut c_void,
80 pub name: *const c_char,
81 pub vendor: *const c_char,
82 pub url: *const c_char,
83 pub version: *const c_char,
84 pub get_extension:
85 Option<unsafe extern "C" fn(*const ClapHost, *const c_char) -> *const c_void>,
86 pub request_restart: Option<unsafe extern "C" fn(*const ClapHost)>,
87 pub request_process: Option<unsafe extern "C" fn(*const ClapHost)>,
88 pub request_callback: Option<unsafe extern "C" fn(*const ClapHost)>,
89}
90
91#[repr(C)]
92pub struct ClapPluginEntry {
93 pub clap_version: ClapVersion,
94 pub init: Option<unsafe extern "C" fn(*const c_char) -> bool>,
95 pub deinit: Option<unsafe extern "C" fn()>,
96 pub get_factory: Option<unsafe extern "C" fn(*const c_char) -> *const c_void>,
97}
98
99#[repr(C)]
100pub struct ClapPluginFactory {
101 pub get_plugin_count: Option<unsafe extern "C" fn(*const ClapPluginFactory) -> u32>,
102 pub get_plugin_descriptor:
103 Option<unsafe extern "C" fn(*const ClapPluginFactory, u32) -> *const ClapPluginDescriptor>,
104 pub create_plugin: Option<
105 unsafe extern "C" fn(
106 *const ClapPluginFactory,
107 *const ClapHost,
108 *const c_char,
109 ) -> *const ClapPlugin,
110 >,
111}
112
113#[repr(C)]
114pub struct ClapPluginDescriptor {
115 pub clap_version: ClapVersion,
116 pub id: *const c_char,
117 pub name: *const c_char,
118 pub vendor: *const c_char,
119 pub url: *const c_char,
120 pub manual_url: *const c_char,
121 pub support_url: *const c_char,
122 pub version: *const c_char,
123 pub description: *const c_char,
124 pub features: *const *const c_char,
125}
126
127#[repr(C)]
128pub struct ClapPlugin {
129 pub desc: *const ClapPluginDescriptor,
130 pub plugin_data: *mut c_void,
131 pub init: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
132 pub destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
133 pub activate: Option<unsafe extern "C" fn(*const ClapPlugin, f64, u32, u32) -> bool>,
134 pub deactivate: Option<unsafe extern "C" fn(*const ClapPlugin)>,
135 pub start_processing: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
136 pub stop_processing: Option<unsafe extern "C" fn(*const ClapPlugin)>,
137 pub reset: Option<unsafe extern "C" fn(*const ClapPlugin)>,
138 pub process: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapProcess) -> i32>,
139 pub get_extension:
140 Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char) -> *const c_void>,
141 pub on_main_thread: Option<unsafe extern "C" fn(*const ClapPlugin)>,
142}
143
144#[repr(C)]
147pub struct ClapProcess {
148 pub steady_time: i64,
149 pub frames_count: u32,
150 pub transport: *const c_void,
151 pub audio_inputs: *const ClapAudioBuffer,
152 pub audio_outputs: *mut ClapAudioBuffer,
153 pub audio_inputs_count: u32,
154 pub audio_outputs_count: u32,
155 pub in_events: *const ClapInputEvents,
156 pub out_events: *const ClapOutputEvents,
157}
158
159#[repr(C)]
160pub struct ClapAudioBuffer {
161 pub data32: *mut *mut f32,
162 pub data64: *mut *mut f64,
163 pub channel_count: u32,
164 pub latency: u32,
165 pub constant_mask: u64,
166}
167
168#[repr(C)]
169pub struct ClapInputEvents {
170 pub ctx: *const c_void,
171 pub size: Option<unsafe extern "C" fn(*const ClapInputEvents) -> u32>,
172 pub get: Option<unsafe extern "C" fn(*const ClapInputEvents, u32) -> *const ClapEventHeader>,
173}
174
175#[repr(C)]
176pub struct ClapOutputEvents {
177 pub ctx: *mut c_void,
178 pub try_push:
179 Option<unsafe extern "C" fn(*const ClapOutputEvents, *const ClapEventHeader) -> bool>,
180}
181
182#[repr(C)]
183#[derive(Clone, Copy)]
184pub struct ClapEventHeader {
185 pub size: u32,
186 pub time: u32,
187 pub space_id: u16,
188 pub type_: u16,
189 pub flags: u32,
190}
191
192#[repr(C)]
193#[derive(Clone, Copy)]
194pub struct ClapEventParamValue {
195 pub header: ClapEventHeader,
196 pub param_id: u32,
197 pub cookie: *mut c_void,
198 pub note_id: i32,
199 pub port_index: i16,
200 pub channel: i16,
201 pub key: i16,
202 pub value: f64,
203}
204
205#[repr(C)]
206#[derive(Clone, Copy)]
207pub struct ClapEventParamMod {
208 pub header: ClapEventHeader,
209 pub param_id: u32,
210 pub cookie: *mut c_void,
211 pub note_id: i32,
212 pub port_index: i16,
213 pub channel: i16,
214 pub key: i16,
215 pub amount: f64,
216}
217
218#[repr(C)]
219#[derive(Clone, Copy)]
220pub struct ClapEventParamGesture {
221 pub header: ClapEventHeader,
222 pub param_id: u32,
223}
224
225#[repr(C)]
226#[derive(Clone, Copy)]
227pub struct ClapEventMidi {
228 pub header: ClapEventHeader,
229 pub port_index: u16,
230 pub data: [u8; 3],
231}
232
233#[repr(C)]
234#[derive(Clone, Copy)]
235pub struct ClapEventNote {
236 pub header: ClapEventHeader,
237 pub note_id: i32,
238 pub port_index: i16,
239 pub channel: i16,
240 pub key: i16,
241 pub velocity: f64,
242}
243
244pub const CLAP_CORE_EVENT_SPACE_ID: u16 = 0;
245
246pub const CLAP_EVENT_PARAM_VALUE: u16 = 0;
247pub const CLAP_EVENT_PARAM_MOD: u16 = 1;
248pub const CLAP_EVENT_PARAM_GESTURE_BEGIN: u16 = 4;
249pub const CLAP_EVENT_PARAM_GESTURE_END: u16 = 5;
250pub const CLAP_EVENT_NOTE_ON: u16 = 2;
251pub const CLAP_EVENT_NOTE_OFF: u16 = 3;
252pub const CLAP_EVENT_NOTE_CHOKE: u16 = 6;
253pub const CLAP_EVENT_NOTE_END: u16 = 7;
254pub const CLAP_EVENT_MIDI: u16 = 8;
255pub const CLAP_EVENT_MIDI_SYSEX: u16 = 9;
256pub const CLAP_EVENT_MIDI2: u16 = 10;
257
258#[repr(C)]
261pub struct ClapHostParams {
262 pub resize: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
263 pub clear: Option<unsafe extern "C" fn(*const ClapHost, u32, u32)>,
264 pub request_flush: Option<unsafe extern "C" fn(*const ClapHost)>,
265}
266
267#[repr(C)]
268pub struct ClapHostAudioPorts {
269 pub is_rescan_flag_supported: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
270 pub rescan: Option<unsafe extern "C" fn(*const ClapHost, u32)>,
271}
272
273#[repr(C)]
274pub struct ClapHostLatency {
275 pub changed: Option<unsafe extern "C" fn(*const ClapHost)>,
276}
277
278#[repr(C)]
279pub struct ClapHostThreadPool {
280 pub request_exec: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
281}
282
283#[repr(C)]
284pub struct ClapHostGui {
285 pub resize_hints_changed: Option<unsafe extern "C" fn(*const ClapHost)>,
286 pub request_resize: Option<unsafe extern "C" fn(*const ClapHost, u32, u32) -> bool>,
287 pub request_show: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
288 pub request_hide: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
289 pub closed: Option<unsafe extern "C" fn(*const ClapHost, bool)>,
290}
291
292#[repr(C)]
293pub struct ClapHostThreadCheck {
294 pub is_main_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
295 pub is_audio_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
296}
297
298#[repr(C)]
299pub struct ClapHostLog {
300 pub log: Option<unsafe extern "C" fn(*const ClapHost, u32, *const c_char)>,
301}
302
303#[repr(C)]
304pub struct ClapHostTimerSupport {
305 pub register_timer: Option<unsafe extern "C" fn(*const ClapHost, u32, *mut u32) -> bool>,
306 pub unregister_timer: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
307}
308
309#[repr(C)]
310pub struct ClapHostPosixFdSupport {
311 pub register_fd: Option<unsafe extern "C" fn(*const ClapHost, i32, u32) -> bool>,
312 pub modify_fd: Option<unsafe extern "C" fn(*const ClapHost, i32, u32) -> bool>,
313 pub unregister_fd: Option<unsafe extern "C" fn(*const ClapHost, i32) -> bool>,
314}
315
316#[repr(C)]
317pub struct ClapPluginParams {
318 pub count: Option<unsafe extern "C" fn(*const ClapPlugin) -> u32>,
319 pub get_info: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut ClapParamInfo) -> bool>,
320 pub get_value: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut f64) -> bool>,
321 pub value_to_text:
322 Option<unsafe extern "C" fn(*const ClapPlugin, u32, f64, *mut c_char, u32) -> bool>,
323 pub text_to_value:
324 Option<unsafe extern "C" fn(*const ClapPlugin, u32, *const c_char, *mut f64) -> bool>,
325 pub flush: Option<
326 unsafe extern "C" fn(*const ClapPlugin, *const ClapInputEvents, *const ClapOutputEvents),
327 >,
328}
329
330#[repr(C)]
331pub struct ClapParamInfo {
332 pub id: u32,
333 pub flags: u32,
334 pub cookie: *mut c_void,
335 pub name: [c_char; 256],
336 pub module: [c_char; 1024],
337 pub min_value: f64,
338 pub max_value: f64,
339 pub default_value: f64,
340}
341
342#[repr(C)]
343pub struct ClapPluginAudioPorts {
344 pub count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
345 pub get:
346 Option<unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapAudioPortInfo) -> bool>,
347}
348
349#[repr(C)]
350pub struct ClapAudioPortInfo {
351 pub id: u32,
352 pub name: [c_char; 256],
353 pub flags: u32,
354 pub channel_count: u32,
355 pub port_type: *const c_char,
356 pub in_place_pair: u32,
357}
358
359#[repr(C)]
360pub struct ClapPluginGui {
361 pub is_api_supported:
362 Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
363 pub get_preferred_api:
364 Option<unsafe extern "C" fn(*const ClapPlugin, *mut *const c_char, *mut bool) -> bool>,
365 pub create: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
366 pub destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
367 pub set_scale: Option<unsafe extern "C" fn(*const ClapPlugin, f64) -> bool>,
368 pub get_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
369 pub can_resize: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
370 pub get_resize_hints:
371 Option<unsafe extern "C" fn(*const ClapPlugin, *mut ClapGuiResizeHints) -> bool>,
372 pub adjust_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
373 pub set_size: Option<unsafe extern "C" fn(*const ClapPlugin, u32, u32) -> bool>,
374 pub set_parent: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapWindow) -> bool>,
375 pub set_transient: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapWindow) -> bool>,
376 pub suggest_title: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char)>,
377 pub show: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
378 pub hide: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
379}
380
381#[repr(C)]
382pub struct ClapGuiResizeHints {
383 pub can_resize_horizontally: bool,
384 pub can_resize_vertically: bool,
385 pub preserve_aspect_ratio: bool,
386 pub aspect_ratio_width: u32,
387 pub aspect_ratio_height: u32,
388}
389
390#[repr(C)]
391pub struct ClapWindow {
392 pub api: *const c_char,
393 pub clap_window__: ClapWindowUnion,
394}
395
396#[repr(C)]
397pub union ClapWindowUnion {
398 pub x11: c_ulong,
399 pub cocoa: *mut c_void,
400 pub win32: *mut c_void,
401}
402
403pub const CLAP_EXT_PARAMS: &CStr = c"clap.params";
406pub const CLAP_EXT_AUDIO_PORTS: &CStr = c"clap.audio-ports";
407pub const CLAP_EXT_NOTE_PORTS: &CStr = c"clap.note-ports";
408pub const CLAP_EXT_GUI: &CStr = c"clap.gui";
409pub const CLAP_EXT_STATE: &CStr = c"clap.state";
410pub const CLAP_EXT_THREAD_POOL: &CStr = c"clap.thread-pool";
411pub const CLAP_EXT_LATENCY: &CStr = c"clap.latency";
412pub const CLAP_EXT_TAIL: &CStr = c"clap.tail";
413pub const CLAP_EXT_TIMER_SUPPORT: &CStr = c"clap.timer-support";
414pub const CLAP_EXT_THREAD_CHECK: &CStr = c"clap.thread-check";
415pub const CLAP_EXT_LOG: &CStr = c"clap.log";
416pub const CLAP_EXT_POSIX_FD_SUPPORT: &CStr = c"clap.posix-fd-support";
417pub const CLAP_PORT_MONO: &str = "clap.mono";
418pub const CLAP_PORT_STEREO: &str = "clap.stereo";
419
420#[repr(C)]
423pub struct ClapPluginThreadPool {
424 pub exec: Option<unsafe extern "C" fn(*const ClapPlugin, u32)>,
425}
426
427#[repr(C)]
428pub struct ClapPluginTimerSupport {
429 pub on_timer: Option<unsafe extern "C" fn(*const ClapPlugin, u32)>,
430}
431
432#[repr(C)]
433pub struct ClapPluginPosixFdSupport {
434 pub on_fd: Option<unsafe extern "C" fn(*const ClapPlugin, i32, u32)>,
435}
436
437#[repr(C)]
438pub struct ClapPluginNotePorts {
439 pub count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
440 pub get:
441 Option<unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapNotePortInfo) -> bool>,
442}
443
444#[repr(C)]
445pub struct ClapNotePortInfo {
446 pub id: u32,
447 pub name: [c_char; 256],
448 pub flags: u32,
449 pub supported_dialects: u16,
450 pub preferred_dialect: u16,
451}
452
453#[repr(C)]
454pub struct ClapHostState {
455 pub mark_dirty: Option<unsafe extern "C" fn(*const ClapHost)>,
456}
457
458#[repr(C)]
459pub struct HostData {
460 pub host: *mut ClapHost,
461 pub plugin: *const ClapPlugin,
462}
463
464use libloading::Library;
467
468pub struct PluginInstance {
469 _library: Library,
470 entry: *const ClapPluginEntry,
471 plugin: *const ClapPlugin,
472 host: Box<ClapHost>,
473 param_count: u32,
474 gui: Option<*const ClapPluginGui>,
475 gui_created: bool,
476}
477
478unsafe impl Send for PluginInstance {}
479
480impl PluginInstance {
481 pub fn new(plugin_path: &str, plugin_id: &str) -> Result<Self, String> {
484 let path = Path::new(plugin_path);
485 if !path.exists() {
486 return Err(format!("plugin path does not exist: {plugin_path}"));
487 }
488
489 let library =
490 unsafe { Library::new(path) }.map_err(|e| format!("failed to load library: {e}"))?;
491
492 let entry: libloading::Symbol<*const ClapPluginEntry> = unsafe {
493 library
494 .get(b"clap_entry\0")
495 .map_err(|e| format!("clap_entry not found: {e}"))?
496 };
497
498 let entry = unsafe { &**entry };
499
500 if let Some(init) = entry.init {
501 let plugin_path_c = CString::new(plugin_path).map_err(|e| e.to_string())?;
502 if !unsafe { init(plugin_path_c.as_ptr()) } {
503 return Err("clap_entry.init() failed".to_string());
504 }
505 }
506
507 let factory = if let Some(get_factory) = entry.get_factory {
508 let factory_id = CString::new("clap.plugin-factory").unwrap();
509 let factory_ptr = unsafe { get_factory(factory_id.as_ptr()) };
510 if factory_ptr.is_null() {
511 return Err("clap.plugin-factory not found".to_string());
512 }
513 unsafe { &*(factory_ptr as *const ClapPluginFactory) }
514 } else {
515 return Err("clap_entry.get_factory is null".to_string());
516 };
517
518 let descriptor = if plugin_id.is_empty() {
519 let count = factory
520 .get_plugin_count
521 .map(|f| unsafe { f(factory) })
522 .unwrap_or(0);
523 if count == 0 {
524 return Err("plugin factory is empty".to_string());
525 }
526 factory
527 .get_plugin_descriptor
528 .and_then(|f| {
529 let d = unsafe { f(factory, 0) };
530 if d.is_null() { None } else { Some(d) }
531 })
532 .ok_or("get_plugin_descriptor returned null")?
533 } else {
534 let count = factory
535 .get_plugin_count
536 .map(|f| unsafe { f(factory) })
537 .unwrap_or(0);
538 let mut found = None;
539 for i in 0..count {
540 if let Some(desc) = factory
541 .get_plugin_descriptor
542 .map(|f| unsafe { f(factory, i) })
543 {
544 if desc.is_null() {
545 continue;
546 }
547 let id = unsafe { CStr::from_ptr((*desc).id) };
548 if id.to_bytes() == plugin_id.as_bytes() {
549 found = Some(desc);
550 break;
551 }
552 }
553 }
554 found.ok_or(format!("plugin id '{}' not found", plugin_id))?
555 };
556
557 let actual_id = unsafe { CStr::from_ptr((*descriptor).id) }
558 .to_str()
559 .map_err(|e| e.to_string())?;
560 let plugin_id_c = CString::new(actual_id).map_err(|e| e.to_string())?;
561
562 let mut host = Box::new(ClapHost {
563 clap_version: CLAP_VERSION,
564 host_data: ptr::null_mut(),
565 name: c"maolan-plugin-host".as_ptr(),
566 vendor: c"Maolan".as_ptr(),
567 url: c"https://maolan.github.io".as_ptr(),
568 version: c"0.1.0".as_ptr(),
569 get_extension: Some(host_get_extension),
570 request_restart: Some(host_request_restart),
571 request_process: Some(host_request_process),
572 request_callback: Some(host_request_callback),
573 });
574
575 let plugin = factory.create_plugin.ok_or("create_plugin is null")?;
576 let plugin = unsafe { plugin(factory, &*host, plugin_id_c.as_ptr()) };
577 if plugin.is_null() {
578 if let Some(deinit) = entry.deinit {
579 unsafe { deinit() };
580 }
581 return Err("create_plugin returned null".to_string());
582 }
583
584 let host_data = Box::into_raw(Box::new(HostData {
586 host: &mut *host,
587 plugin,
588 }));
589 host.host_data = host_data.cast::<c_void>();
590
591 let init = unsafe { (*plugin).init }.ok_or("plugin.init is null")?;
592 if !unsafe { init(plugin) } {
593 unsafe {
594 if let Some(destroy) = (*plugin).destroy {
595 destroy(plugin);
596 }
597 }
598 if let Some(deinit) = entry.deinit {
599 unsafe { deinit() };
600 }
601 unsafe {
602 let _ = Box::from_raw(host_data);
603 }
604 return Err("plugin.init() returned false".to_string());
605 }
606
607 let param_count = unsafe {
608 let params_ext = (*plugin)
609 .get_extension
610 .map(|f| f(plugin, CLAP_EXT_PARAMS.as_ptr()));
611 if let Some(ext) = params_ext {
612 if !ext.is_null() {
613 let params = &*(ext as *const ClapPluginParams);
614 params.count.map(|f| f(plugin)).unwrap_or(0)
615 } else {
616 0
617 }
618 } else {
619 0
620 }
621 };
622
623 Ok(Self {
624 _library: library,
625 entry: entry as *const ClapPluginEntry,
626 plugin,
627 host,
628 param_count,
629 gui: None,
630 gui_created: false,
631 })
632 }
633
634 pub fn name(&self) -> String {
635 unsafe {
636 if let Some(desc) = (*self.plugin).desc.as_ref() {
637 CStr::from_ptr(desc.name).to_string_lossy().into_owned()
638 } else {
639 "unknown".to_string()
640 }
641 }
642 }
643
644 pub fn plugin_ptr(&self) -> *const ClapPlugin {
645 self.plugin
646 }
647
648 pub fn activate(
649 &self,
650 sample_rate: f64,
651 min_frames: u32,
652 max_frames: u32,
653 ) -> Result<(), String> {
654 let activate = unsafe { (*self.plugin).activate }.ok_or("activate is null")?;
655 if unsafe { activate(self.plugin, sample_rate, min_frames, max_frames) } {
656 Ok(())
657 } else {
658 Err("plugin.activate() returned false".to_string())
659 }
660 }
661
662 pub fn deactivate(&self) {
663 if let Some(deactivate) = unsafe { (*self.plugin).deactivate } {
664 unsafe { deactivate(self.plugin) };
665 }
666 }
667
668 pub fn start_processing(&self) -> Result<(), String> {
669 let start = unsafe { (*self.plugin).start_processing }.ok_or("start_processing is null")?;
670 if unsafe { start(self.plugin) } {
671 Ok(())
672 } else {
673 Err("plugin.start_processing() returned false".to_string())
674 }
675 }
676
677 pub fn stop_processing(&self) {
678 if let Some(stop) = unsafe { (*self.plugin).stop_processing } {
679 unsafe { stop(self.plugin) };
680 }
681 }
682
683 pub fn reset(&self) {
684 if let Some(reset) = unsafe { (*self.plugin).reset } {
685 unsafe { reset(self.plugin) };
686 }
687 }
688
689 pub fn process(&self, process: &ClapProcess) -> Result<(), String> {
690 let process_fn = unsafe { (*self.plugin).process }.ok_or("process is null")?;
691 let status = unsafe { process_fn(self.plugin, process) };
692 if status == 4 {
695 Err("plugin.process() returned CLAP_PROCESS_ERROR".to_string())
696 } else {
697 Ok(())
698 }
699 }
700
701 pub fn param_count(&self) -> u32 {
702 self.param_count
703 }
704
705 pub fn gui_is_supported(&mut self) -> bool {
706 let ext = unsafe {
707 (*self.plugin)
708 .get_extension
709 .map(|f| f(self.plugin, CLAP_EXT_GUI.as_ptr()))
710 };
711 if let Some(ptr) = ext
712 && !ptr.is_null()
713 {
714 self.gui = Some(ptr as *const ClapPluginGui);
715 return true;
716 }
717 false
718 }
719
720 pub fn gui_create(&mut self, api: &str, is_floating: bool) -> Result<(), String> {
721 let gui = self.gui.ok_or("GUI extension not available")?;
722 let create = unsafe { (*gui).create }.ok_or("gui.create is null")?;
723 let api_c = CString::new(api).map_err(|e| e.to_string())?;
724 if unsafe { create(self.plugin, api_c.as_ptr(), is_floating) } {
725 self.gui_created = true;
726 Ok(())
727 } else {
728 Err("plugin.gui.create() returned false".to_string())
729 }
730 }
731
732 pub fn gui_set_parent(&self, window_id: u64) -> Result<(), String> {
733 let gui = self.gui.ok_or("GUI extension not available")?;
734 let set_parent = unsafe { (*gui).set_parent }.ok_or("gui.set_parent is null")?;
735 let api = c"x11".as_ptr();
736 let window = ClapWindow {
737 api,
738 clap_window__: ClapWindowUnion {
739 x11: window_id as c_ulong,
740 },
741 };
742 if unsafe { set_parent(self.plugin, &window) } {
743 Ok(())
744 } else {
745 Err("plugin.gui.set_parent() returned false".to_string())
746 }
747 }
748
749 pub fn gui_show(&self) -> Result<(), String> {
750 let gui = self.gui.ok_or("GUI extension not available")?;
751 let show = unsafe { (*gui).show }.ok_or("gui.show is null")?;
752 if unsafe { show(self.plugin) } {
753 Ok(())
754 } else {
755 Err("plugin.gui.show() returned false".to_string())
756 }
757 }
758
759 pub fn gui_hide(&self) -> Result<(), String> {
760 let gui = self.gui.ok_or("GUI extension not available")?;
761 let hide = unsafe { (*gui).hide }.ok_or("gui.hide is null")?;
762 if unsafe { hide(self.plugin) } {
763 Ok(())
764 } else {
765 Err("plugin.gui.hide() returned false".to_string())
766 }
767 }
768
769 pub fn gui_created(&self) -> bool {
770 self.gui_created
771 }
772
773 pub fn gui_destroy(&mut self) {
774 if self.gui_created {
775 if let Some(gui) = self.gui
776 && let Some(destroy) = unsafe { (*gui).destroy }
777 {
778 unsafe { destroy(self.plugin) };
779 }
780 self.gui_created = false;
781 }
782 }
783}
784
785impl Drop for PluginInstance {
786 fn drop(&mut self) {
787 self.gui_destroy();
788 if let Some(destroy) = unsafe { (*self.plugin).destroy } {
789 unsafe { destroy(self.plugin) };
790 }
791 if !self.host.host_data.is_null() {
793 unsafe {
794 let _ = Box::from_raw(self.host.host_data as *mut HostData);
795 }
796 }
797 if let Some(deinit) = unsafe { (*self.entry).deinit } {
799 unsafe { deinit() };
800 }
801 }
802}
803
804unsafe extern "C" fn host_get_extension(
807 _host: *const ClapHost,
808 id: *const c_char,
809) -> *const c_void {
810 let id = unsafe { CStr::from_ptr(id).to_bytes() };
811 match id {
812 b"clap.params" => &CLAP_HOST_PARAMS as *const _ as *const c_void,
813 b"clap.audio-ports" => &CLAP_HOST_AUDIO_PORTS as *const _ as *const c_void,
814 b"clap.latency" => &CLAP_HOST_LATENCY as *const _ as *const c_void,
815 b"clap.thread-pool" => &CLAP_HOST_THREAD_POOL as *const _ as *const c_void,
816 b"clap.host.gui" => &CLAP_HOST_GUI as *const _ as *const c_void,
817 b"clap.thread-check" => &CLAP_HOST_THREAD_CHECK as *const _ as *const c_void,
818 b"clap.log" => &CLAP_HOST_LOG as *const _ as *const c_void,
819 b"clap.timer-support" => &CLAP_HOST_TIMER_SUPPORT as *const _ as *const c_void,
820 b"clap.posix-fd-support" => &CLAP_HOST_POSIX_FD_SUPPORT as *const _ as *const c_void,
821 b"clap.state" => &CLAP_HOST_STATE as *const _ as *const c_void,
822 _ => ptr::null(),
823 }
824}
825
826unsafe extern "C" fn host_request_restart(_host: *const ClapHost) {}
827unsafe extern "C" fn host_request_process(_host: *const ClapHost) {}
828unsafe extern "C" fn host_request_callback(_host: *const ClapHost) {}
829
830static CLAP_HOST_PARAMS: ClapHostParams = ClapHostParams {
831 resize: Some(host_params_resize),
832 clear: Some(host_params_clear),
833 request_flush: Some(host_params_request_flush),
834};
835
836static CLAP_HOST_AUDIO_PORTS: ClapHostAudioPorts = ClapHostAudioPorts {
837 is_rescan_flag_supported: Some(host_audio_ports_is_rescan_flag_supported),
838 rescan: Some(host_audio_ports_rescan),
839};
840
841static CLAP_HOST_LATENCY: ClapHostLatency = ClapHostLatency {
842 changed: Some(host_latency_changed),
843};
844
845static CLAP_HOST_THREAD_POOL: ClapHostThreadPool = ClapHostThreadPool {
846 request_exec: Some(host_thread_pool_request_exec),
847};
848
849static CLAP_HOST_GUI: ClapHostGui = ClapHostGui {
850 resize_hints_changed: Some(host_gui_resize_hints_changed),
851 request_resize: Some(host_gui_request_resize),
852 request_show: Some(host_gui_request_show),
853 request_hide: Some(host_gui_request_hide),
854 closed: Some(host_gui_closed),
855};
856
857static CLAP_HOST_THREAD_CHECK: ClapHostThreadCheck = ClapHostThreadCheck {
858 is_main_thread: Some(host_thread_check_is_main_thread),
859 is_audio_thread: Some(host_thread_check_is_audio_thread),
860};
861
862static CLAP_HOST_LOG: ClapHostLog = ClapHostLog {
863 log: Some(host_log_log),
864};
865
866static CLAP_HOST_TIMER_SUPPORT: ClapHostTimerSupport = ClapHostTimerSupport {
867 register_timer: Some(host_timer_support_register_timer),
868 unregister_timer: Some(host_timer_support_unregister_timer),
869};
870
871static CLAP_HOST_POSIX_FD_SUPPORT: ClapHostPosixFdSupport = ClapHostPosixFdSupport {
872 register_fd: Some(host_posix_fd_support_register_fd),
873 modify_fd: Some(host_posix_fd_support_modify_fd),
874 unregister_fd: Some(host_posix_fd_support_unregister_fd),
875};
876
877static CLAP_HOST_STATE: ClapHostState = ClapHostState {
878 mark_dirty: Some(host_state_mark_dirty),
879};
880
881unsafe extern "C" fn host_params_resize(_host: *const ClapHost, _capacity: u32) -> bool {
882 true
883}
884unsafe extern "C" fn host_params_clear(_host: *const ClapHost, _begin: u32, _end: u32) {}
885unsafe extern "C" fn host_params_request_flush(_host: *const ClapHost) {
886 crate::host::request_params_flush();
887}
888unsafe extern "C" fn host_audio_ports_is_rescan_flag_supported(
889 _host: *const ClapHost,
890 _flag: u32,
891) -> bool {
892 false
893}
894unsafe extern "C" fn host_audio_ports_rescan(_host: *const ClapHost, _flag: u32) {
895 crate::host::request_audio_ports_rescan();
896}
897unsafe extern "C" fn host_latency_changed(_host: *const ClapHost) {}
898unsafe extern "C" fn host_thread_pool_request_exec(host: *const ClapHost, num_tasks: u32) -> bool {
899 if host.is_null() {
900 return false;
901 }
902 let host_data = unsafe { &*((*host).host_data as *const HostData) };
903 let plugin = host_data.plugin;
904 if plugin.is_null() {
905 return false;
906 }
907 let ext = unsafe {
909 (*plugin)
910 .get_extension
911 .map(|f| f(plugin, CLAP_EXT_THREAD_POOL.as_ptr()))
912 };
913 let ext = match ext {
914 Some(p) if !p.is_null() => p,
915 _ => return false,
916 };
917 let tp = unsafe { &*(ext as *const ClapPluginThreadPool) };
918 let Some(exec) = tp.exec else {
919 return false;
920 };
921 for task_index in 0..num_tasks {
923 unsafe { exec(plugin, task_index) };
924 }
925 true
926}
927
928unsafe extern "C" fn host_gui_resize_hints_changed(_host: *const ClapHost) {}
929unsafe extern "C" fn host_gui_request_resize(
930 _host: *const ClapHost,
931 _width: u32,
932 _height: u32,
933) -> bool {
934 false
935}
936unsafe extern "C" fn host_gui_request_show(_host: *const ClapHost) -> bool {
937 false
938}
939unsafe extern "C" fn host_gui_request_hide(_host: *const ClapHost) -> bool {
940 false
941}
942unsafe extern "C" fn host_gui_closed(_host: *const ClapHost, _was_destroyed: bool) {}
943
944unsafe extern "C" fn host_state_mark_dirty(_host: *const ClapHost) {
945 tracing::info!("Plugin called clap_host_state.mark_dirty()");
946}
947
948unsafe extern "C" fn host_thread_check_is_main_thread(_host: *const ClapHost) -> bool {
949 current_thread_type() == ThreadType::MainThread
950}
951
952unsafe extern "C" fn host_thread_check_is_audio_thread(_host: *const ClapHost) -> bool {
953 matches!(
954 current_thread_type(),
955 ThreadType::AudioThread | ThreadType::AudioThreadPool
956 )
957}
958
959unsafe extern "C" fn host_log_log(_host: *const ClapHost, severity: u32, msg: *const c_char) {
960 if msg.is_null() {
961 return;
962 }
963 let msg = unsafe { CStr::from_ptr(msg) }.to_string_lossy();
964 match severity {
965 0 => tracing::debug!(target: "clap_plugin", "{msg}"),
966 1 => tracing::info!(target: "clap_plugin", "{msg}"),
967 2 => tracing::warn!(target: "clap_plugin", "{msg}"),
968 3..=5 => tracing::error!(target: "clap_plugin", "{msg}"),
969 _ => tracing::info!(target: "clap_plugin", "{msg}"),
970 }
971}
972
973unsafe extern "C" fn host_timer_support_register_timer(
974 _host: *const ClapHost,
975 period_ms: u32,
976 timer_id: *mut u32,
977) -> bool {
978 if timer_id.is_null() {
979 return false;
980 }
981 let id = next_timer_id();
982 unsafe { *timer_id = id };
983 let mut timers = host_timers().lock().unwrap();
984 timers.push(HostTimer {
985 id,
986 period_ms,
987 deadline: Instant::now() + Duration::from_millis(period_ms as u64),
988 });
989 true
990}
991
992unsafe extern "C" fn host_timer_support_unregister_timer(
993 _host: *const ClapHost,
994 timer_id: u32,
995) -> bool {
996 let mut timers = host_timers().lock().unwrap();
997 if let Some(pos) = timers.iter().position(|t| t.id == timer_id) {
998 timers.swap_remove(pos);
999 true
1000 } else {
1001 false
1002 }
1003}
1004
1005unsafe extern "C" fn host_posix_fd_support_register_fd(
1006 _host: *const ClapHost,
1007 fd: i32,
1008 flags: u32,
1009) -> bool {
1010 let mut fds = host_fds().lock().unwrap();
1011 if fds.iter().any(|f| f.fd == fd) {
1012 return false;
1013 }
1014 fds.push(HostFd { fd, flags });
1015 true
1016}
1017
1018unsafe extern "C" fn host_posix_fd_support_modify_fd(
1019 _host: *const ClapHost,
1020 fd: i32,
1021 flags: u32,
1022) -> bool {
1023 let mut fds = host_fds().lock().unwrap();
1024 if let Some(f) = fds.iter_mut().find(|f| f.fd == fd) {
1025 f.flags = flags;
1026 true
1027 } else {
1028 false
1029 }
1030}
1031
1032unsafe extern "C" fn host_posix_fd_support_unregister_fd(_host: *const ClapHost, fd: i32) -> bool {
1033 let mut fds = host_fds().lock().unwrap();
1034 if let Some(pos) = fds.iter().position(|f| f.fd == fd) {
1035 fds.swap_remove(pos);
1036 true
1037 } else {
1038 false
1039 }
1040}
1041
1042unsafe extern "C" fn empty_input_events_size(_: *const ClapInputEvents) -> u32 {
1045 0
1046}
1047
1048unsafe extern "C" fn empty_input_events_get(
1049 _: *const ClapInputEvents,
1050 _index: u32,
1051) -> *const ClapEventHeader {
1052 ptr::null()
1053}
1054
1055unsafe extern "C" fn empty_output_events_try_push(
1056 _: *const ClapOutputEvents,
1057 _: *const ClapEventHeader,
1058) -> bool {
1059 false
1060}
1061
1062pub fn empty_input_events() -> ClapInputEvents {
1063 ClapInputEvents {
1064 ctx: ptr::null(),
1065 size: Some(empty_input_events_size),
1066 get: Some(empty_input_events_get),
1067 }
1068}
1069
1070pub fn empty_output_events() -> ClapOutputEvents {
1071 ClapOutputEvents {
1072 ctx: ptr::null_mut(),
1073 try_push: Some(empty_output_events_try_push),
1074 }
1075}
1076
1077pub struct EventBuffer {
1081 events: Vec<Vec<u8>>,
1082}
1083
1084impl Default for EventBuffer {
1085 fn default() -> Self {
1086 Self::new()
1087 }
1088}
1089
1090impl EventBuffer {
1091 pub fn new() -> Self {
1092 Self { events: Vec::new() }
1093 }
1094
1095 pub fn push_param_value(&mut self, param_id: u32, value: f64, sample_offset: u32) {
1096 let ev = ClapEventParamValue {
1097 header: ClapEventHeader {
1098 size: std::mem::size_of::<ClapEventParamValue>() as u32,
1099 time: sample_offset,
1100 space_id: CLAP_CORE_EVENT_SPACE_ID,
1101 type_: CLAP_EVENT_PARAM_VALUE,
1102 flags: 0,
1103 },
1104 param_id,
1105 cookie: ptr::null_mut(),
1106 note_id: -1,
1107 port_index: -1,
1108 channel: -1,
1109 key: -1,
1110 value,
1111 };
1112 self.events.push(unsafe {
1113 std::slice::from_raw_parts(
1114 &ev as *const _ as *const u8,
1115 std::mem::size_of::<ClapEventParamValue>(),
1116 )
1117 .to_vec()
1118 });
1119 }
1120
1121 pub fn push_param_mod(&mut self, param_id: u32, amount: f64, sample_offset: u32) {
1122 let ev = ClapEventParamMod {
1123 header: ClapEventHeader {
1124 size: std::mem::size_of::<ClapEventParamMod>() as u32,
1125 time: sample_offset,
1126 space_id: CLAP_CORE_EVENT_SPACE_ID,
1127 type_: CLAP_EVENT_PARAM_MOD,
1128 flags: 0,
1129 },
1130 param_id,
1131 cookie: ptr::null_mut(),
1132 note_id: -1,
1133 port_index: -1,
1134 channel: -1,
1135 key: -1,
1136 amount,
1137 };
1138 self.events.push(unsafe {
1139 std::slice::from_raw_parts(
1140 &ev as *const _ as *const u8,
1141 std::mem::size_of::<ClapEventParamMod>(),
1142 )
1143 .to_vec()
1144 });
1145 }
1146
1147 pub fn push_param_gesture_begin(&mut self, param_id: u32, sample_offset: u32) {
1148 let ev = ClapEventParamGesture {
1149 header: ClapEventHeader {
1150 size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1151 time: sample_offset,
1152 space_id: CLAP_CORE_EVENT_SPACE_ID,
1153 type_: CLAP_EVENT_PARAM_GESTURE_BEGIN,
1154 flags: 0,
1155 },
1156 param_id,
1157 };
1158 self.events.push(unsafe {
1159 std::slice::from_raw_parts(
1160 &ev as *const _ as *const u8,
1161 std::mem::size_of::<ClapEventParamGesture>(),
1162 )
1163 .to_vec()
1164 });
1165 }
1166
1167 pub fn push_param_gesture_end(&mut self, param_id: u32, sample_offset: u32) {
1168 let ev = ClapEventParamGesture {
1169 header: ClapEventHeader {
1170 size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1171 time: sample_offset,
1172 space_id: CLAP_CORE_EVENT_SPACE_ID,
1173 type_: CLAP_EVENT_PARAM_GESTURE_END,
1174 flags: 0,
1175 },
1176 param_id,
1177 };
1178 self.events.push(unsafe {
1179 std::slice::from_raw_parts(
1180 &ev as *const _ as *const u8,
1181 std::mem::size_of::<ClapEventParamGesture>(),
1182 )
1183 .to_vec()
1184 });
1185 }
1186
1187 pub fn push_note_on(
1188 &mut self,
1189 note_id: i32,
1190 port_index: i16,
1191 channel: i16,
1192 key: i16,
1193 velocity: f64,
1194 sample_offset: u32,
1195 ) {
1196 let ev = ClapEventNote {
1197 header: ClapEventHeader {
1198 size: std::mem::size_of::<ClapEventNote>() as u32,
1199 time: sample_offset,
1200 space_id: CLAP_CORE_EVENT_SPACE_ID,
1201 type_: CLAP_EVENT_NOTE_ON,
1202 flags: 0,
1203 },
1204 note_id,
1205 port_index,
1206 channel,
1207 key,
1208 velocity,
1209 };
1210 self.events.push(unsafe {
1211 std::slice::from_raw_parts(
1212 &ev as *const _ as *const u8,
1213 std::mem::size_of::<ClapEventNote>(),
1214 )
1215 .to_vec()
1216 });
1217 }
1218
1219 pub fn push_note_off(
1220 &mut self,
1221 note_id: i32,
1222 port_index: i16,
1223 channel: i16,
1224 key: i16,
1225 velocity: f64,
1226 sample_offset: u32,
1227 ) {
1228 let ev = ClapEventNote {
1229 header: ClapEventHeader {
1230 size: std::mem::size_of::<ClapEventNote>() as u32,
1231 time: sample_offset,
1232 space_id: CLAP_CORE_EVENT_SPACE_ID,
1233 type_: CLAP_EVENT_NOTE_OFF,
1234 flags: 0,
1235 },
1236 note_id,
1237 port_index,
1238 channel,
1239 key,
1240 velocity,
1241 };
1242 self.events.push(unsafe {
1243 std::slice::from_raw_parts(
1244 &ev as *const _ as *const u8,
1245 std::mem::size_of::<ClapEventNote>(),
1246 )
1247 .to_vec()
1248 });
1249 }
1250
1251 pub fn push_midi(&mut self, data: [u8; 3], port_index: u16, sample_offset: u32) {
1252 let ev = ClapEventMidi {
1253 header: ClapEventHeader {
1254 size: std::mem::size_of::<ClapEventMidi>() as u32,
1255 time: sample_offset,
1256 space_id: CLAP_CORE_EVENT_SPACE_ID,
1257 type_: CLAP_EVENT_MIDI,
1258 flags: 0,
1259 },
1260 port_index,
1261 data,
1262 };
1263 self.events.push(unsafe {
1264 std::slice::from_raw_parts(
1265 &ev as *const _ as *const u8,
1266 std::mem::size_of::<ClapEventMidi>(),
1267 )
1268 .to_vec()
1269 });
1270 }
1271
1272 pub fn len(&self) -> usize {
1273 self.events.len()
1274 }
1275
1276 pub fn is_empty(&self) -> bool {
1277 self.events.is_empty()
1278 }
1279
1280 pub fn as_input_events(&self) -> ClapInputEvents {
1281 ClapInputEvents {
1282 ctx: self as *const _ as *const c_void,
1283 size: Some(event_buffer_size),
1284 get: Some(event_buffer_get),
1285 }
1286 }
1287}
1288
1289unsafe extern "C" fn event_buffer_size(events: *const ClapInputEvents) -> u32 {
1290 let buf = unsafe { &*((*events).ctx as *const EventBuffer) };
1291 buf.events.len() as u32
1292}
1293
1294unsafe extern "C" fn event_buffer_get(
1295 events: *const ClapInputEvents,
1296 index: u32,
1297) -> *const ClapEventHeader {
1298 let buf = unsafe { &*((*events).ctx as *const EventBuffer) };
1299 buf.events
1300 .get(index as usize)
1301 .map(|bytes| bytes.as_ptr() as *const ClapEventHeader)
1302 .unwrap_or(ptr::null())
1303}
1304
1305pub struct EventCapture {
1308 events: Vec<Vec<u8>>,
1309}
1310
1311impl Default for EventCapture {
1312 fn default() -> Self {
1313 Self::new()
1314 }
1315}
1316
1317impl EventCapture {
1318 pub fn new() -> Self {
1319 Self { events: Vec::new() }
1320 }
1321
1322 pub fn drain(&mut self) -> Vec<Vec<u8>> {
1323 std::mem::take(&mut self.events)
1324 }
1325
1326 pub fn as_output_events(&self) -> ClapOutputEvents {
1327 ClapOutputEvents {
1328 ctx: self as *const _ as *mut c_void,
1329 try_push: Some(event_capture_try_push),
1330 }
1331 }
1332}
1333
1334unsafe extern "C" fn event_capture_try_push(
1335 events: *const ClapOutputEvents,
1336 header: *const ClapEventHeader,
1337) -> bool {
1338 if header.is_null() {
1339 return false;
1340 }
1341 let header = unsafe { &*header };
1342 let size = header.size as usize;
1343 if size == 0 || size > 128 {
1344 return false;
1345 }
1346 let capture = unsafe { &mut *((*events).ctx as *mut EventCapture) };
1347 let bytes =
1348 unsafe { std::slice::from_raw_parts(header as *const _ as *const u8, size).to_vec() };
1349 capture.events.push(bytes);
1350 true
1351}