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