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