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