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