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