Skip to main content

maolan_plugin_host/
clap.rs

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}