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