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>
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
92pub 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 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 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 pub fn count_classes(&self) -> i32 {
172 use vst3::Steinberg::IPluginFactoryTrait;
173 unsafe { self.factory.countClasses() }
174 }
175
176 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 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
273pub struct ClassInfo {
275 pub name: String,
276 pub category: String,
277 pub cid: [i8; 16],
278}
279
280#[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#[derive(Debug, Clone)]
291pub struct Vst3GuiInfo {
292 pub has_gui: bool,
293 pub size: Option<(i32, i32)>,
294}
295
296pub 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
336pub 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
372pub 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 #[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 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 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 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 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
1490fn 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}