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