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}