1use crate::audio::io::AudioIO;
2use crate::midi::io::MidiEvent;
3use crate::mutex::UnsafeMutex;
4use crate::plugins::ipc;
5use crate::plugins::types::ParameterInfo;
6use crate::plugins::types::Vst3PluginState;
7use maolan_plugin_protocol::events::EventPair;
8use maolan_plugin_protocol::protocol::*;
9use maolan_plugin_protocol::ringbuf::RingBuffer;
10use maolan_plugin_protocol::shm::ShmMapping;
11use std::collections::HashMap;
12use std::path::{Path, PathBuf};
13use std::process::{Child, ChildStderr};
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::{Arc, atomic::AtomicU32};
16use std::time::{Duration, Instant};
17
18pub struct Vst3Processor {
19 path: String,
20 name: String,
21 audio_inputs: Vec<Arc<AudioIO>>,
22 audio_outputs: Vec<Arc<AudioIO>>,
23 main_audio_inputs: usize,
24 main_audio_outputs: usize,
25 param_infos: Vec<ParameterInfo>,
26 param_values: UnsafeMutex<HashMap<u32, f64>>,
27 bypassed: Arc<AtomicBool>,
28
29 child: UnsafeMutex<Option<Child>>,
30 stderr: UnsafeMutex<Option<ChildStderr>>,
31 mapping: Option<ShmMapping>,
32 events: Option<EventPair>,
33 shm_name: String,
34
35 crash_count: AtomicU32,
36 last_process_time: UnsafeMutex<Instant>,
37}
38
39pub type SharedVst3Processor = Arc<UnsafeMutex<Vst3Processor>>;
40
41impl Vst3Processor {
42 pub fn new(
43 sample_rate: f64,
44 buffer_size: usize,
45 plugin_path: &str,
46 input_count: usize,
47 output_count: usize,
48 host_binary: PathBuf,
49 ) -> Result<Self, String> {
50 let audio_inputs = (0..input_count.max(1))
51 .map(|_| Arc::new(AudioIO::new(buffer_size)))
52 .collect::<Vec<_>>();
53 let audio_outputs = (0..output_count.max(1))
54 .map(|_| Arc::new(AudioIO::new(buffer_size)))
55 .collect::<Vec<_>>();
56
57 let instance_id = ipc::unique_instance_id("vst3");
58 let num_inputs = input_count.max(1);
59 let num_outputs = output_count.max(1);
60 let (mut child, mapping, events, shm_name, stderr) = ipc::spawn_host(ipc::HostSpawnArgs {
61 host_binary: &host_binary,
62 format: "vst3",
63 plugin_spec: plugin_path,
64 instance_id: &instance_id,
65 extra_args: &[
66 &sample_rate.to_string(),
67 &buffer_size.to_string(),
68 &num_inputs.to_string(),
69 &num_outputs.to_string(),
70 ],
71 })?;
72
73 let header = unsafe { header_ref(mapping.as_ptr()) };
74 if !ipc::wait_for_ready(header, Duration::from_secs(10)) {
75 let _ = child.kill();
76 return Err("VST3 host did not signal ready".to_string());
77 }
78
79 let name = unsafe {
80 maolan_plugin_protocol::protocol::read_plugin_name_from_scratch(mapping.as_ptr())
81 .unwrap_or_else(|| {
82 Path::new(plugin_path)
83 .file_stem()
84 .and_then(|s| s.to_str())
85 .unwrap_or("VST3")
86 .to_string()
87 })
88 };
89
90 let param_infos = Vec::new();
91
92 Ok(Self {
93 path: plugin_path.to_string(),
94 name,
95 audio_inputs,
96 audio_outputs,
97 main_audio_inputs: input_count.max(1),
98 main_audio_outputs: output_count.max(1),
99 param_infos,
100 param_values: UnsafeMutex::new(HashMap::new()),
101 bypassed: Arc::new(AtomicBool::new(false)),
102 child: UnsafeMutex::new(Some(child)),
103 stderr: UnsafeMutex::new(stderr),
104 mapping: Some(mapping),
105 events: Some(events),
106 shm_name,
107 crash_count: AtomicU32::new(0),
108 last_process_time: UnsafeMutex::new(Instant::now()),
109 })
110 }
111
112 pub fn setup_audio_ports(&self) {
113 for port in &self.audio_inputs {
114 port.setup();
115 }
116 for port in &self.audio_outputs {
117 port.setup();
118 }
119 }
120
121 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
122 &self.audio_inputs
123 }
124
125 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
126 &self.audio_outputs
127 }
128
129 pub fn main_audio_input_count(&self) -> usize {
130 self.main_audio_inputs
131 }
132
133 pub fn main_audio_output_count(&self) -> usize {
134 self.main_audio_outputs
135 }
136
137 pub fn midi_input_count(&self) -> usize {
138 0
139 }
140
141 pub fn midi_output_count(&self) -> usize {
142 0
143 }
144
145 pub fn set_bypassed(&self, bypassed: bool) {
146 self.bypassed.store(bypassed, Ordering::Relaxed);
147 }
148
149 pub fn is_bypassed(&self) -> bool {
150 self.bypassed.load(Ordering::Relaxed)
151 }
152
153 pub fn parameter_infos(&self) -> Vec<ParameterInfo> {
154 self.param_infos.clone()
155 }
156
157 pub fn parameter_values(&self) -> HashMap<u32, f64> {
158 self.param_values.lock().clone()
159 }
160
161 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
162 self.set_parameter_at(param_id, value, 0)
163 }
164
165 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
166 self.param_values.lock().insert(param_id, value);
167 if let Some(ref mapping) = self.mapping {
168 let ring = unsafe {
169 let buf = param_ring_ptr(mapping.as_ptr());
170 let (w, r) = param_indices(mapping.as_ptr());
171 RingBuffer::new(buf, w, r, RING_CAPACITY)
172 };
173 let ev = ParameterEvent {
174 param_index: param_id,
175 value: value as f32,
176 sample_offset: 0,
177 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
178 };
179 if !ring.push(ev) {}
180 }
181 Ok(())
182 }
183
184 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
185 Ok(())
186 }
187
188 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
189 Ok(())
190 }
191
192 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
193 false
194 }
195
196 pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
197 let (mapping, events) = match (&self.mapping, &self.events) {
198 (Some(m), Some(e)) => (m, e),
199 _ => return Err("VST3 processor not initialized".to_string()),
200 };
201 let ptr = mapping.as_ptr();
202 let header = unsafe { header_mut(ptr) };
203
204 header.request_type.store(1, Ordering::Release);
205 header.request_status.store(0, Ordering::Release);
206 if let Err(e) = events.signal_host() {
207 header.request_type.store(0, Ordering::Release);
208 return Err(format!("Failed to signal host for state save: {}", e));
209 }
210
211 if let Err(e) = events.wait_host(Duration::from_secs(5)) {
212 header.request_type.store(0, Ordering::Release);
213 return Err(format!("Host did not respond to state save: {}", e));
214 }
215
216 let status = header.request_status.load(Ordering::Acquire);
217 let size = header.scratch_size.load(Ordering::Acquire) as usize;
218 if status != 1 {
219 header.request_type.store(0, Ordering::Release);
220 return Err("State save failed in host".to_string());
221 }
222
223 let scratch = unsafe { scratch_ptr(ptr) };
224 let state = deserialize_vst3_state(scratch, size)?;
225 header.request_type.store(0, Ordering::Release);
226 Ok(state)
227 }
228
229 pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
230 let (mapping, events) = match (&self.mapping, &self.events) {
231 (Some(m), Some(e)) => (m, e),
232 _ => return Err("VST3 processor not initialized".to_string()),
233 };
234 let ptr = mapping.as_ptr();
235 let header = unsafe { header_mut(ptr) };
236
237 let scratch = unsafe { scratch_ptr(ptr) };
238 let size = serialize_vst3_state(scratch, state)?;
239 header.scratch_size.store(size as u32, Ordering::Release);
240
241 header.request_type.store(2, Ordering::Release);
242 header.request_status.store(0, Ordering::Release);
243 if let Err(e) = events.signal_host() {
244 header.request_type.store(0, Ordering::Release);
245 return Err(format!("Failed to signal host for state restore: {}", e));
246 }
247
248 if let Err(e) = events.wait_host(Duration::from_secs(5)) {
249 header.request_type.store(0, Ordering::Release);
250 return Err(format!("Host did not respond to state restore: {}", e));
251 }
252
253 let status = header.request_status.load(Ordering::Acquire);
254 header.request_type.store(0, Ordering::Release);
255 if status != 1 {
256 return Err("State restore failed in host".to_string());
257 }
258 Ok(())
259 }
260
261 pub fn process_with_audio_io(&self, frames: usize) {
262 let _ = self.process_with_midi(frames, &[]);
263 }
264
265 pub fn process_with_midi(&self, frames: usize, _midi_in: &[MidiEvent]) -> Vec<MidiEvent> {
266 if self.bypassed.load(Ordering::Relaxed) {
267 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
268 return Vec::new();
269 }
270
271 {
272 let child = self.child.lock();
273 if let Some(ref mut c) = child.as_mut() {
274 match c.try_wait() {
275 Ok(Some(status)) if !status.success() => {
276 self.crash_count.fetch_add(1, Ordering::Relaxed);
277 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
278 return Vec::new();
279 }
280 Ok(None) => {}
281 Ok(Some(_status)) => {}
282 Err(_) => {}
283 }
284 }
285 }
286
287 let (mapping, events) = match (&self.mapping, &self.events) {
288 (Some(m), Some(e)) => (m, e),
289 _ => {
290 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
291 return Vec::new();
292 }
293 };
294
295 let ptr = mapping.as_ptr();
296 let num_in = self.audio_inputs.len();
297 let num_out = self.audio_outputs.len();
298 unsafe {
299 ipc::configure_shm_header(ptr, frames, num_in, num_out);
300
301 let t = transport_mut(ptr);
302 t.playhead_sample = 0;
303 t.tempo = 120.0;
304 t.numerator = 4;
305 t.denominator = 4;
306 t.flags = 1;
307
308 ipc::copy_inputs_to_shm(&self.audio_inputs, ptr, frames);
309 }
310
311 if events.signal_host().is_err() {
312 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
313 return Vec::new();
314 }
315
316 let timeout = Duration::from_millis(100);
317 match events.wait_host(timeout) {
318 Ok(()) => {}
319 Err(_) => {
320 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
321 return Vec::new();
322 }
323 }
324
325 unsafe {
326 ipc::copy_outputs_from_shm(&self.audio_outputs, ptr, frames);
327 }
328
329 *self.last_process_time.lock() = Instant::now();
330 Vec::new()
331 }
332
333 pub fn path(&self) -> &str {
334 &self.path
335 }
336
337 pub fn name(&self) -> &str {
338 &self.name
339 }
340
341 pub fn take_stderr(&self) -> Option<ChildStderr> {
342 self.stderr.lock().take()
343 }
344
345 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
346 Ok(())
347 }
348
349 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
350 Ok(())
351 }
352
353 pub fn run_host_callbacks_main_thread(&self) {}
354
355 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
356 Ok(false)
357 }
358
359 pub fn ui_begin_session(&self) {}
360 pub fn ui_end_session(&self) {}
361 pub fn ui_should_close(&self) -> bool {
362 false
363 }
364 pub fn ui_take_due_timers(&self) -> Vec<u32> {
365 Vec::new()
366 }
367 pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
368 Vec::new()
369 }
370 pub fn ui_take_state_update(&self) -> Option<Vst3PluginState> {
371 None
372 }
373
374 pub fn gui_info(&self) -> Result<crate::plugins::types::Vst3GuiInfo, String> {
375 Err("GUI not yet supported for VST3 plugins".to_string())
376 }
377
378 pub fn gui_create(&self, _platform_type: &str) -> Result<(), String> {
379 Err("GUI not yet supported for VST3 plugins".to_string())
380 }
381
382 pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
383 Err("GUI not yet supported for VST3 plugins".to_string())
384 }
385
386 pub fn gui_set_parent(&self, _window: usize, _platform_type: &str) -> Result<(), String> {
387 Err("GUI not yet supported for VST3 plugins".to_string())
388 }
389
390 pub fn gui_on_size(&self, _width: i32, _height: i32) -> Result<(), String> {
391 Err("GUI not yet supported for VST3 plugins".to_string())
392 }
393
394 pub fn gui_show(&self) -> Result<(), String> {
395 if let Some(ref mapping) = self.mapping
396 && let Some(ref events) = self.events
397 {
398 let header = unsafe { header_mut(mapping.as_ptr()) };
399 header.request_type.store(3, Ordering::Release);
400 let _ = events.signal_host();
401 return Ok(());
402 }
403 Err("No active host to show GUI".to_string())
404 }
405
406 pub fn gui_hide(&self) {
407 if let Some(ref mapping) = self.mapping
408 && let Some(ref events) = self.events
409 {
410 let header = unsafe { header_mut(mapping.as_ptr()) };
411 header.request_type.store(4, Ordering::Release);
412 let _ = events.signal_host();
413 }
414 }
415
416 pub fn gui_destroy(&self) {}
417
418 pub fn gui_on_main_thread(&self) {}
419
420 pub fn gui_on_timer(&self, _timer_id: u32) {}
421
422 pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
423 None
424 }
425
426 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
427 let mut result = Vec::new();
428 if let Some(ref mapping) = self.mapping {
429 let ring = unsafe {
430 let buf = echo_ring_ptr(mapping.as_ptr());
431 let (w, r) = echo_indices(mapping.as_ptr());
432 RingBuffer::new(buf, w, r, RING_CAPACITY)
433 };
434 while let Some(ev) = ring.pop() {
435 result.push(ev);
436 }
437 }
438 result
439 }
440}
441
442impl Drop for Vst3Processor {
443 fn drop(&mut self) {
444 ipc::drop_host(&self.mapping, &self.events, &self.child, &self.shm_name);
445 }
446}
447
448impl UnsafeMutex<Vst3Processor> {
449 pub fn setup_audio_ports(&self) {
450 self.lock().setup_audio_ports();
451 }
452
453 pub fn process_with_midi(&self, frames: usize, midi_events: &[MidiEvent]) -> Vec<MidiEvent> {
454 self.lock().process_with_midi(frames, midi_events)
455 }
456
457 pub fn set_bypassed(&self, bypassed: bool) {
458 self.lock().set_bypassed(bypassed);
459 }
460
461 pub fn is_bypassed(&self) -> bool {
462 self.lock().is_bypassed()
463 }
464
465 pub fn parameter_infos(&self) -> Vec<ParameterInfo> {
466 self.lock().parameter_infos()
467 }
468
469 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
470 self.lock().set_parameter(param_id, value)
471 }
472
473 pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
474 self.lock().set_parameter_at(param_id, value, frame)
475 }
476
477 pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
478 self.lock().begin_parameter_edit_at(param_id, frame)
479 }
480
481 pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
482 self.lock().end_parameter_edit_at(param_id, frame)
483 }
484
485 pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
486 self.lock().snapshot_state()
487 }
488
489 pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
490 self.lock().restore_state(state)
491 }
492
493 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
494 self.lock().audio_inputs()
495 }
496
497 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
498 self.lock().audio_outputs()
499 }
500
501 pub fn main_audio_input_count(&self) -> usize {
502 self.lock().main_audio_input_count()
503 }
504
505 pub fn main_audio_output_count(&self) -> usize {
506 self.lock().main_audio_output_count()
507 }
508
509 pub fn midi_input_count(&self) -> usize {
510 self.lock().midi_input_count()
511 }
512
513 pub fn midi_output_count(&self) -> usize {
514 self.lock().midi_output_count()
515 }
516
517 pub fn path(&self) -> String {
518 self.lock().path().to_string()
519 }
520
521 pub fn name(&self) -> String {
522 self.lock().name().to_string()
523 }
524
525 pub fn run_host_callbacks_main_thread(&self) {
526 self.lock().run_host_callbacks_main_thread();
527 }
528
529 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
530 self.lock().reconfigure_ports_if_needed()
531 }
532
533 pub fn ui_begin_session(&self) {
534 self.lock().ui_begin_session();
535 }
536
537 pub fn ui_end_session(&self) {
538 self.lock().ui_end_session();
539 }
540
541 pub fn ui_should_close(&self) -> bool {
542 self.lock().ui_should_close()
543 }
544
545 pub fn ui_take_due_timers(&self) -> Vec<u32> {
546 self.lock().ui_take_due_timers()
547 }
548
549 pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
550 self.lock().ui_take_param_updates()
551 }
552
553 pub fn ui_take_state_update(&self) -> Option<Vst3PluginState> {
554 self.lock().ui_take_state_update()
555 }
556
557 pub fn gui_info(&self) -> Result<crate::plugins::types::Vst3GuiInfo, String> {
558 self.lock().gui_info()
559 }
560
561 pub fn gui_create(&self, platform_type: &str) -> Result<(), String> {
562 self.lock().gui_create(platform_type)
563 }
564
565 pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
566 self.lock().gui_get_size()
567 }
568
569 pub fn gui_set_parent(&self, window: usize, platform_type: &str) -> Result<(), String> {
570 self.lock().gui_set_parent(window, platform_type)
571 }
572
573 pub fn gui_on_size(&self, width: i32, height: i32) -> Result<(), String> {
574 self.lock().gui_on_size(width, height)
575 }
576
577 pub fn gui_show(&self) -> Result<(), String> {
578 self.lock().gui_show()
579 }
580
581 pub fn gui_hide(&self) {
582 self.lock().gui_hide();
583 }
584
585 pub fn gui_destroy(&self) {
586 self.lock().gui_destroy();
587 }
588
589 pub fn gui_on_main_thread(&self) {
590 self.lock().gui_on_main_thread();
591 }
592
593 pub fn gui_on_timer(&self, timer_id: u32) {
594 self.lock().gui_on_timer(timer_id);
595 }
596
597 pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
598 self.lock().gui_check_resize()
599 }
600}
601
602fn serialize_vst3_state(scratch: *mut u8, state: &Vst3PluginState) -> Result<usize, String> {
603 let max_len = maolan_plugin_protocol::protocol::SCRATCH_SIZE;
604 let mut offset = 0usize;
605
606 let plugin_id_bytes = state.plugin_id.as_bytes();
607 if offset + 4 > max_len {
608 return Err("scratch overflow".to_string());
609 }
610 unsafe {
611 std::ptr::write_unaligned(
612 scratch.add(offset) as *mut u32,
613 plugin_id_bytes.len() as u32,
614 );
615 }
616 offset += 4;
617 if offset + plugin_id_bytes.len() > max_len {
618 return Err("scratch overflow".to_string());
619 }
620 unsafe {
621 std::ptr::copy_nonoverlapping(
622 plugin_id_bytes.as_ptr(),
623 scratch.add(offset),
624 plugin_id_bytes.len(),
625 );
626 }
627 offset += plugin_id_bytes.len();
628
629 if offset + 4 > max_len {
630 return Err("scratch overflow".to_string());
631 }
632 unsafe {
633 std::ptr::write_unaligned(
634 scratch.add(offset) as *mut u32,
635 state.component_state.len() as u32,
636 );
637 }
638 offset += 4;
639 if offset + state.component_state.len() > max_len {
640 return Err("scratch overflow".to_string());
641 }
642 unsafe {
643 std::ptr::copy_nonoverlapping(
644 state.component_state.as_ptr(),
645 scratch.add(offset),
646 state.component_state.len(),
647 );
648 }
649 offset += state.component_state.len();
650
651 if offset + 4 > max_len {
652 return Err("scratch overflow".to_string());
653 }
654 unsafe {
655 std::ptr::write_unaligned(
656 scratch.add(offset) as *mut u32,
657 state.controller_state.len() as u32,
658 );
659 }
660 offset += 4;
661 if offset + state.controller_state.len() > max_len {
662 return Err("scratch overflow".to_string());
663 }
664 unsafe {
665 std::ptr::copy_nonoverlapping(
666 state.controller_state.as_ptr(),
667 scratch.add(offset),
668 state.controller_state.len(),
669 );
670 }
671 offset += state.controller_state.len();
672
673 Ok(offset)
674}
675
676fn deserialize_vst3_state(scratch: *const u8, size: usize) -> Result<Vst3PluginState, String> {
677 if size < 12 {
678 return Err("scratch too small for VST3 state".to_string());
679 }
680 let mut offset = 0usize;
681
682 let plugin_id_len =
683 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
684 offset += 4;
685 if offset + plugin_id_len > size {
686 return Err("scratch underflow".to_string());
687 }
688 let mut plugin_id_bytes = vec![0u8; plugin_id_len];
689 unsafe {
690 std::ptr::copy_nonoverlapping(
691 scratch.add(offset),
692 plugin_id_bytes.as_mut_ptr(),
693 plugin_id_len,
694 );
695 }
696 offset += plugin_id_len;
697 let plugin_id = String::from_utf8(plugin_id_bytes).map_err(|e| e.to_string())?;
698
699 let component_state_len =
700 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
701 offset += 4;
702 if offset + component_state_len > size {
703 return Err("scratch underflow".to_string());
704 }
705 let mut component_state = vec![0u8; component_state_len];
706 unsafe {
707 std::ptr::copy_nonoverlapping(
708 scratch.add(offset),
709 component_state.as_mut_ptr(),
710 component_state_len,
711 );
712 }
713 offset += component_state_len;
714
715 let controller_state_len =
716 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
717 offset += 4;
718 if offset + controller_state_len > size {
719 return Err("scratch underflow".to_string());
720 }
721 let mut controller_state = vec![0u8; controller_state_len];
722 unsafe {
723 std::ptr::copy_nonoverlapping(
724 scratch.add(offset),
725 controller_state.as_mut_ptr(),
726 controller_state_len,
727 );
728 }
729
730 Ok(Vst3PluginState {
731 plugin_id,
732 component_state,
733 controller_state,
734 })
735}
736
737#[cfg(test)]
738mod tests {
739 use super::*;
740
741 fn find_host_binary() -> PathBuf {
742 ipc::find_plugin_host_binary().expect("maolan-plugin-host binary should be built for tests")
743 }
744
745 #[test]
746 fn find_host_binary_locates_binary() {
747 let host_bin = find_host_binary();
748 assert!(
749 host_bin.exists(),
750 "plugin-host binary should exist at {}",
751 host_bin.display()
752 );
753 }
754
755 #[test]
756 fn vst3_state_serialization_roundtrip() {
757 let state = Vst3PluginState {
758 plugin_id: "test.plugin.vst3".to_string(),
759 component_state: vec![1, 2, 3, 4, 5],
760 controller_state: vec![10, 20, 30],
761 };
762 let mut scratch = vec![0u8; SCRATCH_SIZE];
763 let size =
764 serialize_vst3_state(scratch.as_mut_ptr(), &state).expect("serialize should succeed");
765 assert!(size > 0);
766 assert!(size < SCRATCH_SIZE);
767
768 let decoded =
769 deserialize_vst3_state(scratch.as_ptr(), size).expect("deserialize should succeed");
770 assert_eq!(decoded.plugin_id, state.plugin_id);
771 assert_eq!(decoded.component_state, state.component_state);
772 assert_eq!(decoded.controller_state, state.controller_state);
773 }
774
775 #[test]
776 fn vst3_processor_crash_bypass() {
777 let host_bin = find_host_binary();
778
779 let processor = Vst3Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
780 .expect("should create VST3 processor for crash test");
781
782 processor.setup_audio_ports();
783
784 {
785 let buf = processor.audio_inputs()[0].buffer.lock();
786 buf.fill(1.0);
787 *processor.audio_inputs()[0].finished.lock() = true;
788 }
789
790 processor.process_with_audio_io(256);
791
792 let out_buf = processor.audio_outputs()[0].buffer.lock();
793 assert!(
794 out_buf.iter().all(|&s| s == 1.0),
795 "after crash, output should be bypass copy of input"
796 );
797 }
798}