Skip to main content

maolan_plugin_host/vst3/
processor.rs

1#![allow(clippy::unnecessary_cast)]
2
3use super::interfaces::{HostPlugFrame, PluginInstance, Vst3GuiInfo, protected_call};
4use super::midi::{EventBuffer, ParameterChanges};
5use super::port::{BusInfo, ParameterInfo};
6use super::state::{MemoryStream, Vst3PluginState, ibstream_ptr};
7use crate::util::AudioPort;
8use crate::util::MidiEvent;
9use std::ffi::{CString, c_void};
10use std::fmt;
11use std::path::Path;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::{Arc, Mutex};
14use vst3::ComPtr;
15use vst3::ComWrapper;
16use vst3::Steinberg::Vst::ProcessModes_::kRealtime;
17use vst3::Steinberg::Vst::SymbolicSampleSizes_::kSample32;
18use vst3::Steinberg::Vst::{IEditControllerTrait, ViewType};
19use vst3::Steinberg::{FIDString, IPlugFrame, IPlugView, IPlugViewTrait, ViewRect, kResultOk};
20
21#[derive(Clone, Copy, Debug, Default)]
22pub struct Vst3TransportInfo {
23    pub playhead_sample: i64,
24    pub playing: bool,
25    pub tempo: f64,
26    pub tsig_num: i32,
27    pub tsig_denom: i32,
28}
29
30pub struct Vst3Processor {
31    path: String,
32    name: String,
33    plugin_id: String,
34
35    instance: PluginInstance,
36
37    _factory: super::interfaces::PluginFactory,
38
39    audio_inputs: Vec<Arc<AudioPort>>,
40    audio_outputs: Vec<Arc<AudioPort>>,
41    midi_input_ports: usize,
42    midi_output_ports: usize,
43    main_audio_inputs: usize,
44    main_audio_outputs: usize,
45    input_buses: Vec<BusInfo>,
46    output_buses: Vec<BusInfo>,
47
48    parameters: Vec<ParameterInfo>,
49    scalar_values: Arc<Mutex<Vec<f32>>>,
50    previous_values: Arc<Mutex<Vec<f32>>>,
51
52    pending_param_changes: Arc<Mutex<Vec<(u32, f64, u32)>>>,
53    max_samples_per_block: usize,
54    processing_started: bool,
55    sample_rate: f64,
56    bypassed: AtomicBool,
57    transport_info: Mutex<Vst3TransportInfo>,
58
59    gui_session: Arc<Mutex<Vst3GuiSession>>,
60}
61
62struct Vst3GuiSession {
63    view: Option<ComPtr<IPlugView>>,
64    plug_frame: Option<ComWrapper<HostPlugFrame>>,
65    ui_should_close: bool,
66    platform_type: Option<String>,
67}
68
69impl fmt::Debug for Vst3Processor {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.debug_struct("Vst3Processor")
72            .field("path", &self.path)
73            .field("name", &self.name)
74            .field("plugin_id", &self.plugin_id)
75            .field("audio_inputs", &self.audio_inputs.len())
76            .field("audio_outputs", &self.audio_outputs.len())
77            .field("midi_input_ports", &self.midi_input_ports)
78            .field("midi_output_ports", &self.midi_output_ports)
79            .field("main_audio_inputs", &self.main_audio_inputs)
80            .field("main_audio_outputs", &self.main_audio_outputs)
81            .field("input_buses", &self.input_buses)
82            .field("output_buses", &self.output_buses)
83            .field("parameters", &self.parameters)
84            .field("max_samples_per_block", &self.max_samples_per_block)
85            .field("processing_started", &self.processing_started)
86            .finish()
87    }
88}
89
90impl Vst3Processor {
91    pub fn new_with_sample_rate(
92        sample_rate: f64,
93        buffer_size: usize,
94        plugin_path: &str,
95        audio_inputs: usize,
96        audio_outputs: usize,
97    ) -> Result<Self, String> {
98        let path_buf = Path::new(plugin_path);
99        let name = path_buf
100            .file_stem()
101            .or_else(|| path_buf.file_name())
102            .and_then(|s| s.to_str())
103            .unwrap_or("Unknown VST3")
104            .to_string();
105
106        let factory = super::interfaces::PluginFactory::from_module(path_buf)?;
107
108        let class_count = factory.count_classes();
109        if class_count == 0 {
110            return Err("No plugin classes found".to_string());
111        }
112
113        let mut class_info = None;
114        for i in 0..class_count {
115            if let Some(info) = factory.get_class_info(i)
116                && info.category.contains("Audio Module")
117            {
118                class_info = Some(info);
119                break;
120            }
121        }
122
123        let class_info = class_info
124            .or_else(|| factory.get_class_info(0))
125            .ok_or("Failed to get class info")?;
126
127        let mut instance = factory.create_instance(&class_info.cid)?;
128
129        instance.initialize(&factory)?;
130
131        let (plugin_input_buses, plugin_output_buses) = instance.audio_bus_counts();
132        let (plugin_main_in_channels, plugin_main_out_channels) =
133            instance.main_audio_channel_counts();
134        let (midi_input_ports, midi_output_ports) = instance.event_bus_counts();
135
136        let requested_inputs = if plugin_input_buses > 0 {
137            audio_inputs
138                .max(1)
139                .min(plugin_main_in_channels.max(1))
140                .min(i32::MAX as usize)
141        } else {
142            0
143        };
144        let requested_outputs = if plugin_output_buses > 0 {
145            audio_outputs
146                .max(1)
147                .min(plugin_main_out_channels.max(1))
148                .min(i32::MAX as usize)
149        } else {
150            0
151        };
152
153        let input_buses = if plugin_input_buses > 0 {
154            vec![BusInfo {
155                index: 0,
156                name: "Input".to_string(),
157                channel_count: requested_inputs.max(1),
158                is_active: true,
159            }]
160        } else {
161            vec![]
162        };
163
164        let output_buses = if plugin_output_buses > 0 {
165            vec![BusInfo {
166                index: 0,
167                name: "Output".to_string(),
168                channel_count: requested_outputs.max(1),
169                is_active: true,
170            }]
171        } else {
172            vec![]
173        };
174
175        let mut audio_input_ios = Vec::new();
176        for _ in 0..requested_inputs {
177            audio_input_ios.push(Arc::new(AudioPort::new(buffer_size)));
178        }
179
180        let mut audio_output_ios = Vec::new();
181        for _ in 0..requested_outputs {
182            audio_output_ios.push(Arc::new(AudioPort::new(buffer_size)));
183        }
184
185        instance.setup_processing(
186            sample_rate,
187            buffer_size as i32,
188            requested_inputs as i32,
189            requested_outputs as i32,
190        )?;
191        instance.set_active(true)?;
192
193        let processing_started = false;
194
195        let parameters = protected_call(|| instance.query_parameters()).unwrap_or_default();
196        let scalar_values = Arc::new(Mutex::new(
197            parameters.iter().map(|p| p.default_value as f32).collect(),
198        ));
199        let previous_values = Arc::new(Mutex::new(
200            parameters.iter().map(|p| p.default_value as f32).collect(),
201        ));
202        let pending_param_changes = Arc::new(Mutex::new(Vec::new()));
203        let plugin_id = format!("{:02X?}", class_info.cid);
204
205        let gui_session = Arc::new(Mutex::new(Vst3GuiSession {
206            view: None,
207            plug_frame: None,
208            ui_should_close: false,
209            platform_type: None,
210        }));
211
212        Ok(Self {
213            path: plugin_path.to_string(),
214            name,
215            plugin_id,
216            instance,
217            _factory: factory,
218            audio_inputs: audio_input_ios,
219            audio_outputs: audio_output_ios,
220            midi_input_ports,
221            midi_output_ports,
222            main_audio_inputs: requested_inputs,
223            main_audio_outputs: requested_outputs,
224            input_buses,
225            output_buses,
226            parameters,
227            scalar_values,
228            previous_values,
229            pending_param_changes,
230            max_samples_per_block: buffer_size,
231            processing_started,
232            sample_rate,
233            bypassed: AtomicBool::new(false),
234            transport_info: Mutex::new(Vst3TransportInfo::default()),
235            gui_session,
236        })
237    }
238
239    pub fn path(&self) -> &str {
240        &self.path
241    }
242
243    pub fn name(&self) -> &str {
244        &self.name
245    }
246
247    pub fn plugin_id(&self) -> &str {
248        &self.plugin_id
249    }
250
251    pub fn audio_inputs(&self) -> &[Arc<AudioPort>] {
252        &self.audio_inputs
253    }
254
255    pub fn audio_outputs(&self) -> &[Arc<AudioPort>] {
256        &self.audio_outputs
257    }
258
259    pub fn main_audio_input_count(&self) -> usize {
260        self.main_audio_inputs
261    }
262
263    pub fn main_audio_output_count(&self) -> usize {
264        self.main_audio_outputs
265    }
266
267    pub fn midi_input_count(&self) -> usize {
268        self.midi_input_ports
269    }
270
271    pub fn midi_output_count(&self) -> usize {
272        self.midi_output_ports
273    }
274
275    pub fn setup_audio_ports(&self) {
276        for port in &self.audio_inputs {
277            port.setup();
278        }
279        for port in &self.audio_outputs {
280            port.setup();
281        }
282    }
283
284    pub fn set_bypassed(&self, bypassed: bool) {
285        self.bypassed.store(bypassed, Ordering::Relaxed);
286    }
287
288    pub fn is_bypassed(&self) -> bool {
289        self.bypassed.load(Ordering::Relaxed)
290    }
291
292    fn bypass_copy_inputs_to_outputs(&self) {
293        for (input, output) in self.audio_inputs.iter().zip(self.audio_outputs.iter()) {
294            let src = input.buffer.lock();
295            let dst = output.buffer.lock();
296            dst.fill(0.0);
297            for (d, s) in dst.iter_mut().zip(src.iter()) {
298                *d = *s;
299            }
300            *output.finished.lock() = true;
301        }
302        for output in self.audio_outputs.iter().skip(self.audio_inputs.len()) {
303            output.buffer.lock().fill(0.0);
304            *output.finished.lock() = true;
305        }
306    }
307
308    pub fn process_with_audio_io(&self, frames: usize) {
309        for input in &self.audio_inputs {
310            input.process();
311        }
312        if self.bypassed.load(Ordering::Relaxed) {
313            self.bypass_copy_inputs_to_outputs();
314            return;
315        }
316
317        let processor = match &self.instance.audio_processor {
318            Some(proc) => proc,
319            None => {
320                self.process_silence();
321                return;
322            }
323        };
324
325        if self.process_vst3(processor, frames, &[]).is_err() {
326            self.process_silence();
327        }
328    }
329
330    #[allow(clippy::unnecessary_cast)]
331    pub fn process_with_midi(&self, frames: usize, input_events: &[MidiEvent]) -> Vec<MidiEvent> {
332        for input in &self.audio_inputs {
333            input.process();
334        }
335        if self.bypassed.load(Ordering::Relaxed) {
336            self.bypass_copy_inputs_to_outputs();
337            return Vec::new();
338        }
339
340        let processor = match &self.instance.audio_processor {
341            Some(proc) => proc,
342            None => {
343                self.process_silence();
344                return Vec::new();
345            }
346        };
347
348        match self.process_vst3(processor, frames, input_events) {
349            Ok(output_buffer) => output_buffer.to_midi_events(),
350            Err(_) => {
351                self.process_silence();
352                Vec::new()
353            }
354        }
355    }
356
357    fn process_vst3(
358        &self,
359        processor: &vst3::ComPtr<vst3::Steinberg::Vst::IAudioProcessor>,
360        frames: usize,
361        input_events: &[MidiEvent],
362    ) -> Result<EventBuffer, String> {
363        use vst3::Steinberg::Vst::IAudioProcessorTrait;
364        use vst3::Steinberg::Vst::*;
365
366        let input_guards: Vec<_> = self
367            .audio_inputs
368            .iter()
369            .map(|io| io.buffer.lock())
370            .collect();
371        let output_guards: Vec<_> = self
372            .audio_outputs
373            .iter()
374            .map(|io| io.buffer.lock())
375            .collect();
376
377        let mut input_channel_ptrs: Vec<*mut f32> = input_guards
378            .iter()
379            .map(|buf| buf.as_ptr() as *mut f32)
380            .collect();
381        let mut output_channel_ptrs: Vec<*mut f32> = output_guards
382            .iter()
383            .map(|buf| buf.as_ptr() as *mut f32)
384            .collect();
385
386        let max_input_frames = input_guards
387            .iter()
388            .map(|buf| buf.len())
389            .min()
390            .unwrap_or(frames);
391        let max_output_frames = output_guards
392            .iter()
393            .map(|buf| buf.len())
394            .min()
395            .unwrap_or(frames);
396        let num_frames = frames.min(max_input_frames).min(max_output_frames);
397        if num_frames == 0 {
398            return Ok(EventBuffer::new());
399        }
400
401        let mut input_buses = Vec::new();
402        if !self.input_buses.is_empty() && !input_channel_ptrs.is_empty() {
403            input_buses.push(AudioBusBuffers {
404                numChannels: input_channel_ptrs.len() as i32,
405                silenceFlags: 0,
406                __field0: AudioBusBuffers__type0 {
407                    channelBuffers32: input_channel_ptrs.as_mut_ptr(),
408                },
409            });
410        }
411
412        let mut output_buses = Vec::new();
413        if !self.output_buses.is_empty() && !output_channel_ptrs.is_empty() {
414            output_buses.push(AudioBusBuffers {
415                numChannels: output_channel_ptrs.len() as i32,
416                silenceFlags: 0,
417                __field0: AudioBusBuffers__type0 {
418                    channelBuffers32: output_channel_ptrs.as_mut_ptr(),
419                },
420            });
421        }
422
423        let transport = self
424            .transport_info
425            .lock()
426            .unwrap_or_else(|e| e.into_inner());
427        let mut process_context: ProcessContext = unsafe { std::mem::zeroed() };
428        process_context.sampleRate = self.sample_rate;
429        process_context.tempo = transport.tempo;
430        process_context.timeSigNumerator = transport.tsig_num;
431        process_context.timeSigDenominator = transport.tsig_denom;
432        process_context.projectTimeSamples = transport.playhead_sample;
433        #[allow(clippy::unnecessary_cast)]
434        {
435            let mut state = ProcessContext_::StatesAndFlags_::kTempoValid
436                | ProcessContext_::StatesAndFlags_::kTimeSigValid
437                | ProcessContext_::StatesAndFlags_::kContTimeValid
438                | ProcessContext_::StatesAndFlags_::kSystemTimeValid;
439            if transport.playing {
440                state |= ProcessContext_::StatesAndFlags_::kPlaying;
441            }
442            process_context.state = state as u32;
443        }
444        let input_event_list = if self.midi_input_ports > 0 {
445            Some(ComWrapper::new(EventBuffer::from_midi_events(
446                input_events,
447                0,
448            )))
449        } else {
450            None
451        };
452        let midi_mapping = self
453            .instance
454            .edit_controller
455            .as_ref()
456            .and_then(|controller| controller.cast::<IMidiMapping>());
457        let param_changes = ParameterChanges::new();
458        {
459            let mut pending = self.pending_param_changes.lock().unwrap();
460            for (param_id, value, offset) in pending.drain(..) {
461                param_changes.add_point(param_id, offset as i32, value);
462            }
463        }
464        if let Some(mapping) = midi_mapping
465            && let Some(midi_changes) =
466                ParameterChanges::from_midi_events(input_events, &mapping, 0)
467        {
468            for queue in midi_changes.queues_ref() {
469                for (offset, value) in queue.points_ref() {
470                    param_changes.add_point(queue.parameter_id(), *offset, *value);
471                }
472            }
473        }
474        let input_parameter_changes = if !param_changes.queues_ref().is_empty() {
475            Some(ComWrapper::new(param_changes))
476        } else {
477            None
478        };
479        let output_event_list = if self.midi_output_ports > 0 {
480            Some(ComWrapper::new(EventBuffer::new()))
481        } else {
482            None
483        };
484        let mut process_data = ProcessData {
485            processMode: kRealtime as i32,
486            symbolicSampleSize: kSample32 as i32,
487            numSamples: num_frames as i32,
488            numInputs: input_buses.len() as i32,
489            numOutputs: output_buses.len() as i32,
490            inputs: if input_buses.is_empty() {
491                std::ptr::null_mut()
492            } else {
493                input_buses.as_mut_ptr()
494            },
495            outputs: if output_buses.is_empty() {
496                std::ptr::null_mut()
497            } else {
498                output_buses.as_mut_ptr()
499            },
500            inputParameterChanges: input_parameter_changes
501                .as_ref()
502                .map(ParameterChanges::changes_ptr)
503                .unwrap_or(std::ptr::null_mut()),
504            outputParameterChanges: std::ptr::null_mut(),
505            inputEvents: input_event_list
506                .as_ref()
507                .map(EventBuffer::event_list_ptr)
508                .unwrap_or(std::ptr::null_mut()),
509            outputEvents: output_event_list
510                .as_ref()
511                .map(EventBuffer::event_list_ptr)
512                .unwrap_or(std::ptr::null_mut()),
513            processContext: &mut process_context,
514        };
515
516        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
517            processor.process(&mut process_data)
518        }));
519
520        match result {
521            Ok(vst3::Steinberg::kResultOk) => {}
522            Ok(err_code) => {
523                return Err(format!("VST3 process failed with result: {}", err_code));
524            }
525            Err(_) => {
526                return Err("VST3 process panicked".to_string());
527            }
528        }
529
530        for output in &self.audio_outputs {
531            *output.finished.lock() = true;
532        }
533
534        Ok(output_event_list
535            .as_ref()
536            .map(|events| EventBuffer::from_midi_events(&events.to_midi_events(), 0))
537            .unwrap_or_default())
538    }
539
540    fn process_silence(&self) {
541        for output in &self.audio_outputs {
542            let out_buf = output.buffer.lock();
543            out_buf.fill(0.0);
544            *output.finished.lock() = true;
545        }
546    }
547
548    pub fn parameters(&self) -> &[ParameterInfo] {
549        &self.parameters
550    }
551
552    pub fn get_parameter_value(&self, param_id: u32) -> Option<f32> {
553        let idx = self.parameters.iter().position(|p| p.id == param_id)?;
554        Some(self.scalar_values.lock().unwrap()[idx])
555    }
556
557    pub fn set_transport_info(&self, info: Vst3TransportInfo) {
558        if let Ok(mut t) = self.transport_info.lock() {
559            *t = info;
560        }
561    }
562
563    pub fn set_parameter_value(&self, param_id: u32, normalized_value: f32) -> Result<(), String> {
564        self.set_parameter_value_at(param_id, normalized_value, 0)
565    }
566
567    pub fn set_parameter_value_at(
568        &self,
569        param_id: u32,
570        normalized_value: f32,
571        sample_offset: u32,
572    ) -> Result<(), String> {
573        let idx = self
574            .parameters
575            .iter()
576            .position(|p| p.id == param_id)
577            .ok_or("Parameter not found")?;
578
579        self.scalar_values.lock().unwrap()[idx] = normalized_value;
580
581        if let Some(controller) = &self.instance.edit_controller {
582            use vst3::Steinberg::Vst::IEditControllerTrait;
583            unsafe {
584                controller.setParamNormalized(param_id, normalized_value as f64);
585            }
586        }
587
588        self.pending_param_changes.lock().unwrap().push((
589            param_id,
590            normalized_value as f64,
591            sample_offset,
592        ));
593
594        Ok(())
595    }
596
597    pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
598        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
599
600        let instance = &self.instance;
601
602        let comp_stream = vst3::ComWrapper::new(MemoryStream::new());
603        unsafe {
604            let result = instance
605                .component
606                .getState(ibstream_ptr(&comp_stream) as *mut _);
607            if result != vst3::Steinberg::kResultOk {
608                return Err("Failed to get component state".to_string());
609            }
610        }
611
612        let ctrl_stream = vst3::ComWrapper::new(MemoryStream::new());
613        if let Some(controller) = &instance.edit_controller {
614            unsafe {
615                controller.getState(ibstream_ptr(&ctrl_stream) as *mut _);
616            }
617        }
618
619        Ok(Vst3PluginState {
620            plugin_id: self.plugin_id.clone(),
621            component_state: comp_stream.bytes(),
622            controller_state: ctrl_stream.bytes(),
623        })
624    }
625
626    pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
627        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
628
629        if state.plugin_id != self.plugin_id {
630            return Err(format!(
631                "Plugin ID mismatch: expected '{}', got '{}'",
632                self.plugin_id, state.plugin_id
633            ));
634        }
635
636        let instance = &self.instance;
637
638        if !state.component_state.is_empty() {
639            let comp_stream =
640                vst3::ComWrapper::new(MemoryStream::from_bytes(&state.component_state));
641            unsafe {
642                let result = instance
643                    .component
644                    .setState(ibstream_ptr(&comp_stream) as *mut _);
645                if result != vst3::Steinberg::kResultOk {
646                    return Err("Failed to set component state".to_string());
647                }
648            }
649        }
650
651        if !state.controller_state.is_empty()
652            && let Some(controller) = &instance.edit_controller
653        {
654            let ctrl_stream =
655                vst3::ComWrapper::new(MemoryStream::from_bytes(&state.controller_state));
656            unsafe {
657                controller.setState(ibstream_ptr(&ctrl_stream) as *mut _);
658            }
659
660            for (idx, param) in self.parameters.iter().enumerate() {
661                let value = unsafe { controller.getParamNormalized(param.id) };
662                self.scalar_values.lock().unwrap()[idx] = value as f32;
663                self.previous_values.lock().unwrap()[idx] = value as f32;
664            }
665        }
666
667        Ok(())
668    }
669
670    pub fn gui_info(&self) -> Result<Vst3GuiInfo, String> {
671        let controller = self
672            .instance
673            .edit_controller
674            .as_ref()
675            .ok_or("No edit controller available")?;
676        let view = unsafe { controller.createView(ViewType::kEditor) };
677        if view.is_null() {
678            return Ok(Vst3GuiInfo {
679                has_gui: false,
680                size: None,
681            });
682        }
683
684        unsafe {
685            let _ = ComPtr::<IPlugView>::from_raw(view);
686        }
687        Ok(Vst3GuiInfo {
688            has_gui: true,
689            size: None,
690        })
691    }
692
693    pub fn gui_create(&self, platform_type: &str) -> Result<(), String> {
694        let mut session = self.gui_session.lock().unwrap();
695        if session.view.is_some() {
696            return Ok(());
697        }
698        let controller = self
699            .instance
700            .edit_controller
701            .as_ref()
702            .ok_or("No edit controller available")?;
703        let view = unsafe { controller.createView(ViewType::kEditor) };
704        if view.is_null() {
705            return Err("Plugin does not provide an editor view".to_string());
706        }
707        let view =
708            unsafe { ComPtr::<IPlugView>::from_raw(view) }.ok_or("Failed to wrap IPlugView")?;
709
710        let platform_cstr =
711            CString::new(platform_type).map_err(|e| format!("Invalid platform type: {e}"))?;
712        let supported =
713            unsafe { view.isPlatformTypeSupported(platform_cstr.as_ptr() as FIDString) };
714        if supported != kResultOk {
715            return Err(format!("Platform type '{}' not supported", platform_type));
716        }
717
718        session.view = Some(view);
719        session.platform_type = Some(platform_type.to_string());
720        Ok(())
721    }
722
723    pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
724        let session = self.gui_session.lock().unwrap();
725        let view = session.view.as_ref().ok_or("No GUI view created")?;
726        let mut rect = ViewRect {
727            left: 0,
728            top: 0,
729            right: 0,
730            bottom: 0,
731        };
732        let result = unsafe { view.getSize(&mut rect) };
733        if result != kResultOk {
734            return Err("Failed to get GUI size".to_string());
735        }
736        Ok((rect.right - rect.left, rect.bottom - rect.top))
737    }
738
739    pub fn gui_set_parent(&self, window: usize, platform_type: &str) -> Result<(), String> {
740        let mut session = self.gui_session.lock().unwrap();
741        let view = session.view.as_ref().ok_or("No GUI view created")?;
742
743        let plug_frame = ComWrapper::new(HostPlugFrame::new());
744        if let Some(frame_ptr) = plug_frame.to_com_ptr::<IPlugFrame>() {
745            unsafe {
746                let _ = view.setFrame(frame_ptr.into_raw());
747            }
748        }
749
750        let platform_cstr =
751            CString::new(platform_type).map_err(|e| format!("Invalid platform type: {e}"))?;
752        let result =
753            unsafe { view.attached(window as *mut c_void, platform_cstr.as_ptr() as FIDString) };
754        if result != kResultOk {
755            return Err(format!("Failed to attach GUI view: {:#x}", result));
756        }
757
758        session.plug_frame = Some(plug_frame);
759        Ok(())
760    }
761
762    pub fn gui_on_size(&self, width: i32, height: i32) -> Result<(), String> {
763        let session = self.gui_session.lock().unwrap();
764        let view = session.view.as_ref().ok_or("No GUI view created")?;
765        let mut rect = ViewRect {
766            left: 0,
767            top: 0,
768            right: width,
769            bottom: height,
770        };
771        unsafe {
772            let _ = view.onSize(&mut rect);
773        }
774        Ok(())
775    }
776
777    pub fn gui_show(&self) -> Result<(), String> {
778        let session = self.gui_session.lock().unwrap();
779        let view = session.view.as_ref().ok_or("No GUI view created")?;
780        unsafe {
781            let _ = view.onFocus(1);
782        }
783        Ok(())
784    }
785
786    pub fn gui_hide(&self) {
787        if let Ok(session) = self.gui_session.lock()
788            && let Some(view) = session.view.as_ref()
789        {
790            unsafe {
791                let _ = view.onFocus(0);
792            }
793        }
794    }
795
796    pub fn gui_destroy(&self) {
797        if let Ok(mut session) = self.gui_session.lock() {
798            if let Some(view) = session.view.take() {
799                unsafe {
800                    let _ = view.setFrame(std::ptr::null_mut());
801                    let _ = view.removed();
802                }
803            }
804            session.plug_frame.take();
805            session.platform_type.take();
806        }
807    }
808
809    pub fn ui_begin_session(&self) {
810        if let Ok(mut session) = self.gui_session.lock() {
811            session.ui_should_close = false;
812        }
813    }
814
815    pub fn ui_end_session(&self) {
816        if let Ok(mut session) = self.gui_session.lock() {
817            session.ui_should_close = false;
818        }
819    }
820
821    pub fn ui_should_close(&self) -> bool {
822        if let Ok(session) = self.gui_session.lock() {
823            return session.ui_should_close;
824        }
825        false
826    }
827
828    pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
829        let mut changes = Vec::new();
830        if let Ok(mut param_changes) = self.instance.parameter_changes.lock() {
831            std::mem::swap(&mut changes, &mut *param_changes);
832        }
833        changes
834    }
835
836    pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
837        if let Ok(session) = self.gui_session.lock()
838            && let Some(ref frame) = session.plug_frame
839            && frame.resize_requested.swap(false, Ordering::Relaxed)
840            && let Ok(size) = frame.requested_size.lock()
841        {
842            return *size;
843        }
844        None
845    }
846
847    pub fn gui_on_main_thread(&self) {
848        super::interfaces::pump_host_run_loop();
849    }
850}
851
852impl Drop for Vst3Processor {
853    fn drop(&mut self) {
854        if self.processing_started {
855            self.instance.stop_processing();
856        }
857        let _ = self.instance.set_active(false);
858        let _ = self.instance.terminate();
859    }
860}
861
862pub fn list_plugins() -> Vec<super::host::Vst3PluginInfo> {
863    super::host::list_plugins()
864}