Skip to main content

maolan_engine/plugins/vst3/
processor.rs

1use super::interfaces::PluginInstance;
2use super::midi::{EventBuffer, ParameterChanges};
3use super::port::{BusInfo, ParameterInfo};
4use super::state::{MemoryStream, Vst3PluginState, ibstream_ptr};
5use crate::audio::io::AudioIO;
6use crate::midi::io::MidiEvent;
7use std::fmt;
8use std::path::Path;
9use std::sync::{Arc, Mutex};
10use vst3::ComWrapper;
11use vst3::Steinberg::Vst::ProcessModes_::kRealtime;
12use vst3::Steinberg::Vst::SymbolicSampleSizes_::kSample32;
13
14pub struct Vst3Processor {
15    // Plugin identity
16    path: String,
17    name: String,
18    plugin_id: String,
19
20    // COM interfaces
21    instance: PluginInstance,
22    // Keep factory/module alive for the plugin instance lifetime.
23    _factory: super::interfaces::PluginFactory,
24
25    // Audio I/O (reuse existing AudioIO)
26    audio_inputs: Vec<Arc<AudioIO>>,
27    audio_outputs: Vec<Arc<AudioIO>>,
28    midi_input_ports: usize,
29    midi_output_ports: usize,
30    main_audio_inputs: usize,
31    main_audio_outputs: usize,
32    input_buses: Vec<BusInfo>,
33    output_buses: Vec<BusInfo>,
34
35    // Parameters
36    parameters: Vec<ParameterInfo>,
37    scalar_values: Arc<Mutex<Vec<f32>>>,
38    previous_values: Arc<Mutex<Vec<f32>>>,
39    max_samples_per_block: usize,
40    processing_started: bool,
41}
42
43impl fmt::Debug for Vst3Processor {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.debug_struct("Vst3Processor")
46            .field("path", &self.path)
47            .field("name", &self.name)
48            .field("plugin_id", &self.plugin_id)
49            .field("audio_inputs", &self.audio_inputs.len())
50            .field("audio_outputs", &self.audio_outputs.len())
51            .field("midi_input_ports", &self.midi_input_ports)
52            .field("midi_output_ports", &self.midi_output_ports)
53            .field("main_audio_inputs", &self.main_audio_inputs)
54            .field("main_audio_outputs", &self.main_audio_outputs)
55            .field("input_buses", &self.input_buses)
56            .field("output_buses", &self.output_buses)
57            .field("parameters", &self.parameters)
58            .field("max_samples_per_block", &self.max_samples_per_block)
59            .field("processing_started", &self.processing_started)
60            .finish()
61    }
62}
63
64impl Vst3Processor {
65    /// Create a new VST3 processor with explicit sample rate
66    pub fn new_with_sample_rate(
67        sample_rate: f64,
68        buffer_size: usize,
69        plugin_path: &str,
70        audio_inputs: usize,
71        audio_outputs: usize,
72    ) -> Result<Self, String> {
73        let path_buf = Path::new(plugin_path);
74        let name = path_buf
75            .file_stem()
76            .or_else(|| path_buf.file_name())
77            .and_then(|s| s.to_str())
78            .unwrap_or("Unknown VST3")
79            .to_string();
80
81        // Load plugin factory and create instance
82        let factory = super::interfaces::PluginFactory::from_module(path_buf)?;
83
84        let class_count = factory.count_classes();
85        if class_count == 0 {
86            return Err("No plugin classes found".to_string());
87        }
88
89        // Get first class and create instance
90        let class_info = factory
91            .get_class_info(0)
92            .ok_or("Failed to get class info")?;
93
94        let mut instance = factory.create_instance(&class_info.cid)?;
95
96        // Initialize the plugin
97        instance.initialize(&factory)?;
98
99        let (plugin_input_buses, plugin_output_buses) = instance.audio_bus_counts();
100        let (plugin_main_in_channels, plugin_main_out_channels) =
101            instance.main_audio_channel_counts();
102        let (midi_input_ports, midi_output_ports) = instance.event_bus_counts();
103
104        let requested_inputs = if plugin_input_buses > 0 {
105            audio_inputs
106                .max(1)
107                .min(plugin_main_in_channels.max(1))
108                .min(i32::MAX as usize)
109        } else {
110            0
111        };
112        let requested_outputs = if plugin_output_buses > 0 {
113            audio_outputs
114                .max(1)
115                .min(plugin_main_out_channels.max(1))
116                .min(i32::MAX as usize)
117        } else {
118            0
119        };
120        // Query buses (for now, use the provided counts)
121        let input_buses = if plugin_input_buses > 0 {
122            vec![BusInfo {
123                index: 0,
124                name: "Input".to_string(),
125                channel_count: requested_inputs.max(1),
126                is_active: true,
127            }]
128        } else {
129            vec![]
130        };
131
132        let output_buses = if plugin_output_buses > 0 {
133            vec![BusInfo {
134                index: 0,
135                name: "Output".to_string(),
136                channel_count: requested_outputs.max(1),
137                is_active: true,
138            }]
139        } else {
140            vec![]
141        };
142
143        // Create AudioIO for each channel
144        let mut audio_input_ios = Vec::new();
145        for _ in 0..requested_inputs {
146            audio_input_ios.push(Arc::new(AudioIO::new(buffer_size)));
147        }
148
149        let mut audio_output_ios = Vec::new();
150        for _ in 0..requested_outputs {
151            audio_output_ios.push(Arc::new(AudioIO::new(buffer_size)));
152        }
153
154        // Configure processing before activation so plugins see the final
155        // host sample rate/block size when they enter active state.
156        instance.setup_processing(
157            sample_rate,
158            buffer_size as i32,
159            requested_inputs as i32,
160            requested_outputs as i32,
161        )?;
162        instance.set_active(true)?;
163
164        let processing_started = false;
165
166        // Temporary workaround: querying IEditController parameters crashes on
167        // some Linux VST3 plugins (e.g. lsp-plugins), so keep parameter list empty.
168        let parameters = Vec::new();
169        let scalar_values = Arc::new(Mutex::new(Vec::new()));
170        let previous_values = Arc::new(Mutex::new(Vec::new()));
171        let plugin_id = format!("{:02X?}", class_info.cid);
172
173        Ok(Self {
174            path: plugin_path.to_string(),
175            name,
176            plugin_id,
177            instance,
178            _factory: factory,
179            audio_inputs: audio_input_ios,
180            audio_outputs: audio_output_ios,
181            midi_input_ports,
182            midi_output_ports,
183            main_audio_inputs: requested_inputs,
184            main_audio_outputs: requested_outputs,
185            input_buses,
186            output_buses,
187            parameters,
188            scalar_values,
189            previous_values,
190            max_samples_per_block: buffer_size,
191            processing_started,
192        })
193    }
194
195    pub fn path(&self) -> &str {
196        &self.path
197    }
198
199    pub fn name(&self) -> &str {
200        &self.name
201    }
202
203    pub fn plugin_id(&self) -> &str {
204        &self.plugin_id
205    }
206
207    pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
208        &self.audio_inputs
209    }
210
211    pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
212        &self.audio_outputs
213    }
214
215    pub fn main_audio_input_count(&self) -> usize {
216        self.main_audio_inputs
217    }
218
219    pub fn main_audio_output_count(&self) -> usize {
220        self.main_audio_outputs
221    }
222
223    pub fn midi_input_count(&self) -> usize {
224        self.midi_input_ports
225    }
226
227    pub fn midi_output_count(&self) -> usize {
228        self.midi_output_ports
229    }
230
231    pub fn setup_audio_ports(&self) {
232        for port in &self.audio_inputs {
233            port.setup();
234        }
235        for port in &self.audio_outputs {
236            port.setup();
237        }
238    }
239
240    pub fn process_with_audio_io(&self, frames: usize) {
241        // Process all input AudioIO ports
242        for input in &self.audio_inputs {
243            input.process();
244        }
245
246        // Get the audio processor
247        let processor = match &self.instance.audio_processor {
248            Some(proc) => proc,
249            None => {
250                self.process_silence();
251                return;
252            }
253        };
254
255        // Call real VST3 processing (no MIDI)
256        if self.process_vst3(processor, frames, &[]).is_err() {
257            self.process_silence();
258        }
259    }
260
261    /// Process audio with MIDI events
262    pub fn process_with_midi(&self, frames: usize, input_events: &[MidiEvent]) -> Vec<MidiEvent> {
263        // Process all input AudioIO ports
264        for input in &self.audio_inputs {
265            input.process();
266        }
267
268        // Get the audio processor
269        let processor = match &self.instance.audio_processor {
270            Some(proc) => proc,
271            None => {
272                self.process_silence();
273                return Vec::new();
274            }
275        };
276
277        // Call real VST3 processing with MIDI
278        match self.process_vst3(processor, frames, input_events) {
279            Ok(output_buffer) => {
280                // Convert output events back to MIDI
281                output_buffer.to_midi_events()
282            }
283            Err(_) => {
284                self.process_silence();
285                Vec::new()
286            }
287        }
288    }
289
290    fn process_vst3(
291        &self,
292        processor: &vst3::ComPtr<vst3::Steinberg::Vst::IAudioProcessor>,
293        frames: usize,
294        input_events: &[MidiEvent],
295    ) -> Result<EventBuffer, String> {
296        use vst3::Steinberg::Vst::IAudioProcessorTrait;
297        use vst3::Steinberg::Vst::*;
298
299        // Keep buffer guards alive while the plugin reads/writes through raw pointers.
300        let input_guards: Vec<_> = self
301            .audio_inputs
302            .iter()
303            .map(|io| io.buffer.lock())
304            .collect();
305        let output_guards: Vec<_> = self
306            .audio_outputs
307            .iter()
308            .map(|io| io.buffer.lock())
309            .collect();
310
311        let mut input_channel_ptrs: Vec<*mut f32> = input_guards
312            .iter()
313            .map(|buf| buf.as_ptr() as *mut f32)
314            .collect();
315        let mut output_channel_ptrs: Vec<*mut f32> = output_guards
316            .iter()
317            .map(|buf| buf.as_ptr() as *mut f32)
318            .collect();
319
320        let max_input_frames = input_guards
321            .iter()
322            .map(|buf| buf.len())
323            .min()
324            .unwrap_or(frames);
325        let max_output_frames = output_guards
326            .iter()
327            .map(|buf| buf.len())
328            .min()
329            .unwrap_or(frames);
330        let num_frames = frames.min(max_input_frames).min(max_output_frames);
331        if num_frames == 0 {
332            return Ok(EventBuffer::new());
333        }
334
335        let mut input_buses = Vec::new();
336        if !self.input_buses.is_empty() && !input_channel_ptrs.is_empty() {
337            input_buses.push(AudioBusBuffers {
338                numChannels: input_channel_ptrs.len() as i32,
339                silenceFlags: 0,
340                __field0: AudioBusBuffers__type0 {
341                    channelBuffers32: input_channel_ptrs.as_mut_ptr(),
342                },
343            });
344        }
345
346        let mut output_buses = Vec::new();
347        if !self.output_buses.is_empty() && !output_channel_ptrs.is_empty() {
348            output_buses.push(AudioBusBuffers {
349                numChannels: output_channel_ptrs.len() as i32,
350                silenceFlags: 0,
351                __field0: AudioBusBuffers__type0 {
352                    channelBuffers32: output_channel_ptrs.as_mut_ptr(),
353                },
354            });
355        }
356
357        // Create ProcessData
358        let mut process_context: ProcessContext = unsafe { std::mem::zeroed() };
359        let input_event_list = if self.midi_input_ports > 0 {
360            Some(ComWrapper::new(EventBuffer::from_midi_events(
361                input_events,
362                0,
363            )))
364        } else {
365            None
366        };
367        let midi_mapping = self
368            .instance
369            .edit_controller
370            .as_ref()
371            .and_then(|controller| controller.cast::<IMidiMapping>());
372        let input_parameter_changes = midi_mapping.as_ref().and_then(|mapping| {
373            ParameterChanges::from_midi_events(input_events, mapping, 0).map(ComWrapper::new)
374        });
375        let output_event_list = if self.midi_output_ports > 0 {
376            Some(ComWrapper::new(EventBuffer::new()))
377        } else {
378            None
379        };
380        let mut process_data = ProcessData {
381            processMode: kRealtime as i32,
382            symbolicSampleSize: kSample32 as i32,
383            numSamples: num_frames as i32,
384            numInputs: input_buses.len() as i32,
385            numOutputs: output_buses.len() as i32,
386            inputs: if input_buses.is_empty() {
387                std::ptr::null_mut()
388            } else {
389                input_buses.as_mut_ptr()
390            },
391            outputs: if output_buses.is_empty() {
392                std::ptr::null_mut()
393            } else {
394                output_buses.as_mut_ptr()
395            },
396            inputParameterChanges: input_parameter_changes
397                .as_ref()
398                .map(ParameterChanges::changes_ptr)
399                .unwrap_or(std::ptr::null_mut()),
400            outputParameterChanges: std::ptr::null_mut(),
401            inputEvents: input_event_list
402                .as_ref()
403                .map(EventBuffer::event_list_ptr)
404                .unwrap_or(std::ptr::null_mut()),
405            outputEvents: output_event_list
406                .as_ref()
407                .map(EventBuffer::event_list_ptr)
408                .unwrap_or(std::ptr::null_mut()),
409            processContext: &mut process_context,
410        };
411
412        // Call VST3 process
413        let result = unsafe { processor.process(&mut process_data) };
414
415        if result != vst3::Steinberg::kResultOk {
416            return Err(format!("VST3 process failed with result: {}", result));
417        }
418
419        // Mark outputs as finished
420        for output in &self.audio_outputs {
421            *output.finished.lock() = true;
422        }
423
424        Ok(output_event_list
425            .as_ref()
426            .map(|events| EventBuffer::from_midi_events(&events.to_midi_events(), 0))
427            .unwrap_or_default())
428    }
429
430    fn process_silence(&self) {
431        for output in &self.audio_outputs {
432            let out_buf = output.buffer.lock();
433            out_buf.fill(0.0);
434            *output.finished.lock() = true;
435        }
436    }
437
438    pub fn parameters(&self) -> &[ParameterInfo] {
439        &self.parameters
440    }
441
442    pub fn get_parameter_value(&self, param_id: u32) -> Option<f32> {
443        let idx = self.parameters.iter().position(|p| p.id == param_id)?;
444        Some(self.scalar_values.lock().unwrap()[idx])
445    }
446
447    pub fn set_parameter_value(
448        &mut self,
449        param_id: u32,
450        normalized_value: f32,
451    ) -> Result<(), String> {
452        let idx = self
453            .parameters
454            .iter()
455            .position(|p| p.id == param_id)
456            .ok_or("Parameter not found")?;
457
458        self.scalar_values.lock().unwrap()[idx] = normalized_value;
459
460        // Update controller if available
461        if let Some(controller) = &self.instance.edit_controller {
462            use vst3::Steinberg::Vst::IEditControllerTrait;
463            unsafe {
464                controller.setParamNormalized(param_id, normalized_value as f64);
465            }
466        }
467
468        Ok(())
469    }
470
471    /// Snapshot the current plugin state for saving
472    pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
473        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
474
475        let instance = &self.instance;
476
477        // Save component state
478        let comp_stream = vst3::ComWrapper::new(MemoryStream::new());
479        unsafe {
480            let result = instance
481                .component
482                .getState(ibstream_ptr(&comp_stream) as *mut _);
483            if result != vst3::Steinberg::kResultOk {
484                return Err("Failed to get component state".to_string());
485            }
486        }
487
488        // Save controller state (if available)
489        let ctrl_stream = vst3::ComWrapper::new(MemoryStream::new());
490        if let Some(controller) = &instance.edit_controller {
491            unsafe {
492                controller.getState(ibstream_ptr(&ctrl_stream) as *mut _);
493            }
494        }
495
496        Ok(Vst3PluginState {
497            plugin_id: self.plugin_id.clone(),
498            component_state: comp_stream.bytes(),
499            controller_state: ctrl_stream.bytes(),
500        })
501    }
502
503    /// Restore plugin state from a snapshot
504    pub fn restore_state(&mut self, state: &Vst3PluginState) -> Result<(), String> {
505        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
506
507        if state.plugin_id != self.plugin_id {
508            return Err(format!(
509                "Plugin ID mismatch: expected '{}', got '{}'",
510                self.plugin_id, state.plugin_id
511            ));
512        }
513
514        let instance = &self.instance;
515
516        // Restore component state
517        if !state.component_state.is_empty() {
518            let comp_stream =
519                vst3::ComWrapper::new(MemoryStream::from_bytes(&state.component_state));
520            unsafe {
521                let result = instance
522                    .component
523                    .setState(ibstream_ptr(&comp_stream) as *mut _);
524                if result != vst3::Steinberg::kResultOk {
525                    return Err("Failed to set component state".to_string());
526                }
527            }
528        }
529
530        // Restore controller state (if available)
531        if !state.controller_state.is_empty()
532            && let Some(controller) = &instance.edit_controller
533        {
534            let ctrl_stream =
535                vst3::ComWrapper::new(MemoryStream::from_bytes(&state.controller_state));
536            unsafe {
537                controller.setState(ibstream_ptr(&ctrl_stream) as *mut _);
538            }
539
540            // Re-sync parameter values after restoring state
541            for (idx, param) in self.parameters.iter().enumerate() {
542                let value = unsafe { controller.getParamNormalized(param.id) };
543                self.scalar_values.lock().unwrap()[idx] = value as f32;
544                self.previous_values.lock().unwrap()[idx] = value as f32;
545            }
546        }
547
548        Ok(())
549    }
550}
551
552impl Drop for Vst3Processor {
553    fn drop(&mut self) {
554        if self.processing_started {
555            self.instance.stop_processing();
556        }
557        let _ = self.instance.set_active(false);
558        let _ = self.instance.terminate();
559    }
560}
561
562// Standalone function for listing plugins (backward compatibility)
563pub fn list_plugins() -> Vec<super::host::Vst3PluginInfo> {
564    super::host::list_plugins()
565}