Skip to main content

maolan_engine/plugins/vst3/
interfaces.rs

1#![allow(clippy::unnecessary_cast)]
2
3use std::ffi::c_void;
4use std::path::Path;
5use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
6use std::sync::{Arc, Mutex, OnceLock};
7use std::time::{Duration, Instant};
8use vst3::Steinberg::Vst::ProcessModes_::kRealtime;
9use vst3::Steinberg::Vst::SymbolicSampleSizes_::kSample32;
10use vst3::Steinberg::Vst::*;
11use vst3::Steinberg::*;
12use vst3::{Class, ComPtr, ComWrapper, Interface};
13
14/// Wrap a potentially-panicking plugin call in `catch_unwind`.
15/// Returns the closure's result on success, or an error string if it panics.
16pub fn protected_call<T, F>(op: F) -> Result<T, String>
17where
18    F: FnOnce() -> T + std::panic::UnwindSafe,
19{
20    match std::panic::catch_unwind(op) {
21        Ok(result) => Ok(result),
22        Err(_) => Err("Plugin call panicked".to_string()),
23    }
24}
25
26static HOST_RUN_LOOP_STATE: OnceLock<Mutex<HostRunLoopState>> = OnceLock::new();
27
28struct HostRunLoopState {
29    event_handlers: Vec<RunLoopEventHandler>,
30    timer_handlers: Vec<RunLoopTimerHandler>,
31}
32
33struct RunLoopEventHandler {
34    handler: usize,
35    fd: i32,
36}
37
38struct RunLoopTimerHandler {
39    handler: usize,
40    interval: Duration,
41    next_tick: Instant,
42}
43
44fn run_loop_state() -> &'static Mutex<HostRunLoopState> {
45    HOST_RUN_LOOP_STATE.get_or_init(|| {
46        Mutex::new(HostRunLoopState {
47            event_handlers: Vec::new(),
48            timer_handlers: Vec::new(),
49        })
50    })
51}
52
53pub fn pump_host_run_loop() {
54    let (event_calls, timer_calls): (Vec<(usize, i32)>, Vec<usize>) = {
55        let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
56        let now = Instant::now();
57        let event_calls = state
58            .event_handlers
59            .iter()
60            .map(|h| (h.handler, h.fd))
61            .collect::<Vec<_>>();
62        let mut timer_calls = Vec::new();
63        for timer in &mut state.timer_handlers {
64            if now >= timer.next_tick {
65                timer_calls.push(timer.handler);
66                timer.next_tick = now + timer.interval;
67            }
68        }
69        (event_calls, timer_calls)
70    };
71
72    for (handler, fd) in event_calls {
73        let handler_ptr = handler as *mut Linux::IEventHandler;
74        if handler_ptr.is_null() {
75            continue;
76        }
77        unsafe {
78            ((*(*handler_ptr).vtbl).onFDIsSet)(handler_ptr, fd);
79        }
80    }
81    for handler in timer_calls {
82        let handler_ptr = handler as *mut Linux::ITimerHandler;
83        if handler_ptr.is_null() {
84            continue;
85        }
86        unsafe {
87            ((*(*handler_ptr).vtbl).onTimer)(handler_ptr);
88        }
89    }
90}
91
92/// Safe wrapper around VST3 plugin factory
93pub struct PluginFactory {
94    factory: ComPtr<IPluginFactory>,
95    module: libloading::Library,
96    module_inited: bool,
97}
98
99impl std::fmt::Debug for PluginFactory {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        f.debug_struct("PluginFactory")
102            .field("factory", &"<COM ptr>")
103            .field("module", &"<library>")
104            .finish()
105    }
106}
107
108impl PluginFactory {
109    /// Load a VST3 plugin bundle and create a factory
110    pub fn from_module(bundle_path: &Path) -> Result<Self, String> {
111        let module_path = get_module_path(bundle_path)?;
112
113        let library = unsafe {
114            libloading::Library::new(&module_path)
115                .map_err(|e| format!("Failed to load VST3 module {:?}: {}", module_path, e))?
116        };
117
118        let module_inited = unsafe {
119            match library.get::<unsafe extern "system" fn() -> bool>(b"InitDll") {
120                Ok(init_dll) => init_dll(),
121                Err(_) => false,
122            }
123        };
124
125        let get_factory: libloading::Symbol<unsafe extern "system" fn() -> *mut c_void> = unsafe {
126            library
127                .get(b"GetPluginFactory")
128                .map_err(|e| format!("Failed to find GetPluginFactory: {}", e))?
129        };
130
131        let factory_ptr = unsafe { get_factory() };
132        if factory_ptr.is_null() {
133            return Err("GetPluginFactory returned null".to_string());
134        }
135
136        let factory = unsafe { ComPtr::from_raw(factory_ptr as *mut IPluginFactory) }
137            .ok_or("Failed to create ComPtr for IPluginFactory")?;
138
139        Ok(Self {
140            factory,
141            module: library,
142            module_inited,
143        })
144    }
145
146    /// Get information about a plugin class using the trait
147    pub fn get_class_info(&self, index: i32) -> Option<ClassInfo> {
148        use vst3::Steinberg::IPluginFactoryTrait;
149
150        let mut info = PClassInfo {
151            cid: [0; 16],
152            cardinality: 0,
153            category: [0; 32],
154            name: [0; 64],
155        };
156
157        let result = unsafe { self.factory.getClassInfo(index, &mut info) };
158
159        if result == kResultOk {
160            Some(ClassInfo {
161                name: extract_cstring(&info.name),
162                category: extract_cstring(&info.category),
163                cid: info.cid,
164            })
165        } else {
166            None
167        }
168    }
169
170    /// Count the number of classes using the trait
171    pub fn count_classes(&self) -> i32 {
172        use vst3::Steinberg::IPluginFactoryTrait;
173        unsafe { self.factory.countClasses() }
174    }
175
176    /// Create an instance of a plugin using the trait
177    pub fn create_instance(&self, class_id: &[i8; 16]) -> Result<PluginInstance, String> {
178        use vst3::Steinberg::IPluginFactoryTrait;
179
180        let mut instance_ptr: *mut c_void = std::ptr::null_mut();
181
182        let result = unsafe {
183            self.factory.createInstance(
184                class_id.as_ptr(),
185                IComponent::IID.as_ptr() as *const i8,
186                &mut instance_ptr,
187            )
188        };
189
190        if result != kResultOk || instance_ptr.is_null() {
191            return Err(format!(
192                "Failed to create plugin instance (result: {})",
193                result
194            ));
195        }
196
197        let component = unsafe { ComPtr::from_raw(instance_ptr as *mut IComponent) }
198            .ok_or("Failed to create ComPtr for IComponent")?;
199
200        Ok(PluginInstance::new(component))
201    }
202
203    /// Get factory information
204    pub fn get_factory_info(&self) -> Option<FactoryInfo> {
205        use vst3::Steinberg::IPluginFactoryTrait;
206
207        let mut info = PFactoryInfo {
208            vendor: [0; 64],
209            url: [0; 256],
210            email: [0; 128],
211            flags: 0,
212        };
213
214        let result = unsafe { self.factory.getFactoryInfo(&mut info) };
215
216        if result == kResultOk {
217            Some(FactoryInfo {
218                vendor: extract_cstring(&info.vendor),
219                url: extract_cstring(&info.url),
220                email: extract_cstring(&info.email),
221                flags: info.flags,
222            })
223        } else {
224            None
225        }
226    }
227
228    pub fn create_edit_controller(
229        &self,
230        class_id: &[i8; 16],
231    ) -> Result<ComPtr<IEditController>, String> {
232        use vst3::Steinberg::IPluginFactoryTrait;
233
234        let mut instance_ptr: *mut c_void = std::ptr::null_mut();
235
236        let result = unsafe {
237            self.factory.createInstance(
238                class_id.as_ptr(),
239                IEditController::IID.as_ptr() as *const i8,
240                &mut instance_ptr,
241            )
242        };
243
244        if result != kResultOk || instance_ptr.is_null() {
245            return Err(format!(
246                "Failed to create edit controller instance (result: {})",
247                result
248            ));
249        }
250
251        unsafe { ComPtr::from_raw(instance_ptr as *mut IEditController) }
252            .ok_or("Failed to create ComPtr for IEditController".to_string())
253    }
254}
255
256impl Drop for PluginFactory {
257    fn drop(&mut self) {
258        if !self.module_inited {
259            return;
260        }
261
262        unsafe {
263            if let Ok(exit_dll) = self
264                .module
265                .get::<unsafe extern "system" fn() -> bool>(b"ExitDll")
266            {
267                let _ = exit_dll();
268            }
269        }
270    }
271}
272
273/// Information about a plugin class
274pub struct ClassInfo {
275    pub name: String,
276    pub category: String,
277    pub cid: [i8; 16],
278}
279
280/// Information about the plugin factory
281#[derive(Debug, Clone)]
282pub struct FactoryInfo {
283    pub vendor: String,
284    pub url: String,
285    pub email: String,
286    pub flags: i32,
287}
288
289/// GUI capability information for a VST3 plugin
290#[derive(Debug, Clone)]
291pub struct Vst3GuiInfo {
292    pub has_gui: bool,
293    pub size: Option<(i32, i32)>,
294}
295
296/// Host-side IPlugFrame implementation that tracks resize requests from the plugin
297pub struct HostPlugFrame {
298    pub resize_requested: AtomicBool,
299    pub requested_size: Mutex<Option<(i32, i32)>>,
300}
301
302impl Default for HostPlugFrame {
303    fn default() -> Self {
304        Self {
305            resize_requested: AtomicBool::new(false),
306            requested_size: Mutex::new(None),
307        }
308    }
309}
310
311impl HostPlugFrame {
312    pub fn new() -> Self {
313        Self::default()
314    }
315}
316
317impl Class for HostPlugFrame {
318    type Interfaces = (IPlugFrame,);
319}
320
321impl IPlugFrameTrait for HostPlugFrame {
322    unsafe fn resizeView(&self, _view: *mut IPlugView, new_size: *mut ViewRect) -> tresult {
323        if !new_size.is_null() {
324            let rect = unsafe { *new_size };
325            let width = rect.right - rect.left;
326            let height = rect.bottom - rect.top;
327            if let Ok(mut size) = self.requested_size.lock() {
328                *size = Some((width, height));
329            }
330            self.resize_requested.store(true, Ordering::Relaxed);
331        }
332        kResultOk
333    }
334}
335
336/// Component handler that tracks parameter changes from the plugin GUI
337pub struct ComponentHandler {
338    pub parameter_changes: Arc<Mutex<Vec<(u32, f64)>>>,
339}
340
341impl ComponentHandler {
342    pub fn new(parameter_changes: Arc<Mutex<Vec<(u32, f64)>>>) -> Self {
343        Self { parameter_changes }
344    }
345}
346
347impl Class for ComponentHandler {
348    type Interfaces = (IComponentHandler,);
349}
350
351impl IComponentHandlerTrait for ComponentHandler {
352    unsafe fn beginEdit(&self, _id: ParamID) -> tresult {
353        kResultOk
354    }
355
356    unsafe fn performEdit(&self, id: ParamID, value_normalized: ParamValue) -> tresult {
357        if let Ok(mut changes) = self.parameter_changes.lock() {
358            changes.push((id, value_normalized));
359        }
360        kResultOk
361    }
362
363    unsafe fn endEdit(&self, _id: ParamID) -> tresult {
364        kResultOk
365    }
366
367    unsafe fn restartComponent(&self, _flags: i32) -> tresult {
368        kResultOk
369    }
370}
371
372/// Safe wrapper around a VST3 plugin instance
373pub struct PluginInstance {
374    pub component: ComPtr<IComponent>,
375    pub audio_processor: Option<ComPtr<IAudioProcessor>>,
376    pub edit_controller: Option<ComPtr<IEditController>>,
377    host_context: Box<HostApplicationContext>,
378    component_handler: Option<ComWrapper<ComponentHandler>>,
379    pub parameter_changes: Arc<Mutex<Vec<(u32, f64)>>>,
380}
381
382impl std::fmt::Debug for PluginInstance {
383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384        f.debug_struct("PluginInstance")
385            .field("component", &"<COM ptr>")
386            .field("audio_processor", &self.audio_processor.is_some())
387            .field("edit_controller", &self.edit_controller.is_some())
388            .finish()
389    }
390}
391
392impl PluginInstance {
393    fn new(component: ComPtr<IComponent>) -> Self {
394        Self {
395            component,
396            audio_processor: None,
397            edit_controller: None,
398            host_context: Box::new(HostApplicationContext::new()),
399            component_handler: None,
400            parameter_changes: Arc::new(Mutex::new(Vec::new())),
401        }
402    }
403
404    pub fn audio_bus_counts(&self) -> (usize, usize) {
405        use vst3::Steinberg::Vst::{BusDirections_, IComponentTrait, MediaTypes_};
406
407        let input_count = unsafe {
408            self.component
409                .getBusCount(MediaTypes_::kAudio as i32, BusDirections_::kInput as i32)
410        }
411        .max(0) as usize;
412        let output_count = unsafe {
413            self.component
414                .getBusCount(MediaTypes_::kAudio as i32, BusDirections_::kOutput as i32)
415        }
416        .max(0) as usize;
417        (input_count, output_count)
418    }
419
420    pub fn event_bus_counts(&self) -> (usize, usize) {
421        use vst3::Steinberg::Vst::{BusDirections_, IComponentTrait, MediaTypes_};
422
423        let input_count = unsafe {
424            self.component
425                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kInput as i32)
426        }
427        .max(0) as usize;
428        let output_count = unsafe {
429            self.component
430                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kOutput as i32)
431        }
432        .max(0) as usize;
433        (input_count, output_count)
434    }
435
436    pub fn main_audio_channel_counts(&self) -> (usize, usize) {
437        use vst3::Steinberg::Vst::{BusDirections_, BusTypes_, IComponentTrait, MediaTypes_};
438
439        let main_channels_for_direction = |direction: i32| -> usize {
440            let bus_count = unsafe {
441                self.component
442                    .getBusCount(MediaTypes_::kAudio as i32, direction)
443            }
444            .max(0) as usize;
445            if bus_count == 0 {
446                return 0;
447            }
448
449            let mut first_nonzero = 0usize;
450            for idx in 0..bus_count {
451                let mut info: vst3::Steinberg::Vst::BusInfo = unsafe { std::mem::zeroed() };
452                let result = unsafe {
453                    self.component.getBusInfo(
454                        MediaTypes_::kAudio as i32,
455                        direction,
456                        idx as i32,
457                        &mut info,
458                    )
459                };
460                if result != kResultOk {
461                    continue;
462                }
463                let channels = info.channelCount.max(0) as usize;
464                if channels > 0 && first_nonzero == 0 {
465                    first_nonzero = channels;
466                }
467                if info.busType == BusTypes_::kMain as i32 {
468                    return channels.max(1);
469                }
470            }
471
472            first_nonzero.max(1)
473        };
474
475        (
476            main_channels_for_direction(BusDirections_::kInput as i32),
477            main_channels_for_direction(BusDirections_::kOutput as i32),
478        )
479    }
480
481    /// Initialize the component
482    #[allow(clippy::unnecessary_cast)]
483    pub fn initialize(&mut self, factory: &PluginFactory) -> Result<(), String> {
484        use vst3::Steinberg::IPluginBaseTrait;
485        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
486
487        let context = &mut self.host_context.host as *mut IHostApplication as *mut FUnknown;
488        let result = unsafe { self.component.initialize(context) };
489
490        if result != kResultOk {
491            return Err(format!(
492                "Failed to initialize component (result: {})",
493                result
494            ));
495        }
496
497        let mut processor_ptr: *mut c_void = std::ptr::null_mut();
498        let result = unsafe {
499            let component_raw = self.component.as_ptr();
500            let vtbl = (*component_raw).vtbl;
501            let query_interface = (*vtbl).base.base.queryInterface;
502
503            let iid = std::mem::transmute::<&[u8; 16], &[i8; 16]>(&IAudioProcessor::IID);
504            query_interface(component_raw as *mut _, iid, &mut processor_ptr)
505        };
506
507        if result == kResultOk && !processor_ptr.is_null() {
508            self.audio_processor =
509                unsafe { ComPtr::from_raw(processor_ptr as *mut IAudioProcessor) };
510        }
511
512        let mut controller_ptr: *mut c_void = std::ptr::null_mut();
513        let query_result = unsafe {
514            let component_raw = self.component.as_ptr();
515            let vtbl = (*component_raw).vtbl;
516            let query_interface = (*vtbl).base.base.queryInterface;
517            let iid = std::mem::transmute::<&[u8; 16], &[i8; 16]>(&IEditController::IID);
518            query_interface(component_raw as *mut _, iid, &mut controller_ptr)
519        };
520        if query_result == kResultOk && !controller_ptr.is_null() {
521            self.edit_controller =
522                unsafe { ComPtr::from_raw(controller_ptr as *mut IEditController) };
523        }
524
525        if self.edit_controller.is_none() {
526            let mut controller_cid: TUID = [0; 16];
527            let cid_result = unsafe { self.component.getControllerClassId(&mut controller_cid) };
528            if cid_result == kResultOk {
529                let mut maybe_controller = factory.create_edit_controller(&controller_cid).ok();
530                if let Some(controller) = maybe_controller.as_mut() {
531                    let controller_context =
532                        &mut self.host_context.host as *mut IHostApplication as *mut FUnknown;
533                    let init_result = unsafe { controller.initialize(controller_context) };
534                    if init_result != kResultOk {
535                        maybe_controller = None;
536                    }
537                }
538                self.edit_controller = maybe_controller;
539            }
540        }
541
542        if let Some(controller) = self.edit_controller.as_ref() {
543            let handler = ComWrapper::new(ComponentHandler::new(self.parameter_changes.clone()));
544            if let Some(handler_ptr) = handler.to_com_ptr::<IComponentHandler>() {
545                let _ = unsafe { controller.setComponentHandler(handler_ptr.into_raw()) };
546            }
547            self.component_handler = Some(handler);
548        }
549
550        if let Some(ref controller) = self.edit_controller {
551            let _ = connect_component_and_controller(&self.component, controller);
552        }
553
554        Ok(())
555    }
556
557    /// Query parameter metadata from the edit controller.
558    /// Wrapped in catch_unwind so that buggy plugins don't crash the host.
559    pub fn query_parameters(&self) -> Vec<super::port::ParameterInfo> {
560        let Some(controller) = self.edit_controller.as_ref() else {
561            return Vec::new();
562        };
563
564        let result = protected_call(|| unsafe {
565            use vst3::Steinberg::Vst::IEditControllerTrait;
566            let mut params = Vec::new();
567            let count = controller.getParameterCount();
568            for i in 0..count {
569                let mut info: ParameterInfo = std::mem::zeroed();
570                if controller.getParameterInfo(i, &mut info) != kResultOk {
571                    continue;
572                }
573                let title = extract_string128(&info.title);
574                let short_title = extract_string128(&info.shortTitle);
575                let units = extract_string128(&info.units);
576                let default_value = controller.getParamNormalized(info.id);
577                params.push(super::port::ParameterInfo {
578                    id: info.id,
579                    title,
580                    short_title,
581                    units,
582                    step_count: info.stepCount,
583                    default_value,
584                    flags: info.flags,
585                });
586            }
587            params
588        });
589
590        result.unwrap_or_default()
591    }
592
593    /// Set the component active/inactive
594    pub fn set_active(&mut self, active: bool) -> Result<(), String> {
595        let result = unsafe { self.component.setActive(if active { 1 } else { 0 }) };
596
597        if result != kResultOk {
598            return Err(format!("Failed to set active state (result: {})", result));
599        }
600
601        Ok(())
602    }
603
604    /// Setup processing parameters
605    pub fn setup_processing(
606        &mut self,
607        sample_rate: f64,
608        max_samples: i32,
609        input_channels: i32,
610        output_channels: i32,
611    ) -> Result<(), String> {
612        use vst3::Steinberg::Vst::{
613            BusDirections_, BusInfo, BusInfo_::BusFlags_ as BusFlags, BusTypes_,
614            IAudioProcessorTrait, IComponentTrait, MediaTypes_, SpeakerArr,
615        };
616
617        let processor = self
618            .audio_processor
619            .as_ref()
620            .ok_or("No audio processor available")?;
621
622        let sample_size_result = unsafe { processor.canProcessSampleSize(kSample32 as i32) };
623        if sample_size_result != kResultOk {
624            return Err(format!(
625                "Plugin does not support 32-bit sample size (result: {})",
626                sample_size_result
627            ));
628        }
629
630        let configure_audio_buses = |direction: i32, requested_channels: i32| {
631            let bus_count = unsafe {
632                self.component
633                    .getBusCount(MediaTypes_::kAudio as i32, direction)
634            }
635            .max(0) as usize;
636            if bus_count == 0 {
637                return Vec::new();
638            }
639
640            let mut infos: Vec<BusInfo> = Vec::with_capacity(bus_count);
641            for idx in 0..bus_count {
642                let mut info: BusInfo = unsafe { std::mem::zeroed() };
643                let r = unsafe {
644                    self.component.getBusInfo(
645                        MediaTypes_::kAudio as i32,
646                        direction,
647                        idx as i32,
648                        &mut info,
649                    )
650                };
651                if r != kResultOk {
652                    info.channelCount = if idx == 0 { 2 } else { 0 };
653                    info.busType = if idx == 0 {
654                        BusTypes_::kMain as i32
655                    } else {
656                        BusTypes_::kAux as i32
657                    };
658                    #[allow(clippy::unnecessary_cast)]
659                    {
660                        info.flags = if idx == 0 {
661                            BusFlags::kDefaultActive as u32
662                        } else {
663                            0
664                        };
665                    }
666                }
667                infos.push(info);
668            }
669
670            let mut remaining = requested_channels.max(0);
671            let mut active = vec![false; bus_count];
672            let mut arrangements = vec![SpeakerArr::kEmpty; bus_count];
673
674            let mut ordered: Vec<usize> = (0..bus_count)
675                .filter(|&idx| infos[idx].busType == BusTypes_::kMain as i32)
676                .collect();
677            ordered.extend(
678                (0..bus_count).filter(|&idx| infos[idx].busType != BusTypes_::kMain as i32),
679            );
680
681            for idx in ordered {
682                if remaining <= 0 {
683                    break;
684                }
685                let bus_channels = infos[idx].channelCount.max(1);
686                let allocate = remaining.min(bus_channels);
687                if allocate > 0 {
688                    active[idx] = true;
689                    arrangements[idx] = if allocate > 1 {
690                        SpeakerArr::kStereo
691                    } else {
692                        SpeakerArr::kMono
693                    };
694                    remaining -= allocate;
695                }
696            }
697
698            if requested_channels > 0 && !active.iter().any(|v| *v) {
699                active[0] = true;
700                arrangements[0] = if requested_channels > 1 {
701                    SpeakerArr::kStereo
702                } else {
703                    SpeakerArr::kMono
704                };
705            }
706
707            for (idx, is_active) in active.iter().enumerate().take(bus_count) {
708                let _ = unsafe {
709                    self.component.activateBus(
710                        MediaTypes_::kAudio as i32,
711                        direction,
712                        idx as i32,
713                        if *is_active { 1 } else { 0 },
714                    )
715                };
716            }
717
718            arrangements
719        };
720
721        let mut input_arrangements =
722            configure_audio_buses(BusDirections_::kInput as i32, input_channels);
723        let mut output_arrangements =
724            configure_audio_buses(BusDirections_::kOutput as i32, output_channels);
725        if !input_arrangements.is_empty() || !output_arrangements.is_empty() {
726            let _ = unsafe {
727                processor.setBusArrangements(
728                    if input_arrangements.is_empty() {
729                        std::ptr::null_mut()
730                    } else {
731                        input_arrangements.as_mut_ptr()
732                    },
733                    input_arrangements.len() as i32,
734                    if output_arrangements.is_empty() {
735                        std::ptr::null_mut()
736                    } else {
737                        output_arrangements.as_mut_ptr()
738                    },
739                    output_arrangements.len() as i32,
740                )
741            };
742        }
743
744        let event_in_buses = unsafe {
745            self.component
746                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kInput as i32)
747        }
748        .max(0) as usize;
749        for idx in 0..event_in_buses {
750            let _ = unsafe {
751                self.component.activateBus(
752                    MediaTypes_::kEvent as i32,
753                    BusDirections_::kInput as i32,
754                    idx as i32,
755                    1,
756                )
757            };
758        }
759        let event_out_buses = unsafe {
760            self.component
761                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kOutput as i32)
762        }
763        .max(0) as usize;
764        for idx in 0..event_out_buses {
765            let _ = unsafe {
766                self.component.activateBus(
767                    MediaTypes_::kEvent as i32,
768                    BusDirections_::kOutput as i32,
769                    idx as i32,
770                    1,
771                )
772            };
773        }
774
775        let mut setup = ProcessSetup {
776            processMode: kRealtime as i32,
777            symbolicSampleSize: kSample32 as i32,
778            maxSamplesPerBlock: max_samples,
779            sampleRate: sample_rate,
780        };
781
782        let result = unsafe { processor.setupProcessing(&mut setup) };
783
784        if result != kResultOk {
785            return Err(format!("Failed to setup processing (result: {})", result));
786        }
787
788        Ok(())
789    }
790
791    pub fn start_processing(&mut self) -> Result<(), String> {
792        use vst3::Steinberg::Vst::IAudioProcessorTrait;
793
794        let Some(processor) = &self.audio_processor else {
795            return Ok(());
796        };
797        let result = unsafe { processor.setProcessing(1) };
798        if result != kResultOk {
799            return Err(format!(
800                "Failed to enable processing state (result: {})",
801                result
802            ));
803        }
804        Ok(())
805    }
806
807    pub fn stop_processing(&mut self) {
808        use vst3::Steinberg::Vst::IAudioProcessorTrait;
809
810        if let Some(processor) = &self.audio_processor {
811            unsafe {
812                let _ = processor.setProcessing(0);
813            }
814        }
815    }
816
817    /// Terminate the component
818    pub fn terminate(&mut self) -> Result<(), String> {
819        use vst3::Steinberg::IPluginBaseTrait;
820
821        let result = unsafe { self.component.terminate() };
822
823        if result != kResultOk {
824            return Err(format!(
825                "Failed to terminate component (result: {})",
826                result
827            ));
828        }
829
830        Ok(())
831    }
832}
833
834#[repr(C)]
835struct HostApplicationContext {
836    host: IHostApplication,
837    run_loop: HostRunLoopContext,
838    ref_count: AtomicU32,
839}
840
841impl HostApplicationContext {
842    fn new() -> Self {
843        Self {
844            host: IHostApplication {
845                vtbl: &HOST_APPLICATION_VTBL,
846            },
847            run_loop: HostRunLoopContext::new(),
848            ref_count: AtomicU32::new(1),
849        }
850    }
851}
852
853#[repr(C)]
854struct HostRunLoopContext {
855    iface: Linux::IRunLoop,
856    ref_count: AtomicU32,
857}
858
859impl HostRunLoopContext {
860    fn new() -> Self {
861        Self {
862            iface: Linux::IRunLoop {
863                vtbl: &HOST_RUN_LOOP_VTBL,
864            },
865            ref_count: AtomicU32::new(1),
866        }
867    }
868}
869
870fn connect_component_and_controller(
871    component: &ComPtr<IComponent>,
872    controller: &ComPtr<IEditController>,
873) -> Result<(), String> {
874    let comp_cp = component.cast::<IConnectionPoint>();
875    let ctrl_cp = controller.cast::<IConnectionPoint>();
876
877    if let (Some(comp_cp), Some(ctrl_cp)) = (comp_cp, ctrl_cp) {
878        unsafe {
879            let result1 = comp_cp.connect(ctrl_cp.as_ptr());
880            let result2 = ctrl_cp.connect(comp_cp.as_ptr());
881            if result1 == kResultOk && result2 == kResultOk {
882                Ok(())
883            } else {
884                Err(format!(
885                    "Connection failed: comp->ctrl={:#x}, ctrl->comp={:#x}",
886                    result1, result2
887                ))
888            }
889        }
890    } else {
891        Ok(())
892    }
893}
894
895unsafe extern "system" fn host_query_interface(
896    this: *mut FUnknown,
897    iid: *const TUID,
898    obj: *mut *mut c_void,
899) -> tresult {
900    if this.is_null() || iid.is_null() {
901        if !obj.is_null() {
902            unsafe {
903                *obj = std::ptr::null_mut();
904            }
905        }
906        return kNoInterface;
907    }
908
909    let iid_bytes = unsafe { &*iid };
910    let requested_host = iid_bytes
911        .iter()
912        .zip(IHostApplication::IID.iter())
913        .all(|(a, b)| (*a as u8) == *b);
914    let requested_unknown = iid_bytes
915        .iter()
916        .zip(FUnknown::IID.iter())
917        .all(|(a, b)| (*a as u8) == *b);
918    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
919    let requested_run_loop = iid_bytes
920        .iter()
921        .zip(Linux::IRunLoop::IID.iter())
922        .all(|(a, b)| (*a as u8) == *b);
923    #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd")))]
924    let requested_run_loop = false;
925    if !(requested_host || requested_unknown || requested_run_loop) {
926        if !obj.is_null() {
927            unsafe {
928                *obj = std::ptr::null_mut();
929            }
930        }
931        return kNoInterface;
932    }
933
934    let ctx = this as *mut HostApplicationContext;
935    if !ctx.is_null() {
936        unsafe {
937            if requested_run_loop {
938                (*ctx).run_loop.ref_count.fetch_add(1, Ordering::Relaxed);
939            } else {
940                (*ctx).ref_count.fetch_add(1, Ordering::Relaxed);
941            }
942        }
943    }
944
945    if !obj.is_null() {
946        unsafe {
947            if requested_run_loop {
948                *obj = (&mut (*ctx).run_loop.iface as *mut Linux::IRunLoop).cast::<c_void>();
949            } else {
950                *obj = this.cast::<c_void>();
951            }
952        }
953    }
954    kResultOk
955}
956
957unsafe extern "system" fn host_add_ref(this: *mut FUnknown) -> uint32 {
958    if this.is_null() {
959        return 0;
960    }
961    let ctx = this as *mut HostApplicationContext;
962
963    unsafe { (*ctx).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
964}
965
966unsafe extern "system" fn host_release(this: *mut FUnknown) -> uint32 {
967    if this.is_null() {
968        return 0;
969    }
970    let ctx = this as *mut HostApplicationContext;
971
972    unsafe { (*ctx).ref_count.fetch_sub(1, Ordering::Relaxed) - 1 }
973}
974
975unsafe extern "system" fn host_get_name(
976    _this: *mut IHostApplication,
977    name: *mut String128,
978) -> tresult {
979    if name.is_null() {
980        return kNoInterface;
981    }
982    let encoded: Vec<u16> = "Maolan".encode_utf16().collect();
983
984    unsafe {
985        (*name).fill(0);
986        for (idx, ch) in encoded
987            .iter()
988            .take((*name).len().saturating_sub(1))
989            .enumerate()
990        {
991            (*name)[idx] = *ch;
992        }
993    }
994    kResultOk
995}
996
997unsafe extern "system" fn host_create_instance(
998    _this: *mut IHostApplication,
999    cid: *mut TUID,
1000    iid: *mut TUID,
1001    obj: *mut *mut c_void,
1002) -> tresult {
1003    if obj.is_null() {
1004        return kInvalidArgument;
1005    }
1006
1007    unsafe {
1008        *obj = std::ptr::null_mut();
1009    }
1010
1011    let wants_message =
1012        iid_ptr_matches(cid, &IMessage::IID) || iid_ptr_matches(iid, &IMessage::IID);
1013    let wants_attributes =
1014        iid_ptr_matches(cid, &IAttributeList::IID) || iid_ptr_matches(iid, &IAttributeList::IID);
1015    if wants_message {
1016        let message = Box::new(HostMessage::new());
1017        let raw = Box::into_raw(message);
1018
1019        unsafe {
1020            *obj = (&mut (*raw).iface as *mut IMessage).cast::<c_void>();
1021        }
1022        return kResultOk;
1023    }
1024
1025    if wants_attributes {
1026        let attrs = Box::new(HostAttributeList::new());
1027        let raw = Box::into_raw(attrs);
1028
1029        unsafe {
1030            *obj = (&mut (*raw).iface as *mut IAttributeList).cast::<c_void>();
1031        }
1032        return kResultOk;
1033    }
1034
1035    kNotImplemented
1036}
1037
1038static HOST_APPLICATION_VTBL: IHostApplicationVtbl = IHostApplicationVtbl {
1039    base: FUnknownVtbl {
1040        queryInterface: host_query_interface,
1041        addRef: host_add_ref,
1042        release: host_release,
1043    },
1044    getName: host_get_name,
1045    createInstance: host_create_instance,
1046};
1047
1048unsafe extern "system" fn run_loop_query_interface(
1049    this: *mut FUnknown,
1050    iid: *const TUID,
1051    obj: *mut *mut c_void,
1052) -> tresult {
1053    if this.is_null() || iid.is_null() || obj.is_null() {
1054        return kInvalidArgument;
1055    }
1056    let requested_run_loop = iid_ptr_matches(iid, &Linux::IRunLoop::IID);
1057    let requested_unknown = iid_ptr_matches(iid, &FUnknown::IID);
1058    if !(requested_run_loop || requested_unknown) {
1059        unsafe { *obj = std::ptr::null_mut() };
1060        return kNoInterface;
1061    }
1062    let ctx = this as *mut HostRunLoopContext;
1063    unsafe {
1064        (*ctx).ref_count.fetch_add(1, Ordering::Relaxed);
1065        *obj = this.cast::<c_void>();
1066    }
1067    kResultOk
1068}
1069
1070unsafe extern "system" fn run_loop_add_ref(this: *mut FUnknown) -> uint32 {
1071    if this.is_null() {
1072        return 0;
1073    }
1074    let ctx = this as *mut HostRunLoopContext;
1075    unsafe { (*ctx).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
1076}
1077
1078unsafe extern "system" fn run_loop_release(this: *mut FUnknown) -> uint32 {
1079    if this.is_null() {
1080        return 0;
1081    }
1082    let ctx = this as *mut HostRunLoopContext;
1083    unsafe { (*ctx).ref_count.fetch_sub(1, Ordering::Relaxed) - 1 }
1084}
1085
1086unsafe extern "system" fn run_loop_register_event_handler(
1087    _this: *mut Linux::IRunLoop,
1088    handler: *mut Linux::IEventHandler,
1089    fd: i32,
1090) -> tresult {
1091    if handler.is_null() {
1092        return kInvalidArgument;
1093    }
1094    let unknown = handler as *mut FUnknown;
1095    unsafe {
1096        let _ = ((*(*unknown).vtbl).addRef)(unknown);
1097    }
1098    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1099    state.event_handlers.push(RunLoopEventHandler {
1100        handler: handler as usize,
1101        fd,
1102    });
1103    kResultOk
1104}
1105
1106unsafe extern "system" fn run_loop_unregister_event_handler(
1107    _this: *mut Linux::IRunLoop,
1108    handler: *mut Linux::IEventHandler,
1109) -> tresult {
1110    if handler.is_null() {
1111        return kInvalidArgument;
1112    }
1113    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1114    state.event_handlers.retain(|h| {
1115        if h.handler == handler as usize {
1116            let unknown = handler as *mut FUnknown;
1117            unsafe {
1118                let _ = ((*(*unknown).vtbl).release)(unknown);
1119            }
1120            false
1121        } else {
1122            true
1123        }
1124    });
1125    kResultOk
1126}
1127
1128unsafe extern "system" fn run_loop_register_timer(
1129    _this: *mut Linux::IRunLoop,
1130    handler: *mut Linux::ITimerHandler,
1131    milliseconds: u64,
1132) -> tresult {
1133    if handler.is_null() {
1134        return kInvalidArgument;
1135    }
1136    let unknown = handler as *mut FUnknown;
1137    unsafe {
1138        let _ = ((*(*unknown).vtbl).addRef)(unknown);
1139    }
1140    let interval = Duration::from_millis(milliseconds.max(1));
1141    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1142    state.timer_handlers.push(RunLoopTimerHandler {
1143        handler: handler as usize,
1144        interval,
1145        next_tick: Instant::now() + interval,
1146    });
1147    unsafe {
1148        ((*(*handler).vtbl).onTimer)(handler);
1149    }
1150    kResultOk
1151}
1152
1153unsafe extern "system" fn run_loop_unregister_timer(
1154    _this: *mut Linux::IRunLoop,
1155    handler: *mut Linux::ITimerHandler,
1156) -> tresult {
1157    if handler.is_null() {
1158        return kInvalidArgument;
1159    }
1160    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1161    state.timer_handlers.retain(|t| {
1162        if t.handler == handler as usize {
1163            let unknown = handler as *mut FUnknown;
1164            unsafe {
1165                let _ = ((*(*unknown).vtbl).release)(unknown);
1166            }
1167            false
1168        } else {
1169            true
1170        }
1171    });
1172    kResultOk
1173}
1174
1175static HOST_RUN_LOOP_VTBL: Linux::IRunLoopVtbl = Linux::IRunLoopVtbl {
1176    base: FUnknownVtbl {
1177        queryInterface: run_loop_query_interface,
1178        addRef: run_loop_add_ref,
1179        release: run_loop_release,
1180    },
1181    registerEventHandler: run_loop_register_event_handler,
1182    unregisterEventHandler: run_loop_unregister_event_handler,
1183    registerTimer: run_loop_register_timer,
1184    unregisterTimer: run_loop_unregister_timer,
1185};
1186
1187#[repr(C)]
1188struct HostMessage {
1189    iface: IMessage,
1190    ref_count: AtomicU32,
1191    message_id: FIDString,
1192    attributes: *mut IAttributeList,
1193}
1194
1195impl HostMessage {
1196    fn new() -> Self {
1197        let attrs = Box::new(HostAttributeList::new());
1198        let attrs_raw = Box::into_raw(attrs);
1199        Self {
1200            iface: IMessage {
1201                vtbl: &HOST_MESSAGE_VTBL,
1202            },
1203            ref_count: AtomicU32::new(1),
1204            message_id: c"".as_ptr(),
1205
1206            attributes: unsafe { &mut (*attrs_raw).iface as *mut IAttributeList },
1207        }
1208    }
1209}
1210
1211#[repr(C)]
1212struct HostAttributeList {
1213    iface: IAttributeList,
1214    ref_count: AtomicU32,
1215}
1216
1217impl HostAttributeList {
1218    fn new() -> Self {
1219        Self {
1220            iface: IAttributeList {
1221                vtbl: &HOST_ATTRIBUTE_LIST_VTBL,
1222            },
1223            ref_count: AtomicU32::new(1),
1224        }
1225    }
1226}
1227
1228fn iid_ptr_matches(iid_ptr: *const TUID, guid: &[u8; 16]) -> bool {
1229    if iid_ptr.is_null() {
1230        return false;
1231    }
1232
1233    let iid = unsafe { &*iid_ptr };
1234    iid.iter()
1235        .zip(guid.iter())
1236        .all(|(lhs, rhs)| (*lhs as u8) == *rhs)
1237}
1238
1239unsafe extern "system" fn host_message_query_interface(
1240    this: *mut FUnknown,
1241    iid: *const TUID,
1242    obj: *mut *mut c_void,
1243) -> tresult {
1244    if this.is_null() || iid.is_null() || obj.is_null() {
1245        return kInvalidArgument;
1246    }
1247    let requested_message = iid_ptr_matches(iid, &IMessage::IID);
1248    let requested_unknown = iid_ptr_matches(iid, &FUnknown::IID);
1249    if !(requested_message || requested_unknown) {
1250        unsafe { *obj = std::ptr::null_mut() };
1251        return kNoInterface;
1252    }
1253    let msg = this as *mut HostMessage;
1254
1255    unsafe {
1256        (*msg).ref_count.fetch_add(1, Ordering::Relaxed);
1257        *obj = this.cast::<c_void>();
1258    }
1259    kResultOk
1260}
1261
1262unsafe extern "system" fn host_message_add_ref(this: *mut FUnknown) -> uint32 {
1263    if this.is_null() {
1264        return 0;
1265    }
1266    let msg = this as *mut HostMessage;
1267
1268    unsafe { (*msg).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
1269}
1270
1271unsafe extern "system" fn host_message_release(this: *mut FUnknown) -> uint32 {
1272    if this.is_null() {
1273        return 0;
1274    }
1275    let msg = this as *mut HostMessage;
1276
1277    let remaining = unsafe { (*msg).ref_count.fetch_sub(1, Ordering::AcqRel) - 1 };
1278    if remaining == 0 {
1279        unsafe {
1280            if !(*msg).attributes.is_null() {
1281                let attrs_unknown = (*msg).attributes.cast::<FUnknown>();
1282                let _ = host_attr_release(attrs_unknown);
1283                (*msg).attributes = std::ptr::null_mut();
1284            }
1285            let _ = Box::from_raw(msg);
1286        }
1287    }
1288    remaining
1289}
1290
1291unsafe extern "system" fn host_message_get_id(this: *mut IMessage) -> FIDString {
1292    if this.is_null() {
1293        return c"".as_ptr();
1294    }
1295    let msg = this as *mut HostMessage;
1296
1297    unsafe { (*msg).message_id }
1298}
1299
1300unsafe extern "system" fn host_message_set_id(this: *mut IMessage, id: FIDString) {
1301    if this.is_null() {
1302        return;
1303    }
1304    let msg = this as *mut HostMessage;
1305
1306    unsafe {
1307        (*msg).message_id = if id.is_null() { c"".as_ptr() } else { id };
1308    }
1309}
1310
1311unsafe extern "system" fn host_message_get_attributes(this: *mut IMessage) -> *mut IAttributeList {
1312    if this.is_null() {
1313        return std::ptr::null_mut();
1314    }
1315    let msg = this as *mut HostMessage;
1316
1317    unsafe {
1318        if !(*msg).attributes.is_null() {
1319            let attrs_unknown = (*msg).attributes.cast::<FUnknown>();
1320            let _ = host_attr_add_ref(attrs_unknown);
1321        }
1322        (*msg).attributes
1323    }
1324}
1325
1326static HOST_MESSAGE_VTBL: IMessageVtbl = IMessageVtbl {
1327    base: FUnknownVtbl {
1328        queryInterface: host_message_query_interface,
1329        addRef: host_message_add_ref,
1330        release: host_message_release,
1331    },
1332    getMessageID: host_message_get_id,
1333    setMessageID: host_message_set_id,
1334    getAttributes: host_message_get_attributes,
1335};
1336
1337unsafe extern "system" fn host_attr_query_interface(
1338    this: *mut FUnknown,
1339    iid: *const TUID,
1340    obj: *mut *mut c_void,
1341) -> tresult {
1342    if this.is_null() || iid.is_null() || obj.is_null() {
1343        return kInvalidArgument;
1344    }
1345    let requested_attr = iid_ptr_matches(iid, &IAttributeList::IID);
1346    let requested_unknown = iid_ptr_matches(iid, &FUnknown::IID);
1347    if !(requested_attr || requested_unknown) {
1348        unsafe { *obj = std::ptr::null_mut() };
1349        return kNoInterface;
1350    }
1351    let attrs = this as *mut HostAttributeList;
1352
1353    unsafe {
1354        (*attrs).ref_count.fetch_add(1, Ordering::Relaxed);
1355        *obj = this.cast::<c_void>();
1356    }
1357    kResultOk
1358}
1359
1360unsafe extern "system" fn host_attr_add_ref(this: *mut FUnknown) -> uint32 {
1361    if this.is_null() {
1362        return 0;
1363    }
1364    let attrs = this as *mut HostAttributeList;
1365
1366    unsafe { (*attrs).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
1367}
1368
1369unsafe extern "system" fn host_attr_release(this: *mut FUnknown) -> uint32 {
1370    if this.is_null() {
1371        return 0;
1372    }
1373    let attrs = this as *mut HostAttributeList;
1374
1375    let remaining = unsafe { (*attrs).ref_count.fetch_sub(1, Ordering::AcqRel) - 1 };
1376    if remaining == 0 {
1377        unsafe {
1378            let _ = Box::from_raw(attrs);
1379        }
1380    }
1381    remaining
1382}
1383
1384unsafe extern "system" fn host_attr_set_int(
1385    _this: *mut IAttributeList,
1386    _id: IAttrID,
1387    _value: i64,
1388) -> tresult {
1389    kResultOk
1390}
1391
1392unsafe extern "system" fn host_attr_get_int(
1393    _this: *mut IAttributeList,
1394    _id: IAttrID,
1395    value: *mut i64,
1396) -> tresult {
1397    if value.is_null() {
1398        return kInvalidArgument;
1399    }
1400
1401    unsafe { *value = 0 };
1402    kResultFalse
1403}
1404
1405unsafe extern "system" fn host_attr_set_float(
1406    _this: *mut IAttributeList,
1407    _id: IAttrID,
1408    _value: f64,
1409) -> tresult {
1410    kResultOk
1411}
1412
1413unsafe extern "system" fn host_attr_get_float(
1414    _this: *mut IAttributeList,
1415    _id: IAttrID,
1416    value: *mut f64,
1417) -> tresult {
1418    if value.is_null() {
1419        return kInvalidArgument;
1420    }
1421
1422    unsafe { *value = 0.0 };
1423    kResultFalse
1424}
1425
1426unsafe extern "system" fn host_attr_set_string(
1427    _this: *mut IAttributeList,
1428    _id: IAttrID,
1429    _string: *const TChar,
1430) -> tresult {
1431    kResultOk
1432}
1433
1434unsafe extern "system" fn host_attr_get_string(
1435    _this: *mut IAttributeList,
1436    _id: IAttrID,
1437    string: *mut TChar,
1438    size_in_bytes: u32,
1439) -> tresult {
1440    if string.is_null() || size_in_bytes < std::mem::size_of::<TChar>() as u32 {
1441        return kInvalidArgument;
1442    }
1443
1444    unsafe { *string = 0 };
1445    kResultFalse
1446}
1447
1448unsafe extern "system" fn host_attr_set_binary(
1449    _this: *mut IAttributeList,
1450    _id: IAttrID,
1451    _data: *const c_void,
1452    _size_in_bytes: u32,
1453) -> tresult {
1454    kResultOk
1455}
1456
1457unsafe extern "system" fn host_attr_get_binary(
1458    _this: *mut IAttributeList,
1459    _id: IAttrID,
1460    data: *mut *const c_void,
1461    size_in_bytes: *mut u32,
1462) -> tresult {
1463    if data.is_null() || size_in_bytes.is_null() {
1464        return kInvalidArgument;
1465    }
1466
1467    unsafe {
1468        *data = std::ptr::null();
1469        *size_in_bytes = 0;
1470    }
1471    kResultFalse
1472}
1473
1474static HOST_ATTRIBUTE_LIST_VTBL: IAttributeListVtbl = IAttributeListVtbl {
1475    base: FUnknownVtbl {
1476        queryInterface: host_attr_query_interface,
1477        addRef: host_attr_add_ref,
1478        release: host_attr_release,
1479    },
1480    setInt: host_attr_set_int,
1481    getInt: host_attr_get_int,
1482    setFloat: host_attr_set_float,
1483    getFloat: host_attr_get_float,
1484    setString: host_attr_set_string,
1485    getString: host_attr_get_string,
1486    setBinary: host_attr_set_binary,
1487    getBinary: host_attr_get_binary,
1488};
1489
1490/// Get the actual module path from a VST3 bundle path
1491fn get_module_path(bundle_path: &Path) -> Result<std::path::PathBuf, String> {
1492    #[cfg(target_os = "macos")]
1493    {
1494        let module = bundle_path.join("Contents").join("MacOS").join(
1495            bundle_path
1496                .file_stem()
1497                .and_then(|s| s.to_str())
1498                .unwrap_or("plugin"),
1499        );
1500        if module.exists() {
1501            Ok(module)
1502        } else {
1503            Err(format!("VST3 module not found at {:?}", module))
1504        }
1505    }
1506
1507    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
1508    {
1509        let module = bundle_path
1510            .join("Contents")
1511            .join("x86_64-linux")
1512            .join(format!(
1513                "{}.so",
1514                bundle_path
1515                    .file_stem()
1516                    .and_then(|s| s.to_str())
1517                    .unwrap_or("plugin")
1518            ));
1519        if module.exists() {
1520            Ok(module)
1521        } else {
1522            Err(format!("VST3 module not found at {:?}", module))
1523        }
1524    }
1525
1526    #[cfg(target_os = "windows")]
1527    {
1528        let contents = bundle_path.join("Contents");
1529        let arch_dir = if cfg!(target_arch = "x86_64") {
1530            contents.join("x86_64-win")
1531        } else {
1532            contents.join("x86-win")
1533        };
1534
1535        if let Ok(entries) = std::fs::read_dir(&arch_dir) {
1536            for entry in entries.flatten() {
1537                let file_path = entry.path();
1538                if file_path.is_file()
1539                    && file_path
1540                        .extension()
1541                        .is_some_and(|ext| ext.eq_ignore_ascii_case("vst3"))
1542                {
1543                    return Ok(file_path);
1544                }
1545            }
1546        }
1547
1548        let fallback_dir = if cfg!(target_arch = "x86_64") {
1549            contents.join("x86-win")
1550        } else {
1551            contents.join("x86_64-win")
1552        };
1553        if let Ok(entries) = std::fs::read_dir(&fallback_dir) {
1554            for entry in entries.flatten() {
1555                let file_path = entry.path();
1556                if file_path.is_file()
1557                    && file_path
1558                        .extension()
1559                        .is_some_and(|ext| ext.eq_ignore_ascii_case("vst3"))
1560                {
1561                    return Ok(file_path);
1562                }
1563            }
1564        }
1565
1566        Err(format!(
1567            "VST3 module not found under {:?}",
1568            bundle_path.join("Contents")
1569        ))
1570    }
1571
1572    #[cfg(not(any(
1573        target_os = "macos",
1574        target_os = "linux",
1575        target_os = "freebsd",
1576        target_os = "openbsd",
1577        target_os = "windows"
1578    )))]
1579    {
1580        Err("Unsupported platform".to_string())
1581    }
1582}
1583
1584fn extract_cstring(bytes: &[i8]) -> String {
1585    let len = bytes.iter().position(|&c| c == 0).unwrap_or(bytes.len());
1586    let u8_bytes: Vec<u8> = bytes[..len].iter().map(|&b| b as u8).collect();
1587    String::from_utf8_lossy(&u8_bytes).to_string()
1588}
1589
1590fn extract_string128(s: &String128) -> String {
1591    let len = s.iter().position(|&c| c == 0).unwrap_or(s.len());
1592    String::from_utf16_lossy(&s[..len])
1593}
1594
1595#[cfg(test)]
1596mod tests {
1597    use super::*;
1598    use std::fs::{self, File};
1599    use std::path::PathBuf;
1600    use std::time::{SystemTime, UNIX_EPOCH};
1601
1602    fn unique_temp_dir(name: &str) -> PathBuf {
1603        let nanos = SystemTime::now()
1604            .duration_since(UNIX_EPOCH)
1605            .expect("clock before epoch")
1606            .as_nanos();
1607        std::env::temp_dir().join(format!(
1608            "maolan-engine-{name}-{}-{nanos}",
1609            std::process::id()
1610        ))
1611    }
1612
1613    #[test]
1614    fn iid_ptr_matches_rejects_null_and_non_matching_guids() {
1615        let iid: TUID = [1; 16];
1616        let matching_guid = [1_u8; 16];
1617        let different_guid = [2_u8; 16];
1618
1619        assert!(!iid_ptr_matches(std::ptr::null(), &matching_guid));
1620        assert!(iid_ptr_matches(&iid, &matching_guid));
1621        assert!(!iid_ptr_matches(&iid, &different_guid));
1622    }
1623
1624    #[test]
1625    fn extract_cstring_stops_at_nul_and_uses_lossy_utf8() {
1626        let bytes = [b'A' as i8, b'B' as i8, -1, 0, b'Z' as i8];
1627
1628        assert_eq!(extract_cstring(&bytes), "AB\u{FFFD}");
1629    }
1630
1631    #[test]
1632    fn extract_cstring_uses_full_slice_when_not_nul_terminated() {
1633        let bytes = [b'X' as i8, b'Y' as i8, b'Z' as i8];
1634
1635        assert_eq!(extract_cstring(&bytes), "XYZ");
1636    }
1637
1638    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
1639    #[test]
1640    fn get_module_path_returns_unix_shared_object_path() {
1641        let bundle_path = unique_temp_dir("vst3-module").join("Example.vst3");
1642        let module_path = bundle_path
1643            .join("Contents")
1644            .join("x86_64-linux")
1645            .join("Example.so");
1646        fs::create_dir_all(module_path.parent().expect("module parent"))
1647            .expect("create module directory");
1648        File::create(&module_path).expect("create module file");
1649
1650        let resolved = get_module_path(&bundle_path).expect("resolve module path");
1651
1652        assert_eq!(resolved, module_path);
1653
1654        let _ = fs::remove_dir_all(bundle_path.parent().expect("bundle parent"));
1655    }
1656
1657    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
1658    #[test]
1659    fn get_module_path_errors_when_unix_module_is_missing() {
1660        let bundle_path = unique_temp_dir("missing-vst3").join("Missing.vst3");
1661        fs::create_dir_all(&bundle_path).expect("create bundle directory");
1662
1663        let err = get_module_path(&bundle_path).expect_err("missing module should error");
1664
1665        assert!(err.contains("Missing.so"));
1666
1667        let _ = fs::remove_dir_all(bundle_path.parent().expect("bundle parent"));
1668    }
1669
1670    #[cfg(target_os = "windows")]
1671    #[test]
1672    fn get_module_path_returns_windows_vst3_binary_path() {
1673        let bundle_path = unique_temp_dir("vst3-module").join("Example.vst3");
1674        let module_path = bundle_path
1675            .join("Contents")
1676            .join("x86_64-win")
1677            .join("Example.vst3");
1678        fs::create_dir_all(module_path.parent().expect("module parent"))
1679            .expect("create module directory");
1680        File::create(&module_path).expect("create module file");
1681
1682        let resolved = get_module_path(&bundle_path).expect("resolve module path");
1683
1684        assert_eq!(resolved, module_path);
1685
1686        let _ = fs::remove_dir_all(bundle_path.parent().expect("bundle parent"));
1687    }
1688
1689    #[cfg(target_os = "windows")]
1690    #[test]
1691    fn get_module_path_errors_when_windows_module_is_missing() {
1692        let bundle_path = unique_temp_dir("missing-vst3").join("Missing.vst3");
1693        fs::create_dir_all(&bundle_path).expect("create bundle directory");
1694
1695        let err = get_module_path(&bundle_path).expect_err("missing module should error");
1696
1697        assert!(err.contains("Contents"));
1698
1699        let _ = fs::remove_dir_all(bundle_path.parent().expect("bundle parent"));
1700    }
1701}