Skip to main content

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