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