1use crate::audio::io::AudioIO;
4use crate::midi::io::MidiEvent;
5use crate::mutex::UnsafeMutex;
6use crate::vst3::port::ParameterInfo;
7use crate::vst3::state::Vst3PluginState;
8use maolan_plugin_protocol::events::EventPair;
9use maolan_plugin_protocol::protocol::*;
10use maolan_plugin_protocol::ringbuf::RingBuffer;
11use maolan_plugin_protocol::shm::ShmMapping;
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14use std::process::{Child, Command, Stdio};
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::{Arc, atomic::AtomicU32};
17use std::time::{Duration, Instant};
18
19pub struct Vst3Processor {
21 path: String,
22 name: String,
23 audio_inputs: Vec<Arc<AudioIO>>,
24 audio_outputs: Vec<Arc<AudioIO>>,
25 main_audio_inputs: usize,
26 main_audio_outputs: usize,
27 param_infos: Vec<ParameterInfo>,
28 param_values: UnsafeMutex<HashMap<u32, f64>>,
29 bypassed: Arc<AtomicBool>,
30 child: UnsafeMutex<Option<Child>>,
32 mapping: Option<ShmMapping>,
33 events: Option<EventPair>,
34 shm_name: String,
35 crash_count: AtomicU32,
37 last_process_time: UnsafeMutex<Instant>,
38}
39
40pub type SharedVst3Processor = Arc<UnsafeMutex<Vst3Processor>>;
41
42impl Vst3Processor {
43 pub fn new(
44 sample_rate: f64,
45 buffer_size: usize,
46 plugin_path: &str,
47 input_count: usize,
48 output_count: usize,
49 host_binary: PathBuf,
50 ) -> Result<Self, String> {
51 let audio_inputs = (0..input_count.max(1))
52 .map(|_| Arc::new(AudioIO::new(buffer_size)))
53 .collect::<Vec<_>>();
54 let audio_outputs = (0..output_count.max(1))
55 .map(|_| Arc::new(AudioIO::new(buffer_size)))
56 .collect::<Vec<_>>();
57
58 let instance_id = format!("vst3-{}", std::process::id());
59 let (mut child, mapping, events, shm_name) = spawn_host(
60 &host_binary,
61 plugin_path,
62 &instance_id,
63 sample_rate,
64 buffer_size,
65 input_count.max(1),
66 output_count.max(1),
67 )?;
68
69 let header = unsafe { header_ref(mapping.as_ptr()) };
70 if !wait_for_ready(header, Duration::from_secs(10)) {
71 let _ = child.kill();
72 return Err("VST3 host did not signal ready".to_string());
73 }
74
75 let param_infos = Vec::new();
76 let name = Path::new(plugin_path)
77 .file_stem()
78 .and_then(|s| s.to_str())
79 .unwrap_or("VST3")
80 .to_string();
81
82 Ok(Self {
83 path: plugin_path.to_string(),
84 name,
85 audio_inputs,
86 audio_outputs,
87 main_audio_inputs: input_count.max(1),
88 main_audio_outputs: output_count.max(1),
89 param_infos,
90 param_values: UnsafeMutex::new(HashMap::new()),
91 bypassed: Arc::new(AtomicBool::new(false)),
92 child: UnsafeMutex::new(Some(child)),
93 mapping: Some(mapping),
94 events: Some(events),
95 shm_name,
96 crash_count: AtomicU32::new(0),
97 last_process_time: UnsafeMutex::new(Instant::now()),
98 })
99 }
100
101 pub fn setup_audio_ports(&self) {
102 for port in &self.audio_inputs {
103 port.setup();
104 }
105 for port in &self.audio_outputs {
106 port.setup();
107 }
108 }
109
110 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
111 &self.audio_inputs
112 }
113
114 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
115 &self.audio_outputs
116 }
117
118 pub fn main_audio_input_count(&self) -> usize {
119 self.main_audio_inputs
120 }
121
122 pub fn main_audio_output_count(&self) -> usize {
123 self.main_audio_outputs
124 }
125
126 pub fn midi_input_count(&self) -> usize {
127 0
128 }
129
130 pub fn midi_output_count(&self) -> usize {
131 0
132 }
133
134 pub fn set_bypassed(&self, bypassed: bool) {
135 self.bypassed.store(bypassed, Ordering::Relaxed);
136 }
137
138 pub fn is_bypassed(&self) -> bool {
139 self.bypassed.load(Ordering::Relaxed)
140 }
141
142 pub fn parameter_infos(&self) -> Vec<ParameterInfo> {
143 self.param_infos.clone()
144 }
145
146 pub fn parameter_values(&self) -> HashMap<u32, f64> {
147 self.param_values.lock().clone()
148 }
149
150 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
151 self.set_parameter_at(param_id, value, 0)
152 }
153
154 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
155 self.param_values.lock().insert(param_id, value);
156 if let Some(ref mapping) = self.mapping {
157 let ring = unsafe {
158 let buf = param_ring_ptr(mapping.as_ptr());
159 let (w, r) = param_indices(mapping.as_ptr());
160 RingBuffer::new(buf, w, r, RING_CAPACITY)
161 };
162 let ev = ParameterEvent {
163 param_index: param_id,
164 value: value as f32,
165 sample_offset: 0,
166 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
167 };
168 if !ring.push(ev) {
169 tracing::warn!("VST3 param ring full, dropping parameter event");
170 }
171 }
172 Ok(())
173 }
174
175 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
176 Ok(())
177 }
178
179 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
180 Ok(())
181 }
182
183 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
184 false
185 }
186
187 pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
188 let (mapping, events) = match (&self.mapping, &self.events) {
189 (Some(m), Some(e)) => (m, e),
190 _ => return Err("VST3 processor not initialized".to_string()),
191 };
192 let ptr = mapping.as_ptr();
193 let header = unsafe { header_mut(ptr) };
194
195 header.request_type.store(1, Ordering::Release);
197 header.request_status.store(0, Ordering::Release);
198 if let Err(e) = events.signal_host() {
199 header.request_type.store(0, Ordering::Release);
200 return Err(format!("Failed to signal host for state save: {}", e));
201 }
202
203 if let Err(e) = events.wait_host(Duration::from_secs(5)) {
205 header.request_type.store(0, Ordering::Release);
206 return Err(format!("Host did not respond to state save: {}", e));
207 }
208
209 let status = header.request_status.load(Ordering::Acquire);
210 let size = header.scratch_size.load(Ordering::Acquire) as usize;
211 if status != 1 {
212 header.request_type.store(0, Ordering::Release);
213 return Err("State save failed in host".to_string());
214 }
215
216 let scratch = unsafe { scratch_ptr(ptr) };
217 let state = deserialize_vst3_state(scratch, size)?;
218 header.request_type.store(0, Ordering::Release);
219 Ok(state)
220 }
221
222 pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
223 let (mapping, events) = match (&self.mapping, &self.events) {
224 (Some(m), Some(e)) => (m, e),
225 _ => return Err("VST3 processor not initialized".to_string()),
226 };
227 let ptr = mapping.as_ptr();
228 let header = unsafe { header_mut(ptr) };
229
230 let scratch = unsafe { scratch_ptr(ptr) };
232 let size = serialize_vst3_state(scratch, state)?;
233 header.scratch_size.store(size as u32, Ordering::Release);
234
235 header.request_type.store(2, Ordering::Release);
237 header.request_status.store(0, Ordering::Release);
238 if let Err(e) = events.signal_host() {
239 header.request_type.store(0, Ordering::Release);
240 return Err(format!("Failed to signal host for state restore: {}", e));
241 }
242
243 if let Err(e) = events.wait_host(Duration::from_secs(5)) {
245 header.request_type.store(0, Ordering::Release);
246 return Err(format!("Host did not respond to state restore: {}", e));
247 }
248
249 let status = header.request_status.load(Ordering::Acquire);
250 header.request_type.store(0, Ordering::Release);
251 if status != 1 {
252 return Err("State restore failed in host".to_string());
253 }
254 Ok(())
255 }
256
257 pub fn process_with_audio_io(&self, frames: usize) {
258 let _ = self.process_with_midi(frames, &[]);
259 }
260
261 pub fn process_with_midi(&self, frames: usize, _midi_in: &[MidiEvent]) -> Vec<MidiEvent> {
262 if self.bypassed.load(Ordering::Relaxed) {
263 self.bypass_copy_inputs_to_outputs();
264 return Vec::new();
265 }
266
267 {
269 let child = self.child.lock();
270 if let Some(ref mut c) = child.as_mut() {
271 match c.try_wait() {
272 Ok(Some(status)) if !status.success() => {
273 tracing::error!(
274 "VST3 plugin host crashed for '{}' ({})",
275 self.name,
276 self.path
277 );
278 self.crash_count.fetch_add(1, Ordering::Relaxed);
279 self.bypass_copy_inputs_to_outputs();
280 return Vec::new();
281 }
282 Ok(None) => {
283 eprintln!("[VST3 debug] host still alive");
284 }
285 Ok(Some(status)) => {
286 eprintln!("[VST3 debug] host exited with success: {:?}", status);
287 }
288 Err(e) => {
289 eprintln!("[VST3 debug] try_wait error: {}", e);
290 }
291 }
292 }
293 }
294
295 let started = Instant::now();
296
297 let (mapping, events) = match (&self.mapping, &self.events) {
298 (Some(m), Some(e)) => (m, e),
299 _ => {
300 self.bypass_copy_inputs_to_outputs();
301 return Vec::new();
302 }
303 };
304
305 let ptr = mapping.as_ptr();
306 let num_in = self.audio_inputs.len();
307 let num_out = self.audio_outputs.len();
308 unsafe {
309 let h = header_mut(ptr);
310 h.block_size.store(frames as u32, Ordering::Release);
311 h.num_input_channels.store(num_in as u32, Ordering::Release);
312 h.num_output_channels
313 .store(num_out as u32, Ordering::Release);
314 let t = transport_mut(ptr);
316 t.playhead_sample = 0;
317 t.tempo = 120.0;
318 t.numerator = 4;
319 t.denominator = 4;
320 t.flags = 1; }
322
323 for (ch, input) in self.audio_inputs.iter().enumerate() {
325 let src = input.buffer.lock();
326 let dst = unsafe { audio_channel_ptr(ptr, ch, 0) };
327 let len = frames.min(src.len());
328 unsafe {
329 std::ptr::copy_nonoverlapping(src.as_ptr(), dst, len);
330 }
331 }
332
333 if let Err(e) = events.signal_host() {
335 tracing::error!("Failed to signal VST3 host: {e}");
336 self.bypass_copy_inputs_to_outputs();
337 return Vec::new();
338 }
339 eprintln!("[VST3 debug] signal_host succeeded");
340
341 let timeout = Duration::from_millis(100);
343 match events.wait_host(timeout) {
344 Ok(()) => {
345 eprintln!("[VST3 debug] wait_host succeeded");
346 }
347 Err(e) => {
348 eprintln!(
349 "[VST3 debug] host did not respond for '{}' ({}): {}",
350 self.name, self.path, e
351 );
352 self.bypass_copy_inputs_to_outputs();
353 return Vec::new();
354 }
355 }
356
357 for (ch, output) in self.audio_outputs.iter().enumerate() {
359 let dst = output.buffer.lock();
360 let src = unsafe { audio_channel_ptr(ptr, ch, 1) };
361 let len = frames.min(dst.len());
362 unsafe {
363 std::ptr::copy_nonoverlapping(src, dst.as_mut_ptr(), len);
364 }
365 *output.finished.lock() = true;
366 }
367
368 let elapsed = started.elapsed();
369 if elapsed > Duration::from_millis(20) {
370 tracing::warn!(
371 "Slow VST3 process '{}' ({}) took {:.3} ms for {} frames",
372 self.name,
373 self.path,
374 elapsed.as_secs_f64() * 1000.0,
375 frames
376 );
377 }
378
379 *self.last_process_time.lock() = Instant::now();
380 Vec::new()
381 }
382
383 fn bypass_copy_inputs_to_outputs(&self) {
384 for (input, output) in self.audio_inputs.iter().zip(self.audio_outputs.iter()) {
385 let src = input.buffer.lock();
386 let dst = output.buffer.lock();
387 dst.fill(0.0);
388 for (d, s) in dst.iter_mut().zip(src.iter()) {
389 *d = *s;
390 }
391 *output.finished.lock() = true;
392 }
393 for output in self.audio_outputs.iter().skip(self.audio_inputs.len()) {
394 output.buffer.lock().fill(0.0);
395 *output.finished.lock() = true;
396 }
397 }
398
399 pub fn path(&self) -> &str {
400 &self.path
401 }
402
403 pub fn name(&self) -> &str {
404 &self.name
405 }
406
407 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
408 Ok(())
409 }
410
411 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
412 Ok(())
413 }
414
415 pub fn run_host_callbacks_main_thread(&self) {}
416
417 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
418 Ok(false)
419 }
420
421 pub fn ui_begin_session(&self) {}
422 pub fn ui_end_session(&self) {}
423 pub fn ui_should_close(&self) -> bool {
424 false
425 }
426 pub fn ui_take_due_timers(&self) -> Vec<u32> {
427 Vec::new()
428 }
429 pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
430 Vec::new()
431 }
432 pub fn ui_take_state_update(&self) -> Option<Vst3PluginState> {
433 None
434 }
435
436 pub fn gui_info(&self) -> Result<crate::vst3::interfaces::Vst3GuiInfo, String> {
437 Err("GUI not yet supported for VST3 plugins".to_string())
438 }
439
440 pub fn gui_create(&self, _platform_type: &str) -> Result<(), String> {
441 Err("GUI not yet supported for VST3 plugins".to_string())
442 }
443
444 pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
445 Err("GUI not yet supported for VST3 plugins".to_string())
446 }
447
448 pub fn gui_set_parent(&self, _window: usize, _platform_type: &str) -> Result<(), String> {
449 Err("GUI not yet supported for VST3 plugins".to_string())
450 }
451
452 pub fn gui_on_size(&self, _width: i32, _height: i32) -> Result<(), String> {
453 Err("GUI not yet supported for VST3 plugins".to_string())
454 }
455
456 pub fn gui_show(&self) -> Result<(), String> {
457 if let Some(ref mapping) = self.mapping
458 && let Some(ref events) = self.events
459 {
460 let header = unsafe { header_mut(mapping.as_ptr()) };
461 header.request_type.store(3, Ordering::Release);
462 let _ = events.signal_host();
463 return Ok(());
464 }
465 Err("No active host to show GUI".to_string())
466 }
467
468 pub fn gui_hide(&self) {
469 if let Some(ref mapping) = self.mapping
470 && let Some(ref events) = self.events
471 {
472 let header = unsafe { header_mut(mapping.as_ptr()) };
473 header.request_type.store(4, Ordering::Release);
474 let _ = events.signal_host();
475 }
476 }
477
478 pub fn gui_destroy(&self) {}
479
480 pub fn gui_on_main_thread(&self) {}
481
482 pub fn gui_on_timer(&self, _timer_id: u32) {}
483
484 pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
485 None
486 }
487
488 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
489 let mut result = Vec::new();
490 if let Some(ref mapping) = self.mapping {
491 let ring = unsafe {
492 let buf = echo_ring_ptr(mapping.as_ptr());
493 let (w, r) = echo_indices(mapping.as_ptr());
494 RingBuffer::new(buf, w, r, RING_CAPACITY)
495 };
496 while let Some(ev) = ring.pop() {
497 result.push(ev);
498 }
499 }
500 result
501 }
502}
503
504impl Drop for Vst3Processor {
505 fn drop(&mut self) {
506 if let Some(ref mapping) = self.mapping
507 && let Some(ref events) = self.events
508 {
509 let header = unsafe { header_mut(mapping.as_ptr()) };
510 header.shutdown_request.store(1, Ordering::Release);
511 let _ = events.signal_host();
512 }
513 let mut child_opt = self.child.lock().take();
514 if let Some(mut child) = child_opt.take() {
515 let start = Instant::now();
516 while start.elapsed() < Duration::from_secs(2) {
517 if child.try_wait().map(|s| s.is_some()).unwrap_or(true) {
518 break;
519 }
520 std::thread::sleep(Duration::from_millis(10));
521 }
522 if child.try_wait().map(|s| s.is_none()).unwrap_or(false) {
523 let _ = child.kill();
524 }
525 }
526 let _ = ShmMapping::unlink(&self.shm_name);
527 }
528}
529
530impl UnsafeMutex<Vst3Processor> {
531 pub fn setup_audio_ports(&self) {
532 self.lock().setup_audio_ports();
533 }
534
535 pub fn process_with_midi(&self, frames: usize, midi_events: &[MidiEvent]) -> Vec<MidiEvent> {
536 self.lock().process_with_midi(frames, midi_events)
537 }
538
539 pub fn set_bypassed(&self, bypassed: bool) {
540 self.lock().set_bypassed(bypassed);
541 }
542
543 pub fn is_bypassed(&self) -> bool {
544 self.lock().is_bypassed()
545 }
546
547 pub fn parameter_infos(&self) -> Vec<ParameterInfo> {
548 self.lock().parameter_infos()
549 }
550
551 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
552 self.lock().set_parameter(param_id, value)
553 }
554
555 pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
556 self.lock().set_parameter_at(param_id, value, frame)
557 }
558
559 pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
560 self.lock().begin_parameter_edit_at(param_id, frame)
561 }
562
563 pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
564 self.lock().end_parameter_edit_at(param_id, frame)
565 }
566
567 pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
568 self.lock().snapshot_state()
569 }
570
571 pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
572 self.lock().restore_state(state)
573 }
574
575 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
576 self.lock().audio_inputs()
577 }
578
579 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
580 self.lock().audio_outputs()
581 }
582
583 pub fn main_audio_input_count(&self) -> usize {
584 self.lock().main_audio_input_count()
585 }
586
587 pub fn main_audio_output_count(&self) -> usize {
588 self.lock().main_audio_output_count()
589 }
590
591 pub fn midi_input_count(&self) -> usize {
592 self.lock().midi_input_count()
593 }
594
595 pub fn midi_output_count(&self) -> usize {
596 self.lock().midi_output_count()
597 }
598
599 pub fn path(&self) -> String {
600 self.lock().path().to_string()
601 }
602
603 pub fn name(&self) -> String {
604 self.lock().name().to_string()
605 }
606
607 pub fn run_host_callbacks_main_thread(&self) {
608 self.lock().run_host_callbacks_main_thread();
609 }
610
611 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
612 self.lock().reconfigure_ports_if_needed()
613 }
614
615 pub fn ui_begin_session(&self) {
616 self.lock().ui_begin_session();
617 }
618
619 pub fn ui_end_session(&self) {
620 self.lock().ui_end_session();
621 }
622
623 pub fn ui_should_close(&self) -> bool {
624 self.lock().ui_should_close()
625 }
626
627 pub fn ui_take_due_timers(&self) -> Vec<u32> {
628 self.lock().ui_take_due_timers()
629 }
630
631 pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
632 self.lock().ui_take_param_updates()
633 }
634
635 pub fn ui_take_state_update(&self) -> Option<Vst3PluginState> {
636 self.lock().ui_take_state_update()
637 }
638
639 pub fn gui_info(&self) -> Result<crate::vst3::interfaces::Vst3GuiInfo, String> {
640 self.lock().gui_info()
641 }
642
643 pub fn gui_create(&self, platform_type: &str) -> Result<(), String> {
644 self.lock().gui_create(platform_type)
645 }
646
647 pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
648 self.lock().gui_get_size()
649 }
650
651 pub fn gui_set_parent(&self, window: usize, platform_type: &str) -> Result<(), String> {
652 self.lock().gui_set_parent(window, platform_type)
653 }
654
655 pub fn gui_on_size(&self, width: i32, height: i32) -> Result<(), String> {
656 self.lock().gui_on_size(width, height)
657 }
658
659 pub fn gui_show(&self) -> Result<(), String> {
660 self.lock().gui_show()
661 }
662
663 pub fn gui_hide(&self) {
664 self.lock().gui_hide();
665 }
666
667 pub fn gui_destroy(&self) {
668 self.lock().gui_destroy();
669 }
670
671 pub fn gui_on_main_thread(&self) {
672 self.lock().gui_on_main_thread();
673 }
674
675 pub fn gui_on_timer(&self, timer_id: u32) {
676 self.lock().gui_on_timer(timer_id);
677 }
678
679 pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
680 self.lock().gui_check_resize()
681 }
682}
683
684pub fn find_host_binary() -> Option<PathBuf> {
686 let exe_dir = std::env::current_exe()
687 .ok()
688 .and_then(|p| p.parent().map(PathBuf::from));
689
690 if let Some(ref dir) = exe_dir {
692 let candidate = dir.join("maolan-engine-plugin-host");
693 if candidate.exists() {
694 return Some(candidate);
695 }
696 }
697
698 if let Ok(manifest) = std::env::var("CARGO_MANIFEST_DIR") {
700 let engine_root = Path::new(&manifest);
701 for profile in ["debug", "release"] {
702 let candidate = engine_root
703 .join("target")
704 .join(profile)
705 .join("maolan-engine-plugin-host");
706 if candidate.exists() {
707 return Some(candidate);
708 }
709 }
710 }
711
712 if let Ok(manifest) = std::env::var("CARGO_MANIFEST_DIR") {
714 let engine_root = Path::new(&manifest);
715 for profile in ["debug", "release"] {
716 let candidate = engine_root
717 .parent()
718 .unwrap_or(Path::new(""))
719 .join("daw")
720 .join("target")
721 .join(profile)
722 .join("maolan-engine-plugin-host");
723 if candidate.exists() {
724 return Some(candidate);
725 }
726 }
727 }
728
729 if let Ok(path_var) = std::env::var("PATH") {
731 for dir in path_var.split(':') {
732 let candidate = Path::new(dir).join("maolan-engine-plugin-host");
733 if candidate.exists() {
734 return Some(candidate);
735 }
736 }
737 }
738
739 None
740}
741
742fn spawn_host(
743 host_binary: &PathBuf,
744 plugin_path: &str,
745 instance_id: &str,
746 sample_rate: f64,
747 buffer_size: usize,
748 num_inputs: usize,
749 num_outputs: usize,
750) -> Result<(Child, ShmMapping, EventPair, String), String> {
751 let pid = std::process::id();
752 let shm_name = format!("/maolan-{pid}-{instance_id}");
753
754 let mapping = ShmMapping::create(&shm_name, SHM_SIZE)?;
755 unsafe {
756 init_shm_layout(mapping.as_ptr(), mapping.size());
757 }
758
759 let mut events = EventPair::new().map_err(|e| format!("failed to create pipes: {e}"))?;
760
761 let mut cmd = Command::new(host_binary);
762 cmd.arg("vst3")
763 .arg(plugin_path)
764 .arg(&shm_name)
765 .arg(instance_id)
766 .arg(events.host_read_fd().to_string())
767 .arg(events.host_write_fd().to_string())
768 .arg(sample_rate.to_string())
769 .arg(buffer_size.to_string())
770 .arg(num_inputs.to_string())
771 .arg(num_outputs.to_string())
772 .stdin(Stdio::null())
773 .stdout(Stdio::null())
774 .stderr(Stdio::inherit());
775
776 let child = cmd
777 .spawn()
778 .map_err(|e| format!("failed to spawn VST3 host: {e}"))?;
779
780 events.close_daw_unused();
781
782 Ok((child, mapping, events, shm_name))
783}
784
785fn wait_for_ready(header: &ShmHeader, timeout: Duration) -> bool {
786 let start = Instant::now();
787 while start.elapsed() < timeout {
788 if header.ready.load(Ordering::Acquire) != 0 {
789 return true;
790 }
791 std::thread::sleep(Duration::from_millis(10));
792 }
793 false
794}
795
796fn serialize_vst3_state(scratch: *mut u8, state: &Vst3PluginState) -> Result<usize, String> {
798 let max_len = maolan_plugin_protocol::protocol::SCRATCH_SIZE;
799 let mut offset = 0usize;
800
801 let plugin_id_bytes = state.plugin_id.as_bytes();
802 if offset + 4 > max_len {
803 return Err("scratch overflow".to_string());
804 }
805 unsafe {
806 std::ptr::write_unaligned(
807 scratch.add(offset) as *mut u32,
808 plugin_id_bytes.len() as u32,
809 );
810 }
811 offset += 4;
812 if offset + plugin_id_bytes.len() > max_len {
813 return Err("scratch overflow".to_string());
814 }
815 unsafe {
816 std::ptr::copy_nonoverlapping(
817 plugin_id_bytes.as_ptr(),
818 scratch.add(offset),
819 plugin_id_bytes.len(),
820 );
821 }
822 offset += plugin_id_bytes.len();
823
824 if offset + 4 > max_len {
825 return Err("scratch overflow".to_string());
826 }
827 unsafe {
828 std::ptr::write_unaligned(
829 scratch.add(offset) as *mut u32,
830 state.component_state.len() as u32,
831 );
832 }
833 offset += 4;
834 if offset + state.component_state.len() > max_len {
835 return Err("scratch overflow".to_string());
836 }
837 unsafe {
838 std::ptr::copy_nonoverlapping(
839 state.component_state.as_ptr(),
840 scratch.add(offset),
841 state.component_state.len(),
842 );
843 }
844 offset += state.component_state.len();
845
846 if offset + 4 > max_len {
847 return Err("scratch overflow".to_string());
848 }
849 unsafe {
850 std::ptr::write_unaligned(
851 scratch.add(offset) as *mut u32,
852 state.controller_state.len() as u32,
853 );
854 }
855 offset += 4;
856 if offset + state.controller_state.len() > max_len {
857 return Err("scratch overflow".to_string());
858 }
859 unsafe {
860 std::ptr::copy_nonoverlapping(
861 state.controller_state.as_ptr(),
862 scratch.add(offset),
863 state.controller_state.len(),
864 );
865 }
866 offset += state.controller_state.len();
867
868 Ok(offset)
869}
870
871fn deserialize_vst3_state(scratch: *const u8, size: usize) -> Result<Vst3PluginState, String> {
873 if size < 12 {
874 return Err("scratch too small for VST3 state".to_string());
875 }
876 let mut offset = 0usize;
877
878 let plugin_id_len =
879 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
880 offset += 4;
881 if offset + plugin_id_len > size {
882 return Err("scratch underflow".to_string());
883 }
884 let mut plugin_id_bytes = vec![0u8; plugin_id_len];
885 unsafe {
886 std::ptr::copy_nonoverlapping(
887 scratch.add(offset),
888 plugin_id_bytes.as_mut_ptr(),
889 plugin_id_len,
890 );
891 }
892 offset += plugin_id_len;
893 let plugin_id = String::from_utf8(plugin_id_bytes).map_err(|e| e.to_string())?;
894
895 let component_state_len =
896 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
897 offset += 4;
898 if offset + component_state_len > size {
899 return Err("scratch underflow".to_string());
900 }
901 let mut component_state = vec![0u8; component_state_len];
902 unsafe {
903 std::ptr::copy_nonoverlapping(
904 scratch.add(offset),
905 component_state.as_mut_ptr(),
906 component_state_len,
907 );
908 }
909 offset += component_state_len;
910
911 let controller_state_len =
912 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
913 offset += 4;
914 if offset + controller_state_len > size {
915 return Err("scratch underflow".to_string());
916 }
917 let mut controller_state = vec![0u8; controller_state_len];
918 unsafe {
919 std::ptr::copy_nonoverlapping(
920 scratch.add(offset),
921 controller_state.as_mut_ptr(),
922 controller_state_len,
923 );
924 }
925
926 Ok(Vst3PluginState {
927 plugin_id,
928 component_state,
929 controller_state,
930 })
931}
932
933#[cfg(test)]
934mod tests {
935 use super::*;
936
937 fn find_host_binary() -> PathBuf {
938 super::find_host_binary()
939 .expect("maolan-engine-plugin-host binary should be built for tests")
940 }
941
942 #[test]
943 fn find_host_binary_locates_binary() {
944 let host_bin = find_host_binary();
945 assert!(
946 host_bin.exists(),
947 "plugin-host binary should exist at {}",
948 host_bin.display()
949 );
950 }
951
952 #[test]
953 fn vst3_state_serialization_roundtrip() {
954 let state = Vst3PluginState {
955 plugin_id: "test.plugin.vst3".to_string(),
956 component_state: vec![1, 2, 3, 4, 5],
957 controller_state: vec![10, 20, 30],
958 };
959 let mut scratch = vec![0u8; SCRATCH_SIZE];
960 let size =
961 serialize_vst3_state(scratch.as_mut_ptr(), &state).expect("serialize should succeed");
962 assert!(size > 0);
963 assert!(size < SCRATCH_SIZE);
964
965 let decoded =
966 deserialize_vst3_state(scratch.as_ptr(), size).expect("deserialize should succeed");
967 assert_eq!(decoded.plugin_id, state.plugin_id);
968 assert_eq!(decoded.component_state, state.component_state);
969 assert_eq!(decoded.controller_state, state.controller_state);
970 }
971
972 #[test]
973 fn vst3_processor_crash_bypass() {
974 let host_bin = find_host_binary();
975
976 let processor = Vst3Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
977 .expect("should create VST3 processor for crash test");
978
979 processor.setup_audio_ports();
980
981 {
983 let buf = processor.audio_inputs()[0].buffer.lock();
984 buf.fill(1.0);
985 *processor.audio_inputs()[0].finished.lock() = true;
986 }
987
988 processor.process_with_audio_io(256);
990
991 let out_buf = processor.audio_outputs()[0].buffer.lock();
993 assert!(
994 out_buf.iter().all(|&s| s == 1.0),
995 "after crash, output should be bypass copy of input"
996 );
997 }
998}