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