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