Skip to main content

maolan_engine/plugins/
vst3_proc.rs

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}