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