use std::cell::UnsafeCell;
use std::ffi::{c_char, c_void};
use std::marker::PhantomData;
use std::slice;
use log::warn;
use vst3::{Class, ComRef, Steinberg::Vst::*, Steinberg::*};
use beamer_core::{
AuxiliaryBuffers, Buffer, BusType as CoreBusType, ChordInfo,
FrameRate as CoreFrameRate, MidiBuffer, MidiEvent, MidiEventKind, NoteExpressionInt,
NoteExpressionText, NoteExpressionValue as CoreNoteExpressionValue, Parameters, Plugin,
ProcessContext as CoreProcessContext, ScaleInfo, SysEx, Transport,
MAX_BUSES, MAX_CHANNELS,
MAX_CHORD_NAME_SIZE, MAX_EXPRESSION_TEXT_SIZE, MAX_SCALE_NAME_SIZE, MAX_SYSEX_SIZE,
};
use crate::factory::ComponentFactory;
use crate::util::{copy_wstring, len_wstring};
use crate::wrapper::PluginConfig;
const K_NOTE_ON_EVENT: u16 = 0;
const K_NOTE_OFF_EVENT: u16 = 1;
const K_DATA_EVENT: u16 = 2;
const K_POLY_PRESSURE_EVENT: u16 = 3;
const K_NOTE_EXPRESSION_VALUE_EVENT: u16 = 4;
const K_NOTE_EXPRESSION_TEXT_EVENT: u16 = 5;
const K_CHORD_EVENT: u16 = 6;
const K_SCALE_EVENT: u16 = 7;
const K_NOTE_EXPRESSION_INT_VALUE_EVENT: u16 = 8;
const K_LEGACY_MIDI_CC_OUT_EVENT: u16 = 65535;
const LEGACY_CC_CHANNEL_PRESSURE: u8 = 128;
const LEGACY_CC_PITCH_BEND: u8 = 129;
const LEGACY_CC_PROGRAM_CHANGE: u8 = 130;
const DATA_TYPE_MIDI_SYSEX: u32 = 0;
struct SysExOutputPool {
buffers: Vec<Vec<u8>>,
lengths: Vec<usize>,
max_slots: usize,
max_buffer_size: usize,
next_slot: usize,
overflowed: bool,
#[cfg(feature = "sysex-heap-fallback")]
fallback: Vec<Vec<u8>>,
}
impl SysExOutputPool {
fn with_capacity(slots: usize, buffer_size: usize) -> Self {
let mut buffers = Vec::with_capacity(slots);
for _ in 0..slots {
let buf = vec![0u8; buffer_size];
buffers.push(buf);
}
let lengths = vec![0usize; slots];
Self {
buffers,
lengths,
max_slots: slots,
max_buffer_size: buffer_size,
next_slot: 0,
overflowed: false,
#[cfg(feature = "sysex-heap-fallback")]
fallback: Vec::new(),
}
}
#[inline]
fn clear(&mut self) {
self.next_slot = 0;
self.overflowed = false;
}
#[inline]
fn has_overflowed(&self) -> bool {
self.overflowed
}
#[inline]
fn capacity(&self) -> usize {
self.max_slots
}
#[cfg(feature = "sysex-heap-fallback")]
#[inline]
fn has_fallback(&self) -> bool {
!self.fallback.is_empty()
}
#[cfg(feature = "sysex-heap-fallback")]
#[inline]
fn take_fallback(&mut self) -> Vec<Vec<u8>> {
std::mem::take(&mut self.fallback)
}
fn allocate(&mut self, data: &[u8]) -> Option<(*const u8, usize)> {
if self.next_slot >= self.max_slots {
self.overflowed = true;
#[cfg(feature = "sysex-heap-fallback")]
{
let copy_len = data.len().min(self.max_buffer_size);
self.fallback.push(data[..copy_len].to_vec());
}
return None;
}
let slot = self.next_slot;
self.next_slot += 1;
let copy_len = data.len().min(self.max_buffer_size);
self.buffers[slot][..copy_len].copy_from_slice(&data[..copy_len]);
self.lengths[slot] = copy_len;
Some((self.buffers[slot].as_ptr(), copy_len))
}
}
macro_rules! valid_if {
($state:expr, $flag:expr, $value:expr) => {
if $state & $flag != 0 {
Some($value)
} else {
None
}
};
}
unsafe fn extract_transport(ctx_ptr: *const ProcessContext) -> Transport {
if ctx_ptr.is_null() {
return Transport::default();
}
let ctx = &*ctx_ptr;
let state = ctx.state;
const K_PLAYING: u32 = 1 << 1;
const K_CYCLE_ACTIVE: u32 = 1 << 2;
const K_RECORDING: u32 = 1 << 3;
const K_SYSTEM_TIME_VALID: u32 = 1 << 8;
const K_PROJECT_TIME_MUSIC_VALID: u32 = 1 << 9;
const K_TEMPO_VALID: u32 = 1 << 10;
const K_BAR_POSITION_VALID: u32 = 1 << 11;
const K_CYCLE_VALID: u32 = 1 << 12;
const K_TIME_SIG_VALID: u32 = 1 << 13;
const K_SMPTE_VALID: u32 = 1 << 14;
const K_CLOCK_VALID: u32 = 1 << 15;
const K_CONT_TIME_VALID: u32 = 1 << 17;
Transport {
tempo: valid_if!(state, K_TEMPO_VALID, ctx.tempo),
time_sig_numerator: valid_if!(state, K_TIME_SIG_VALID, ctx.timeSigNumerator),
time_sig_denominator: valid_if!(state, K_TIME_SIG_VALID, ctx.timeSigDenominator),
project_time_samples: Some(ctx.projectTimeSamples),
project_time_beats: valid_if!(state, K_PROJECT_TIME_MUSIC_VALID, ctx.projectTimeMusic),
bar_position_beats: valid_if!(state, K_BAR_POSITION_VALID, ctx.barPositionMusic),
cycle_start_beats: valid_if!(state, K_CYCLE_VALID, ctx.cycleStartMusic),
cycle_end_beats: valid_if!(state, K_CYCLE_VALID, ctx.cycleEndMusic),
is_playing: state & K_PLAYING != 0,
is_recording: state & K_RECORDING != 0,
is_cycle_active: state & K_CYCLE_ACTIVE != 0,
system_time_ns: valid_if!(state, K_SYSTEM_TIME_VALID, ctx.systemTime),
continuous_time_samples: valid_if!(state, K_CONT_TIME_VALID, ctx.continousTimeSamples), samples_to_next_clock: valid_if!(state, K_CLOCK_VALID, ctx.samplesToNextClock),
smpte_offset_subframes: valid_if!(state, K_SMPTE_VALID, ctx.smpteOffsetSubframes),
frame_rate: if state & K_SMPTE_VALID != 0 {
let is_drop = ctx.frameRate.flags & 1 != 0;
CoreFrameRate::from_raw(ctx.frameRate.framesPerSecond, is_drop)
} else {
None
},
}
}
struct ConversionBuffers {
main_input_f32: Vec<Vec<f32>>,
main_output_f32: Vec<Vec<f32>>,
aux_input_f32: Vec<Vec<Vec<f32>>>,
aux_output_f32: Vec<Vec<Vec<f32>>>,
}
impl ConversionBuffers {
fn new() -> Self {
Self {
main_input_f32: Vec::new(),
main_output_f32: Vec::new(),
aux_input_f32: Vec::new(),
aux_output_f32: Vec::new(),
}
}
fn allocate<P: Plugin>(plugin: &P, max_block_size: usize) -> Self {
let num_input_buses = plugin.input_bus_count();
let num_output_buses = plugin.output_bus_count();
let main_in_channels = plugin.input_bus_info(0).map(|b| b.channel_count as usize).unwrap_or(0);
let main_out_channels = plugin.output_bus_info(0).map(|b| b.channel_count as usize).unwrap_or(0);
let main_input_f32: Vec<Vec<f32>> = (0..main_in_channels)
.map(|_| vec![0.0f32; max_block_size])
.collect();
let main_output_f32: Vec<Vec<f32>> = (0..main_out_channels)
.map(|_| vec![0.0f32; max_block_size])
.collect();
let mut aux_input_f32 = Vec::new();
for bus_idx in 1..num_input_buses {
if let Some(info) = plugin.input_bus_info(bus_idx) {
let channels: Vec<Vec<f32>> = (0..info.channel_count)
.map(|_| vec![0.0f32; max_block_size])
.collect();
aux_input_f32.push(channels);
}
}
let mut aux_output_f32 = Vec::new();
for bus_idx in 1..num_output_buses {
if let Some(info) = plugin.output_bus_info(bus_idx) {
let channels: Vec<Vec<f32>> = (0..info.channel_count)
.map(|_| vec![0.0f32; max_block_size])
.collect();
aux_output_f32.push(channels);
}
}
Self {
main_input_f32,
main_output_f32,
aux_input_f32,
aux_output_f32,
}
}
}
fn validate_bus_limits<P: Plugin>(plugin: &P) -> Result<(), String> {
let num_inputs = plugin.input_bus_count();
let num_outputs = plugin.output_bus_count();
if num_inputs > MAX_BUSES {
return Err(format!(
"Plugin declares {} input buses, but MAX_BUSES is {}",
num_inputs, MAX_BUSES
));
}
if num_outputs > MAX_BUSES {
return Err(format!(
"Plugin declares {} output buses, but MAX_BUSES is {}",
num_outputs, MAX_BUSES
));
}
for i in 0..num_inputs {
if let Some(info) = plugin.input_bus_info(i) {
let channels = info.channel_count as usize;
if channels > MAX_CHANNELS {
return Err(format!(
"Input bus {} declares {} channels, but MAX_CHANNELS is {}",
i, channels, MAX_CHANNELS
));
}
}
}
for i in 0..num_outputs {
if let Some(info) = plugin.output_bus_info(i) {
let channels = info.channel_count as usize;
if channels > MAX_CHANNELS {
return Err(format!(
"Output bus {} declares {} channels, but MAX_CHANNELS is {}",
i, channels, MAX_CHANNELS
));
}
}
}
Ok(())
}
fn validate_speaker_arrangement(arrangement: SpeakerArrangement) -> Result<(), String> {
let channel_count = arrangement.count_ones() as usize;
if channel_count > MAX_CHANNELS {
return Err(format!(
"Speaker arrangement has {} channels, but MAX_CHANNELS is {}",
channel_count, MAX_CHANNELS
));
}
Ok(())
}
use beamer_core::sample::Sample;
struct ProcessBufferStorage<S: Sample> {
main_inputs: Vec<*const S>,
main_outputs: Vec<*mut S>,
aux_inputs: Vec<Vec<*const S>>,
aux_outputs: Vec<Vec<*mut S>>,
}
unsafe impl<S: Sample> Send for ProcessBufferStorage<S> {}
unsafe impl<S: Sample> Sync for ProcessBufferStorage<S> {}
impl<S: Sample> ProcessBufferStorage<S> {
fn new() -> Self {
Self {
main_inputs: Vec::new(),
main_outputs: Vec::new(),
aux_inputs: Vec::new(),
aux_outputs: Vec::new(),
}
}
fn allocate<P: Plugin>(plugin: &P) -> Self {
let num_input_buses = plugin.input_bus_count();
let num_output_buses = plugin.output_bus_count();
let main_in_channels = plugin
.input_bus_info(0)
.map(|b| b.channel_count as usize)
.unwrap_or(0);
let main_out_channels = plugin
.output_bus_info(0)
.map(|b| b.channel_count as usize)
.unwrap_or(0);
let main_inputs = Vec::with_capacity(main_in_channels);
let main_outputs = Vec::with_capacity(main_out_channels);
let aux_input_bus_count = num_input_buses.saturating_sub(1);
let aux_output_bus_count = num_output_buses.saturating_sub(1);
let mut aux_inputs = Vec::with_capacity(aux_input_bus_count);
for bus_idx in 1..num_input_buses {
if let Some(info) = plugin.input_bus_info(bus_idx) {
aux_inputs.push(Vec::with_capacity(info.channel_count as usize));
} else {
aux_inputs.push(Vec::new());
}
}
let mut aux_outputs = Vec::with_capacity(aux_output_bus_count);
for bus_idx in 1..num_output_buses {
if let Some(info) = plugin.output_bus_info(bus_idx) {
aux_outputs.push(Vec::with_capacity(info.channel_count as usize));
} else {
aux_outputs.push(Vec::new());
}
}
Self {
main_inputs,
main_outputs,
aux_inputs,
aux_outputs,
}
}
#[inline]
fn clear(&mut self) {
self.main_inputs.clear();
self.main_outputs.clear();
for bus in &mut self.aux_inputs {
bus.clear();
}
for bus in &mut self.aux_outputs {
bus.clear();
}
}
}
pub struct Vst3Processor<P: Plugin> {
plugin: UnsafeCell<P>,
config: &'static PluginConfig,
sample_rate: UnsafeCell<f64>,
max_block_size: UnsafeCell<usize>,
symbolic_sample_size: UnsafeCell<i32>,
midi_input: UnsafeCell<MidiBuffer>,
midi_output: UnsafeCell<MidiBuffer>,
sysex_output_pool: UnsafeCell<SysExOutputPool>,
conversion_buffers: UnsafeCell<ConversionBuffers>,
buffer_storage_f32: UnsafeCell<ProcessBufferStorage<f32>>,
buffer_storage_f64: UnsafeCell<ProcessBufferStorage<f64>>,
_marker: PhantomData<P>,
}
unsafe impl<P: Plugin> Send for Vst3Processor<P> {}
unsafe impl<P: Plugin> Sync for Vst3Processor<P> {}
impl<P: Plugin + 'static> Vst3Processor<P> {
pub fn new(config: &'static PluginConfig) -> Self {
Self {
plugin: UnsafeCell::new(P::create()),
config,
sample_rate: UnsafeCell::new(44100.0),
max_block_size: UnsafeCell::new(1024),
symbolic_sample_size: UnsafeCell::new(SymbolicSampleSizes_::kSample32 as i32),
midi_input: UnsafeCell::new(MidiBuffer::new()),
midi_output: UnsafeCell::new(MidiBuffer::new()),
sysex_output_pool: UnsafeCell::new(SysExOutputPool::with_capacity(
config.sysex_slots,
config.sysex_buffer_size,
)),
conversion_buffers: UnsafeCell::new(ConversionBuffers::new()),
buffer_storage_f32: UnsafeCell::new(ProcessBufferStorage::new()),
buffer_storage_f64: UnsafeCell::new(ProcessBufferStorage::new()),
_marker: PhantomData,
}
}
#[inline]
unsafe fn plugin(&self) -> &P {
&*self.plugin.get()
}
#[inline]
#[allow(clippy::mut_from_ref)]
unsafe fn plugin_mut(&self) -> &mut P {
&mut *self.plugin.get()
}
#[inline]
unsafe fn process_audio_f32(
&self,
process_data: &ProcessData,
num_samples: usize,
plugin: &mut P,
context: &CoreProcessContext,
) {
let storage = &mut *self.buffer_storage_f32.get();
storage.clear();
if process_data.numInputs > 0 && !process_data.inputs.is_null() {
let bus = &*process_data.inputs;
let num_channels = bus.numChannels as usize;
let max_channels = storage.main_inputs.capacity();
if num_channels > 0 && !bus.__field0.channelBuffers32.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers32, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.main_inputs.push(ptr);
}
}
}
}
if process_data.numOutputs > 0 && !process_data.outputs.is_null() {
let bus = &*process_data.outputs;
let num_channels = bus.numChannels as usize;
let max_channels = storage.main_outputs.capacity();
if num_channels > 0 && !bus.__field0.channelBuffers32.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers32, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.main_outputs.push(ptr);
}
}
}
}
if process_data.numInputs > 1 && !process_data.inputs.is_null() {
let input_buses =
slice::from_raw_parts(process_data.inputs, process_data.numInputs as usize);
for (aux_idx, bus) in input_buses[1..].iter().enumerate() {
if aux_idx < storage.aux_inputs.len() {
let num_channels = bus.numChannels as usize;
let max_channels = storage.aux_inputs[aux_idx].capacity();
if num_channels > 0 && !bus.__field0.channelBuffers32.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers32, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.aux_inputs[aux_idx].push(ptr);
}
}
}
}
}
}
if process_data.numOutputs > 1 && !process_data.outputs.is_null() {
let output_buses =
slice::from_raw_parts(process_data.outputs, process_data.numOutputs as usize);
for (aux_idx, bus) in output_buses[1..].iter().enumerate() {
if aux_idx < storage.aux_outputs.len() {
let num_channels = bus.numChannels as usize;
let max_channels = storage.aux_outputs[aux_idx].capacity();
if num_channels > 0 && !bus.__field0.channelBuffers32.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers32, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.aux_outputs[aux_idx].push(ptr);
}
}
}
}
}
}
let main_in_iter = storage
.main_inputs
.iter()
.map(|&ptr| slice::from_raw_parts(ptr, num_samples));
let main_out_iter = storage
.main_outputs
.iter()
.map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples));
let aux_in_iter = storage.aux_inputs.iter().map(|bus| {
bus.iter()
.map(|&ptr| slice::from_raw_parts(ptr, num_samples))
});
let aux_out_iter = storage.aux_outputs.iter().map(|bus| {
bus.iter()
.map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples))
});
let mut buffer = Buffer::new(main_in_iter, main_out_iter, num_samples);
let mut aux = AuxiliaryBuffers::new(aux_in_iter, aux_out_iter, num_samples);
plugin.process(&mut buffer, &mut aux, context);
}
#[inline]
unsafe fn process_audio_f64_native(
&self,
process_data: &ProcessData,
num_samples: usize,
plugin: &mut P,
context: &CoreProcessContext,
) {
let storage = &mut *self.buffer_storage_f64.get();
storage.clear();
if process_data.numInputs > 0 && !process_data.inputs.is_null() {
let bus = &*process_data.inputs;
let num_channels = bus.numChannels as usize;
let max_channels = storage.main_inputs.capacity();
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.main_inputs.push(ptr);
}
}
}
}
if process_data.numOutputs > 0 && !process_data.outputs.is_null() {
let bus = &*process_data.outputs;
let num_channels = bus.numChannels as usize;
let max_channels = storage.main_outputs.capacity();
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.main_outputs.push(ptr);
}
}
}
}
if process_data.numInputs > 1 && !process_data.inputs.is_null() {
let input_buses =
slice::from_raw_parts(process_data.inputs, process_data.numInputs as usize);
for (aux_idx, bus) in input_buses[1..].iter().enumerate() {
if aux_idx < storage.aux_inputs.len() {
let num_channels = bus.numChannels as usize;
let max_channels = storage.aux_inputs[aux_idx].capacity();
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.aux_inputs[aux_idx].push(ptr);
}
}
}
}
}
}
if process_data.numOutputs > 1 && !process_data.outputs.is_null() {
let output_buses =
slice::from_raw_parts(process_data.outputs, process_data.numOutputs as usize);
for (aux_idx, bus) in output_buses[1..].iter().enumerate() {
if aux_idx < storage.aux_outputs.len() {
let num_channels = bus.numChannels as usize;
let max_channels = storage.aux_outputs[aux_idx].capacity();
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs =
slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for &ptr in channel_ptrs.iter().take(max_channels) {
if !ptr.is_null() {
storage.aux_outputs[aux_idx].push(ptr);
}
}
}
}
}
}
let main_in_iter = storage
.main_inputs
.iter()
.map(|&ptr| slice::from_raw_parts(ptr, num_samples));
let main_out_iter = storage
.main_outputs
.iter()
.map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples));
let aux_in_iter = storage.aux_inputs.iter().map(|bus| {
bus.iter()
.map(|&ptr| slice::from_raw_parts(ptr, num_samples))
});
let aux_out_iter = storage.aux_outputs.iter().map(|bus| {
bus.iter()
.map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples))
});
let mut buffer: Buffer<f64> = Buffer::new(main_in_iter, main_out_iter, num_samples);
let mut aux: AuxiliaryBuffers<f64> =
AuxiliaryBuffers::new(aux_in_iter, aux_out_iter, num_samples);
plugin.process_f64(&mut buffer, &mut aux, context);
}
#[inline]
unsafe fn process_audio_f64_converted(
&self,
process_data: &ProcessData,
num_samples: usize,
plugin: &mut P,
context: &CoreProcessContext,
) {
let conv = &mut *self.conversion_buffers.get();
if process_data.numInputs > 0 && !process_data.inputs.is_null() {
let input_buses = slice::from_raw_parts(process_data.inputs, 1);
let bus = &input_buses[0];
let num_channels = (bus.numChannels as usize).min(conv.main_input_f32.len());
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs = slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for (ch, &ptr) in channel_ptrs.iter().enumerate() {
if !ptr.is_null() && ch < conv.main_input_f32.len() {
let src = slice::from_raw_parts(ptr, num_samples);
for (i, &s) in src.iter().enumerate() {
conv.main_input_f32[ch][i] = s as f32;
}
}
}
}
}
for (bus_idx, aux_bus) in conv.aux_input_f32.iter_mut().enumerate() {
let vst_bus_idx = bus_idx + 1; if process_data.numInputs as usize > vst_bus_idx && !process_data.inputs.is_null() {
let input_buses = slice::from_raw_parts(
process_data.inputs,
process_data.numInputs as usize,
);
let bus = &input_buses[vst_bus_idx];
let num_channels = (bus.numChannels as usize).min(aux_bus.len());
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs = slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for (ch, &ptr) in channel_ptrs.iter().enumerate() {
if !ptr.is_null() && ch < aux_bus.len() {
let src = slice::from_raw_parts(ptr, num_samples);
for (i, &s) in src.iter().enumerate() {
aux_bus[ch][i] = s as f32;
}
}
}
}
}
}
let main_input_iter = conv.main_input_f32
.iter()
.map(|v| &v[..num_samples]);
let main_output_iter = conv.main_output_f32
.iter_mut()
.map(|v| &mut v[..num_samples]);
let aux_input_iter = conv.aux_input_f32
.iter()
.map(|bus| bus.iter().map(|v| &v[..num_samples]));
let aux_output_iter = conv.aux_output_f32
.iter_mut()
.map(|bus| bus.iter_mut().map(|v| &mut v[..num_samples]));
let mut buffer = Buffer::new(main_input_iter, main_output_iter, num_samples);
let mut aux = AuxiliaryBuffers::new(aux_input_iter, aux_output_iter, num_samples);
plugin.process(&mut buffer, &mut aux, context);
if process_data.numOutputs > 0 && !process_data.outputs.is_null() {
let output_buses = slice::from_raw_parts(process_data.outputs, 1);
let bus = &output_buses[0];
let num_channels = (bus.numChannels as usize).min(conv.main_output_f32.len());
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs = slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for (ch, &ptr) in channel_ptrs.iter().enumerate() {
if !ptr.is_null() && ch < conv.main_output_f32.len() {
let dst = slice::from_raw_parts_mut(ptr, num_samples);
for (i, sample) in conv.main_output_f32[ch][..num_samples].iter().enumerate() {
dst[i] = *sample as f64;
}
}
}
}
}
for (bus_idx, aux_bus) in conv.aux_output_f32.iter().enumerate() {
let vst_bus_idx = bus_idx + 1;
if process_data.numOutputs as usize > vst_bus_idx && !process_data.outputs.is_null() {
let output_buses = slice::from_raw_parts(
process_data.outputs,
process_data.numOutputs as usize,
);
let bus = &output_buses[vst_bus_idx];
let num_channels = (bus.numChannels as usize).min(aux_bus.len());
if num_channels > 0 && !bus.__field0.channelBuffers64.is_null() {
let channel_ptrs = slice::from_raw_parts(bus.__field0.channelBuffers64, num_channels);
for (ch, &ptr) in channel_ptrs.iter().enumerate() {
if !ptr.is_null() && ch < aux_bus.len() {
let dst = slice::from_raw_parts_mut(ptr, num_samples);
for (i, sample) in aux_bus[ch][..num_samples].iter().enumerate() {
dst[i] = *sample as f64;
}
}
}
}
}
}
}
}
impl<P: Plugin + 'static> ComponentFactory for Vst3Processor<P> {
fn create(config: &'static PluginConfig) -> Self {
Self::new(config)
}
}
impl<P: Plugin + 'static> Class for Vst3Processor<P> {
type Interfaces = (
IComponent,
IAudioProcessor,
IProcessContextRequirements,
IEditController,
IUnitInfo,
IMidiMapping,
IMidiLearn,
IMidiMapping2,
IMidiLearn2,
INoteExpressionController,
IKeyswitchController,
INoteExpressionPhysicalUIMapping,
IVst3WrapperMPESupport,
);
}
impl<P: Plugin + 'static> IPluginBaseTrait for Vst3Processor<P> {
unsafe fn initialize(&self, _context: *mut FUnknown) -> tresult {
kResultOk
}
unsafe fn terminate(&self) -> tresult {
kResultOk
}
}
impl<P: Plugin + 'static> IComponentTrait for Vst3Processor<P> {
unsafe fn getControllerClassId(&self, class_id: *mut TUID) -> tresult {
if class_id.is_null() {
return kInvalidArgument;
}
if let Some(controller) = self.config.controller_uid {
*class_id = controller;
kResultOk
} else {
kNotImplemented
}
}
unsafe fn setIoMode(&self, _mode: IoMode) -> tresult {
kResultOk
}
unsafe fn getBusCount(&self, media_type: MediaType, dir: BusDirection) -> i32 {
let plugin = self.plugin();
match media_type as MediaTypes {
MediaTypes_::kAudio => match dir as BusDirections {
BusDirections_::kInput => plugin.input_bus_count() as i32,
BusDirections_::kOutput => plugin.output_bus_count() as i32,
_ => 0,
},
MediaTypes_::kEvent => {
if plugin.wants_midi() {
1
} else {
0
}
}
_ => 0,
}
}
unsafe fn getBusInfo(
&self,
media_type: MediaType,
dir: BusDirection,
index: i32,
bus: *mut BusInfo,
) -> tresult {
if bus.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
match media_type as MediaTypes {
MediaTypes_::kAudio => {
let info = match dir as BusDirections {
BusDirections_::kInput => plugin.input_bus_info(index as usize),
BusDirections_::kOutput => plugin.output_bus_info(index as usize),
_ => None,
};
if let Some(info) = info {
let bus = &mut *bus;
bus.mediaType = MediaTypes_::kAudio as MediaType;
bus.direction = dir;
bus.channelCount = info.channel_count as i32;
copy_wstring(info.name, &mut bus.name);
bus.busType = match info.bus_type {
CoreBusType::Main => BusTypes_::kMain,
CoreBusType::Aux => BusTypes_::kAux,
} as BusType;
bus.flags = if info.is_default_active {
BusInfo_::BusFlags_::kDefaultActive
} else {
0
};
kResultOk
} else {
kInvalidArgument
}
}
MediaTypes_::kEvent => {
if index != 0 || !plugin.wants_midi() {
return kInvalidArgument;
}
let bus = &mut *bus;
bus.mediaType = MediaTypes_::kEvent as MediaType;
bus.direction = dir;
bus.channelCount = 1; let name = match dir as BusDirections {
BusDirections_::kInput => "MIDI In",
BusDirections_::kOutput => "MIDI Out",
_ => "MIDI",
};
copy_wstring(name, &mut bus.name);
bus.busType = BusTypes_::kMain as BusType;
bus.flags = BusInfo_::BusFlags_::kDefaultActive;
kResultOk
}
_ => kInvalidArgument,
}
}
unsafe fn getRoutingInfo(
&self,
_in_info: *mut RoutingInfo,
_out_info: *mut RoutingInfo,
) -> tresult {
kNotImplemented
}
unsafe fn activateBus(
&self,
_media_type: MediaType,
_dir: BusDirection,
_index: i32,
_state: TBool,
) -> tresult {
kResultOk
}
unsafe fn setActive(&self, state: TBool) -> tresult {
let plugin = self.plugin_mut();
plugin.set_active(state != 0);
kResultOk
}
unsafe fn setState(&self, state: *mut IBStream) -> tresult {
if state.is_null() {
return kInvalidArgument;
}
let stream = match ComRef::from_raw(state) {
Some(s) => s,
None => return kInvalidArgument,
};
let mut buffer = Vec::new();
let mut chunk = [0u8; 4096];
loop {
let mut bytes_read: i32 = 0;
let result = stream.read(
chunk.as_mut_ptr() as *mut c_void,
chunk.len() as i32,
&mut bytes_read,
);
if result != kResultOk || bytes_read <= 0 {
break;
}
buffer.extend_from_slice(&chunk[..bytes_read as usize]);
}
if buffer.is_empty() {
return kResultOk;
}
let plugin = self.plugin_mut();
match plugin.load_state(&buffer) {
Ok(()) => {
use beamer_core::param_types::Params;
let sample_rate = *self.sample_rate.get();
if sample_rate > 0.0 {
plugin.params_mut().set_sample_rate(sample_rate);
}
plugin.params_mut().reset_smoothing();
kResultOk
}
Err(_) => kResultFalse,
}
}
unsafe fn getState(&self, state: *mut IBStream) -> tresult {
if state.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
let data = match plugin.save_state() {
Ok(d) => d,
Err(_) => return kResultFalse,
};
if data.is_empty() {
return kResultOk;
}
let stream = match ComRef::from_raw(state) {
Some(s) => s,
None => return kInvalidArgument,
};
let mut bytes_written: i32 = 0;
let result = stream.write(
data.as_ptr() as *mut c_void,
data.len() as i32,
&mut bytes_written,
);
if result == kResultOk && bytes_written == data.len() as i32 {
kResultOk
} else {
kResultFalse
}
}
}
impl<P: Plugin + 'static> IAudioProcessorTrait for Vst3Processor<P> {
unsafe fn setBusArrangements(
&self,
inputs: *mut SpeakerArrangement,
num_ins: i32,
outputs: *mut SpeakerArrangement,
num_outs: i32,
) -> tresult {
if num_ins < 0
|| num_outs < 0
|| num_ins as usize > MAX_BUSES
|| num_outs as usize > MAX_BUSES
{
return kResultFalse;
}
if (num_ins > 0 && inputs.is_null()) || (num_outs > 0 && outputs.is_null()) {
return kInvalidArgument;
}
let plugin = self.plugin();
if num_ins as usize != plugin.input_bus_count()
|| num_outs as usize != plugin.output_bus_count()
{
return kResultFalse;
}
for i in 0..num_ins as usize {
let requested = *inputs.add(i);
if validate_speaker_arrangement(requested).is_err() {
return kResultFalse;
}
if let Some(info) = plugin.input_bus_info(i) {
let expected = channel_count_to_speaker_arrangement(info.channel_count);
if requested != expected {
return kResultFalse;
}
}
}
for i in 0..num_outs as usize {
let requested = *outputs.add(i);
if validate_speaker_arrangement(requested).is_err() {
return kResultFalse;
}
if let Some(info) = plugin.output_bus_info(i) {
let expected = channel_count_to_speaker_arrangement(info.channel_count);
if requested != expected {
return kResultFalse;
}
}
}
kResultTrue
}
unsafe fn getBusArrangement(
&self,
dir: BusDirection,
index: i32,
arr: *mut SpeakerArrangement,
) -> tresult {
if arr.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
let info = match dir as BusDirections {
BusDirections_::kInput => plugin.input_bus_info(index as usize),
BusDirections_::kOutput => plugin.output_bus_info(index as usize),
_ => None,
};
if let Some(info) = info {
*arr = channel_count_to_speaker_arrangement(info.channel_count);
kResultOk
} else {
kInvalidArgument
}
}
unsafe fn canProcessSampleSize(&self, symbolic_sample_size: i32) -> tresult {
match symbolic_sample_size as SymbolicSampleSizes {
SymbolicSampleSizes_::kSample32 => kResultOk,
SymbolicSampleSizes_::kSample64 => kResultOk, _ => kNotImplemented,
}
}
unsafe fn getLatencySamples(&self) -> u32 {
self.plugin().latency_samples()
}
unsafe fn setupProcessing(&self, setup: *mut ProcessSetup) -> tresult {
if setup.is_null() {
return kInvalidArgument;
}
let setup = &*setup;
*self.sample_rate.get() = setup.sampleRate;
*self.max_block_size.get() = setup.maxSamplesPerBlock as usize;
*self.symbolic_sample_size.get() = setup.symbolicSampleSize;
let plugin = self.plugin_mut();
plugin.setup(setup.sampleRate, setup.maxSamplesPerBlock as usize);
if let Err(msg) = validate_bus_limits(plugin) {
log::error!("Plugin bus configuration exceeds limits: {}", msg);
return kResultFalse;
}
*self.buffer_storage_f32.get() = ProcessBufferStorage::allocate(plugin);
*self.buffer_storage_f64.get() = ProcessBufferStorage::allocate(plugin);
if setup.symbolicSampleSize == SymbolicSampleSizes_::kSample64 as i32
&& !plugin.supports_double_precision()
{
*self.conversion_buffers.get() =
ConversionBuffers::allocate(plugin, setup.maxSamplesPerBlock as usize);
}
kResultOk
}
unsafe fn setProcessing(&self, _state: TBool) -> tresult {
kResultOk
}
unsafe fn process(&self, data: *mut ProcessData) -> tresult {
if data.is_null() {
return kInvalidArgument;
}
let process_data = &*data;
let num_samples = process_data.numSamples as usize;
if num_samples == 0 {
return kResultOk;
}
if let Some(param_changes) = ComRef::from_raw(process_data.inputParameterChanges) {
let params = self.plugin().params();
let param_count = param_changes.getParameterCount();
for i in 0..param_count {
if let Some(queue) = ComRef::from_raw(param_changes.getParameterData(i)) {
let param_id = queue.getParameterId();
let point_count = queue.getPointCount();
if point_count > 0 {
let mut sample_offset = 0;
let mut value = 0.0;
if queue.getPoint(point_count - 1, &mut sample_offset, &mut value)
== kResultTrue
{
params.set_normalized(param_id, value);
}
}
}
}
}
let midi_input = &mut *self.midi_input.get();
midi_input.clear();
if let Some(event_list) = ComRef::from_raw(process_data.inputEvents) {
let event_count = event_list.getEventCount();
for i in 0..event_count {
let mut event: Event = std::mem::zeroed();
if event_list.getEvent(i, &mut event) == kResultOk {
if let Some(midi_event) = convert_vst3_to_midi(&event) {
midi_input.push(midi_event);
}
}
}
}
if midi_input.has_overflowed() {
warn!(
"MIDI input buffer overflow: {} events max, some events were dropped",
beamer_core::midi::MAX_MIDI_EVENTS
);
}
let midi_output = &mut *self.midi_output.get();
midi_output.clear();
let sysex_pool = &mut *self.sysex_output_pool.get();
sysex_pool.clear();
#[cfg(feature = "sysex-heap-fallback")]
if sysex_pool.has_fallback() {
if let Some(event_list) = ComRef::from_raw(process_data.outputEvents) {
for sysex_data in sysex_pool.take_fallback() {
if let Some((ptr, len)) = sysex_pool.allocate(&sysex_data) {
let mut event: Event = std::mem::zeroed();
event.busIndex = 0;
event.sampleOffset = 0; event.ppqPosition = 0.0;
event.flags = 0;
event.r#type = K_DATA_EVENT;
event.__field0.data.r#type = DATA_TYPE_MIDI_SYSEX;
event.__field0.data.size = len as u32;
event.__field0.data.bytes = ptr;
let _ = event_list.addEvent(&mut event);
}
}
}
warn!(
"SysEx fallback: emitted delayed messages from previous block overflow"
);
}
let plugin = self.plugin_mut();
plugin.process_midi(midi_input.as_slice(), midi_output);
if let Some(event_list) = ComRef::from_raw(process_data.outputEvents) {
for midi_event in midi_output.iter() {
if let Some(mut vst3_event) = convert_midi_to_vst3(midi_event, sysex_pool) {
let _ = event_list.addEvent(&mut vst3_event);
}
}
}
if midi_output.has_overflowed() {
warn!(
"MIDI output buffer overflow: {} events reached capacity, some events were dropped",
midi_output.len()
);
}
if sysex_pool.has_overflowed() {
warn!(
"SysEx output pool overflow: {} slots exhausted, some SysEx messages were dropped",
sysex_pool.capacity()
);
}
let transport = extract_transport(process_data.processContext);
let sample_rate = *self.sample_rate.get();
let context = CoreProcessContext::new(sample_rate, num_samples, transport);
let symbolic_sample_size = *self.symbolic_sample_size.get();
let plugin = self.plugin_mut();
if symbolic_sample_size == SymbolicSampleSizes_::kSample64 as i32 {
if plugin.supports_double_precision() {
self.process_audio_f64_native(process_data, num_samples, plugin, &context);
} else {
self.process_audio_f64_converted(process_data, num_samples, plugin, &context);
}
} else {
self.process_audio_f32(process_data, num_samples, plugin, &context);
}
kResultOk
}
unsafe fn getTailSamples(&self) -> u32 {
let plugin = self.plugin();
plugin.tail_samples().saturating_add(plugin.bypass_ramp_samples())
}
}
impl<P: Plugin + 'static> IProcessContextRequirementsTrait for Vst3Processor<P> {
unsafe fn getProcessContextRequirements(&self) -> u32 {
const K_NEED_SYSTEM_TIME: u32 = 1 << 0;
const K_NEED_CONTINUOUS_TIME_SAMPLES: u32 = 1 << 1;
const K_NEED_PROJECT_TIME_MUSIC: u32 = 1 << 2;
const K_NEED_BAR_POSITION_MUSIC: u32 = 1 << 3;
const K_NEED_CYCLE_MUSIC: u32 = 1 << 4;
const K_NEED_SAMPLES_TO_NEXT_CLOCK: u32 = 1 << 5;
const K_NEED_TEMPO: u32 = 1 << 6;
const K_NEED_TIME_SIGNATURE: u32 = 1 << 7;
const K_NEED_FRAME_RATE: u32 = 1 << 9;
const K_NEED_TRANSPORT_STATE: u32 = 1 << 10;
K_NEED_SYSTEM_TIME
| K_NEED_CONTINUOUS_TIME_SAMPLES
| K_NEED_PROJECT_TIME_MUSIC
| K_NEED_BAR_POSITION_MUSIC
| K_NEED_CYCLE_MUSIC
| K_NEED_SAMPLES_TO_NEXT_CLOCK
| K_NEED_TEMPO
| K_NEED_TIME_SIGNATURE
| K_NEED_FRAME_RATE
| K_NEED_TRANSPORT_STATE
}
}
impl<P: Plugin + 'static> IEditControllerTrait for Vst3Processor<P> {
unsafe fn setComponentState(&self, _state: *mut IBStream) -> tresult {
kResultOk
}
unsafe fn setState(&self, _state: *mut IBStream) -> tresult {
kResultOk
}
unsafe fn getState(&self, _state: *mut IBStream) -> tresult {
kResultOk
}
unsafe fn getParameterCount(&self) -> i32 {
self.plugin().params().count() as i32
}
unsafe fn getParameterInfo(&self, param_index: i32, info: *mut ParameterInfo) -> tresult {
if info.is_null() {
return kInvalidArgument;
}
let params = self.plugin().params();
if let Some(param_info) = params.info(param_index as usize) {
let info = &mut *info;
info.id = param_info.id;
copy_wstring(param_info.name, &mut info.title);
copy_wstring(param_info.short_name, &mut info.shortTitle);
copy_wstring(param_info.units, &mut info.units);
info.stepCount = param_info.step_count;
info.defaultNormalizedValue = param_info.default_normalized;
info.unitId = param_info.unit_id;
info.flags = {
let mut flags = 0;
if param_info.flags.can_automate {
flags |= ParameterInfo_::ParameterFlags_::kCanAutomate;
}
if param_info.flags.is_bypass {
flags |= ParameterInfo_::ParameterFlags_::kIsBypass;
}
if param_info.flags.is_list {
flags |= ParameterInfo_::ParameterFlags_::kIsList;
}
flags
};
kResultOk
} else {
kInvalidArgument
}
}
unsafe fn getParamStringByValue(
&self,
id: u32,
value_normalized: f64,
string: *mut String128,
) -> tresult {
if string.is_null() {
return kInvalidArgument;
}
let params = self.plugin().params();
let display = params.normalized_to_string(id, value_normalized);
copy_wstring(&display, &mut *string);
kResultOk
}
unsafe fn getParamValueByString(
&self,
id: u32,
string: *mut TChar,
value_normalized: *mut f64,
) -> tresult {
if string.is_null() || value_normalized.is_null() {
return kInvalidArgument;
}
let len = len_wstring(string as *const TChar);
if let Ok(s) = String::from_utf16(slice::from_raw_parts(string as *const u16, len)) {
let params = self.plugin().params();
if let Some(value) = params.string_to_normalized(id, &s) {
*value_normalized = value;
return kResultOk;
}
}
kInvalidArgument
}
unsafe fn normalizedParamToPlain(&self, id: u32, value_normalized: f64) -> f64 {
self.plugin().params().normalized_to_plain(id, value_normalized)
}
unsafe fn plainParamToNormalized(&self, id: u32, plain_value: f64) -> f64 {
self.plugin().params().plain_to_normalized(id, plain_value)
}
unsafe fn getParamNormalized(&self, id: u32) -> f64 {
self.plugin().params().get_normalized(id)
}
unsafe fn setParamNormalized(&self, id: u32, value: f64) -> tresult {
self.plugin().params().set_normalized(id, value);
kResultOk
}
unsafe fn setComponentHandler(&self, _handler: *mut IComponentHandler) -> tresult {
kResultOk
}
unsafe fn createView(&self, name: *const c_char) -> *mut IPlugView {
if name.is_null() {
return std::ptr::null_mut();
}
let name_str = std::ffi::CStr::from_ptr(name).to_str().unwrap_or("");
if name_str != "editor" {
return std::ptr::null_mut();
}
std::ptr::null_mut()
}
}
impl<P: Plugin + 'static> IUnitInfoTrait for Vst3Processor<P> {
unsafe fn getUnitCount(&self) -> i32 {
use beamer_core::params::Units;
self.plugin().params().unit_count() as i32
}
unsafe fn getUnitInfo(&self, unit_index: i32, info: *mut UnitInfo) -> tresult {
if info.is_null() || unit_index < 0 {
return kInvalidArgument;
}
use beamer_core::params::Units;
let params = self.plugin().params();
if let Some(unit_info) = params.unit_info(unit_index as usize) {
let info = &mut *info;
info.id = unit_info.id;
info.parentUnitId = unit_info.parent_id;
info.programListId = kNoProgramListId;
copy_wstring(unit_info.name, &mut info.name);
kResultOk
} else {
kInvalidArgument
}
}
unsafe fn getProgramListCount(&self) -> i32 {
0 }
unsafe fn getProgramListInfo(
&self,
_list_index: i32,
_info: *mut ProgramListInfo,
) -> tresult {
kNotImplemented
}
unsafe fn getProgramName(&self, _list_id: i32, _program_index: i32, _name: *mut String128) -> tresult {
kNotImplemented
}
unsafe fn getProgramInfo(
&self,
_list_id: i32,
_program_index: i32,
_attribute_id: *const c_char,
_attribute_value: *mut String128,
) -> tresult {
kNotImplemented
}
unsafe fn hasProgramPitchNames(&self, _list_id: i32, _program_index: i32) -> tresult {
kResultFalse
}
unsafe fn getProgramPitchName(
&self,
_list_id: i32,
_program_index: i32,
_midi_pitch: i16,
_name: *mut String128,
) -> tresult {
kNotImplemented
}
unsafe fn getSelectedUnit(&self) -> i32 {
0 }
unsafe fn selectUnit(&self, _unit_id: i32) -> tresult {
kResultOk }
unsafe fn getUnitByBus(
&self,
_media_type: MediaType,
_dir: BusDirection,
_bus_index: i32,
_channel: i32,
_unit_id: *mut i32,
) -> tresult {
kNotImplemented
}
unsafe fn setUnitProgramData(
&self,
_list_or_unit_id: i32,
_program_index: i32,
_data: *mut IBStream,
) -> tresult {
kNotImplemented
}
}
impl<P: Plugin + 'static> IMidiMappingTrait for Vst3Processor<P> {
unsafe fn getMidiControllerAssignment(
&self,
bus_index: i32,
channel: i16,
midi_controller_number: i16,
id: *mut u32,
) -> tresult {
if id.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
if let Some(param_id) =
plugin.midi_cc_to_param(bus_index, channel, midi_controller_number as u8)
{
*id = param_id;
kResultOk
} else {
kResultFalse
}
}
}
impl<P: Plugin + 'static> IMidiLearnTrait for Vst3Processor<P> {
unsafe fn onLiveMIDIControllerInput(
&self,
bus_index: i32,
channel: i16,
midi_cc: i16,
) -> tresult {
let plugin = self.plugin_mut();
if plugin.on_midi_learn(bus_index, channel, midi_cc as u8) {
kResultOk
} else {
kResultFalse
}
}
}
impl<P: Plugin + 'static> IMidiMapping2Trait for Vst3Processor<P> {
unsafe fn getNumMidi1ControllerAssignments(&self, direction: BusDirections) -> u32 {
if direction != BusDirections_::kInput {
return 0;
}
let plugin = self.plugin();
plugin.midi1_assignments().len() as u32
}
unsafe fn getMidi1ControllerAssignments(
&self,
direction: BusDirections,
list: *const Midi1ControllerParamIDAssignmentList,
) -> tresult {
if list.is_null() || direction != BusDirections_::kInput {
return kInvalidArgument;
}
let plugin = self.plugin();
let assignments = plugin.midi1_assignments();
let list_ref = &*list;
if (list_ref.count as usize) < assignments.len() {
return kResultFalse;
}
if assignments.is_empty() {
return kResultOk;
}
let map = slice::from_raw_parts_mut(list_ref.map, assignments.len());
for (i, a) in assignments.iter().enumerate() {
map[i] = Midi1ControllerParamIDAssignment {
pId: a.assignment.param_id,
busIndex: a.assignment.bus_index,
channel: a.assignment.channel,
controller: a.controller as i16,
};
}
kResultOk
}
unsafe fn getNumMidi2ControllerAssignments(&self, direction: BusDirections) -> u32 {
if direction != BusDirections_::kInput {
return 0;
}
let plugin = self.plugin();
plugin.midi2_assignments().len() as u32
}
unsafe fn getMidi2ControllerAssignments(
&self,
direction: BusDirections,
list: *const Midi2ControllerParamIDAssignmentList,
) -> tresult {
if list.is_null() || direction != BusDirections_::kInput {
return kInvalidArgument;
}
let plugin = self.plugin();
let assignments = plugin.midi2_assignments();
let list_ref = &*list;
if (list_ref.count as usize) < assignments.len() {
return kResultFalse;
}
if assignments.is_empty() {
return kResultOk;
}
let map = slice::from_raw_parts_mut(list_ref.map, assignments.len());
for (i, a) in assignments.iter().enumerate() {
map[i] = Midi2ControllerParamIDAssignment {
pId: a.assignment.param_id,
busIndex: a.assignment.bus_index,
channel: a.assignment.channel,
controller: Midi2Controller {
bank: a.controller.bank,
registered: if a.controller.registered { 1 } else { 0 },
index: a.controller.index,
reserved: 0,
},
};
}
kResultOk
}
}
impl<P: Plugin + 'static> IMidiLearn2Trait for Vst3Processor<P> {
unsafe fn onLiveMidi1ControllerInput(
&self,
bus_index: i32,
channel: u8,
midi_cc: i16,
) -> tresult {
let plugin = self.plugin_mut();
if plugin.on_midi1_learn(bus_index, channel, midi_cc as u8) {
kResultOk
} else {
kResultFalse
}
}
unsafe fn onLiveMidi2ControllerInput(
&self,
bus_index: i32,
channel: u8,
midi_cc: Midi2Controller,
) -> tresult {
let plugin = self.plugin_mut();
let controller = beamer_core::Midi2Controller {
bank: midi_cc.bank,
registered: midi_cc.registered != 0,
index: midi_cc.index,
};
if plugin.on_midi2_learn(bus_index, channel, controller) {
kResultOk
} else {
kResultFalse
}
}
}
impl<P: Plugin + 'static> INoteExpressionControllerTrait for Vst3Processor<P> {
unsafe fn getNoteExpressionCount(&self, bus_index: i32, channel: i16) -> i32 {
let plugin = self.plugin();
plugin.note_expression_count(bus_index, channel) as i32
}
unsafe fn getNoteExpressionInfo(
&self,
bus_index: i32,
channel: i16,
note_expression_index: i32,
info: *mut NoteExpressionTypeInfo,
) -> tresult {
if info.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
if let Some(expr_info) =
plugin.note_expression_info(bus_index, channel, note_expression_index as usize)
{
let vst_info = &mut *info;
vst_info.typeId = expr_info.type_id;
copy_wstring(expr_info.title_str(), &mut vst_info.title);
copy_wstring(expr_info.short_title_str(), &mut vst_info.shortTitle);
copy_wstring(expr_info.units_str(), &mut vst_info.units);
vst_info.unitId = expr_info.unit_id;
vst_info.valueDesc.minimum = expr_info.value_desc.minimum;
vst_info.valueDesc.maximum = expr_info.value_desc.maximum;
vst_info.valueDesc.defaultValue = expr_info.value_desc.default_value;
vst_info.valueDesc.stepCount = expr_info.value_desc.step_count;
vst_info.associatedParameterId = expr_info.associated_parameter_id as u32;
vst_info.flags = expr_info.flags.0;
kResultOk
} else {
kInvalidArgument
}
}
unsafe fn getNoteExpressionStringByValue(
&self,
bus_index: i32,
channel: i16,
id: NoteExpressionTypeID,
value_normalized: NoteExpressionValue,
string: *mut String128,
) -> tresult {
if string.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
let display = plugin.note_expression_value_to_string(bus_index, channel, id, value_normalized);
copy_wstring(&display, &mut *string);
kResultOk
}
unsafe fn getNoteExpressionValueByString(
&self,
bus_index: i32,
channel: i16,
id: NoteExpressionTypeID,
string: *const TChar,
value_normalized: *mut NoteExpressionValue,
) -> tresult {
if string.is_null() || value_normalized.is_null() {
return kInvalidArgument;
}
let len = len_wstring(string);
if let Ok(s) = String::from_utf16(slice::from_raw_parts(string, len)) {
let plugin = self.plugin();
if let Some(value) = plugin.note_expression_string_to_value(bus_index, channel, id, &s)
{
*value_normalized = value;
return kResultOk;
}
}
kResultFalse
}
}
impl<P: Plugin + 'static> IKeyswitchControllerTrait for Vst3Processor<P> {
unsafe fn getKeyswitchCount(&self, bus_index: i32, channel: i16) -> i32 {
let plugin = self.plugin();
plugin.keyswitch_count(bus_index, channel) as i32
}
unsafe fn getKeyswitchInfo(
&self,
bus_index: i32,
channel: i16,
keyswitch_index: i32,
info: *mut KeyswitchInfo,
) -> tresult {
if info.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
if let Some(ks_info) =
plugin.keyswitch_info(bus_index, channel, keyswitch_index as usize)
{
let vst_info = &mut *info;
vst_info.typeId = ks_info.type_id;
copy_wstring(ks_info.title_str(), &mut vst_info.title);
copy_wstring(ks_info.short_title_str(), &mut vst_info.shortTitle);
vst_info.keyswitchMin = ks_info.keyswitch_min;
vst_info.keyswitchMax = ks_info.keyswitch_max;
vst_info.keyRemapped = ks_info.key_remapped;
vst_info.unitId = ks_info.unit_id;
vst_info.flags = ks_info.flags;
kResultOk
} else {
kInvalidArgument
}
}
}
impl<P: Plugin + 'static> INoteExpressionPhysicalUIMappingTrait for Vst3Processor<P> {
unsafe fn getPhysicalUIMapping(
&self,
bus_index: i32,
channel: i16,
list: *mut PhysicalUIMapList,
) -> tresult {
if list.is_null() {
return kInvalidArgument;
}
let plugin = self.plugin();
let mappings = plugin.physical_ui_mappings(bus_index, channel);
let list_ref = &mut *list;
let fill_count = (list_ref.count as usize).min(mappings.len());
if fill_count > 0 && !list_ref.map.is_null() {
let map_slice = slice::from_raw_parts_mut(list_ref.map, fill_count);
for (i, mapping) in mappings.iter().take(fill_count).enumerate() {
map_slice[i].physicalUITypeID = mapping.physical_ui_type_id;
map_slice[i].noteExpressionTypeID = mapping.note_expression_type_id;
}
}
kResultOk
}
}
impl<P: Plugin + 'static> IVst3WrapperMPESupportTrait for Vst3Processor<P> {
unsafe fn enableMPEInputProcessing(&self, state: TBool) -> tresult {
let plugin = self.plugin_mut();
if plugin.enable_mpe_input_processing(state != 0) {
kResultOk
} else {
kResultFalse
}
}
unsafe fn setMPEInputDeviceSettings(
&self,
master_channel: i32,
member_begin_channel: i32,
member_end_channel: i32,
) -> tresult {
let plugin = self.plugin_mut();
let settings = beamer_core::MpeInputDeviceSettings {
master_channel,
member_begin_channel,
member_end_channel,
};
if plugin.set_mpe_input_device_settings(settings) {
kResultOk
} else {
kResultFalse
}
}
}
fn utf16_to_utf8(utf16: &[u16], utf8_buf: &mut [u8]) -> usize {
let mut utf8_pos = 0;
for &code_unit in utf16 {
if code_unit == 0 {
break;
}
let (bytes_needed, char_value) = if code_unit < 0x80 {
(1, code_unit as u32)
} else if code_unit < 0x800 {
(2, code_unit as u32)
} else if (0xD800..=0xDFFF).contains(&code_unit) {
(3, 0xFFFD) } else {
(3, code_unit as u32)
};
if utf8_pos + bytes_needed > utf8_buf.len() {
break;
}
match bytes_needed {
1 => {
utf8_buf[utf8_pos] = char_value as u8;
}
2 => {
utf8_buf[utf8_pos] = (0xC0 | (char_value >> 6)) as u8;
utf8_buf[utf8_pos + 1] = (0x80 | (char_value & 0x3F)) as u8;
}
3 => {
utf8_buf[utf8_pos] = (0xE0 | (char_value >> 12)) as u8;
utf8_buf[utf8_pos + 1] = (0x80 | ((char_value >> 6) & 0x3F)) as u8;
utf8_buf[utf8_pos + 2] = (0x80 | (char_value & 0x3F)) as u8;
}
_ => unreachable!(),
}
utf8_pos += bytes_needed;
}
utf8_pos
}
fn channel_count_to_speaker_arrangement(channel_count: u32) -> SpeakerArrangement {
match channel_count {
1 => SpeakerArr::kMono,
2 => SpeakerArr::kStereo,
n => (1u64 << n) - 1,
}
}
unsafe fn convert_vst3_to_midi(event: &Event) -> Option<MidiEvent> {
let sample_offset = event.sampleOffset as u32;
match event.r#type {
K_NOTE_ON_EVENT => {
let note_on = &event.__field0.noteOn;
Some(MidiEvent::note_on(
sample_offset,
note_on.channel as u8,
note_on.pitch as u8,
note_on.velocity,
note_on.noteId,
note_on.tuning,
note_on.length,
))
}
K_NOTE_OFF_EVENT => {
let note_off = &event.__field0.noteOff;
Some(MidiEvent::note_off(
sample_offset,
note_off.channel as u8,
note_off.pitch as u8,
note_off.velocity,
note_off.noteId,
note_off.tuning,
))
}
K_POLY_PRESSURE_EVENT => {
let poly = &event.__field0.polyPressure;
Some(MidiEvent::poly_pressure(
sample_offset,
poly.channel as u8,
poly.pitch as u8,
poly.pressure,
poly.noteId,
))
}
K_DATA_EVENT => {
let data_event = &event.__field0.data;
if data_event.r#type == DATA_TYPE_MIDI_SYSEX {
let mut sysex = SysEx::new();
let copy_len = (data_event.size as usize).min(MAX_SYSEX_SIZE);
if copy_len > 0 && !data_event.bytes.is_null() {
let src = std::slice::from_raw_parts(data_event.bytes, copy_len);
sysex.data[..copy_len].copy_from_slice(src);
sysex.len = copy_len as u16;
}
Some(MidiEvent {
sample_offset,
event: MidiEventKind::SysEx(Box::new(sysex)),
})
} else {
None
}
}
K_NOTE_EXPRESSION_VALUE_EVENT => {
let expr = &event.__field0.noteExpressionValue;
Some(MidiEvent {
sample_offset,
event: MidiEventKind::NoteExpressionValue(CoreNoteExpressionValue {
note_id: expr.noteId,
expression_type: expr.typeId,
value: expr.value,
}),
})
}
K_NOTE_EXPRESSION_INT_VALUE_EVENT => {
let expr = &event.__field0.noteExpressionIntValue;
Some(MidiEvent {
sample_offset,
event: MidiEventKind::NoteExpressionInt(NoteExpressionInt {
note_id: expr.noteId,
expression_type: expr.typeId,
value: expr.value,
}),
})
}
K_NOTE_EXPRESSION_TEXT_EVENT => {
let expr = &event.__field0.noteExpressionText;
let mut text_event = NoteExpressionText {
note_id: expr.noteId,
expression_type: expr.typeId,
text: [0u8; MAX_EXPRESSION_TEXT_SIZE],
text_len: 0,
};
let text_len = expr.textLen as usize;
if !expr.text.is_null() && text_len > 0 {
let text_slice = std::slice::from_raw_parts(expr.text, text_len);
let utf8_len = utf16_to_utf8(text_slice, &mut text_event.text);
text_event.text_len = utf8_len as u8;
}
Some(MidiEvent {
sample_offset,
event: MidiEventKind::NoteExpressionText(text_event),
})
}
K_CHORD_EVENT => {
let chord = &event.__field0.chord;
let mut info = ChordInfo {
root: chord.root as i8,
bass_note: chord.bassNote as i8,
mask: chord.mask as u16,
name: [0u8; MAX_CHORD_NAME_SIZE],
name_len: 0,
};
let text_len = chord.textLen as usize;
if !chord.text.is_null() && text_len > 0 {
let text_slice = std::slice::from_raw_parts(chord.text, text_len);
let utf8_len = utf16_to_utf8(text_slice, &mut info.name);
info.name_len = utf8_len as u8;
}
Some(MidiEvent {
sample_offset,
event: MidiEventKind::ChordInfo(info),
})
}
K_SCALE_EVENT => {
let scale = &event.__field0.scale;
let mut info = ScaleInfo {
root: scale.root as i8,
mask: scale.mask as u16,
name: [0u8; MAX_SCALE_NAME_SIZE],
name_len: 0,
};
let text_len = scale.textLen as usize;
if !scale.text.is_null() && text_len > 0 {
let text_slice = std::slice::from_raw_parts(scale.text, text_len);
let utf8_len = utf16_to_utf8(text_slice, &mut info.name);
info.name_len = utf8_len as u8;
}
Some(MidiEvent {
sample_offset,
event: MidiEventKind::ScaleInfo(info),
})
}
K_LEGACY_MIDI_CC_OUT_EVENT => {
let cc_event = &event.__field0.midiCCOut;
let channel = cc_event.channel as u8;
match cc_event.controlNumber {
0..=127 => {
Some(MidiEvent::control_change(
sample_offset,
channel,
cc_event.controlNumber,
cc_event.value as f32 / 127.0,
))
}
LEGACY_CC_CHANNEL_PRESSURE => Some(MidiEvent::channel_pressure(
sample_offset,
channel,
cc_event.value as f32 / 127.0,
)),
LEGACY_CC_PITCH_BEND => {
let lsb = (cc_event.value as u8) as u16;
let msb = (cc_event.value2 as u8) as u16;
let raw = (msb << 7) | (lsb & 0x7F);
let normalized = (raw as f32 - 8192.0) / 8192.0;
Some(MidiEvent::pitch_bend(sample_offset, channel, normalized))
}
LEGACY_CC_PROGRAM_CHANGE => Some(MidiEvent::program_change(
sample_offset,
channel,
cc_event.value as u8,
)),
_ => None, }
}
_ => None, }
}
fn convert_midi_to_vst3(midi: &MidiEvent, sysex_pool: &mut SysExOutputPool) -> Option<Event> {
let mut event: Event = unsafe { std::mem::zeroed() };
event.busIndex = 0;
event.sampleOffset = midi.sample_offset as i32;
event.ppqPosition = 0.0;
event.flags = 0;
match &midi.event {
MidiEventKind::NoteOn(note_on) => {
event.r#type = K_NOTE_ON_EVENT;
event.__field0.noteOn.channel = note_on.channel as i16;
event.__field0.noteOn.pitch = note_on.pitch as i16;
event.__field0.noteOn.velocity = note_on.velocity;
event.__field0.noteOn.noteId = note_on.note_id;
event.__field0.noteOn.tuning = note_on.tuning;
event.__field0.noteOn.length = note_on.length;
}
MidiEventKind::NoteOff(note_off) => {
event.r#type = K_NOTE_OFF_EVENT;
event.__field0.noteOff.channel = note_off.channel as i16;
event.__field0.noteOff.pitch = note_off.pitch as i16;
event.__field0.noteOff.velocity = note_off.velocity;
event.__field0.noteOff.noteId = note_off.note_id;
event.__field0.noteOff.tuning = note_off.tuning;
}
MidiEventKind::PolyPressure(poly) => {
event.r#type = K_POLY_PRESSURE_EVENT;
event.__field0.polyPressure.channel = poly.channel as i16;
event.__field0.polyPressure.pitch = poly.pitch as i16;
event.__field0.polyPressure.pressure = poly.pressure;
event.__field0.polyPressure.noteId = poly.note_id;
}
MidiEventKind::ControlChange(cc) => {
event.r#type = K_LEGACY_MIDI_CC_OUT_EVENT;
event.__field0.midiCCOut.controlNumber = cc.controller;
event.__field0.midiCCOut.channel = cc.channel as i8;
event.__field0.midiCCOut.value = (cc.value * 127.0) as i8;
event.__field0.midiCCOut.value2 = 0;
}
MidiEventKind::PitchBend(pb) => {
event.r#type = K_LEGACY_MIDI_CC_OUT_EVENT;
event.__field0.midiCCOut.controlNumber = LEGACY_CC_PITCH_BEND;
event.__field0.midiCCOut.channel = pb.channel as i8;
let raw = ((pb.value * 8192.0) + 8192.0).clamp(0.0, 16383.0) as i16;
event.__field0.midiCCOut.value = (raw & 0x7F) as i8;
event.__field0.midiCCOut.value2 = ((raw >> 7) & 0x7F) as i8;
}
MidiEventKind::ChannelPressure(cp) => {
event.r#type = K_LEGACY_MIDI_CC_OUT_EVENT;
event.__field0.midiCCOut.controlNumber = LEGACY_CC_CHANNEL_PRESSURE;
event.__field0.midiCCOut.channel = cp.channel as i8;
event.__field0.midiCCOut.value = (cp.pressure * 127.0) as i8;
event.__field0.midiCCOut.value2 = 0;
}
MidiEventKind::ProgramChange(pc) => {
event.r#type = K_LEGACY_MIDI_CC_OUT_EVENT;
event.__field0.midiCCOut.controlNumber = LEGACY_CC_PROGRAM_CHANGE;
event.__field0.midiCCOut.channel = pc.channel as i8;
event.__field0.midiCCOut.value = pc.program as i8;
event.__field0.midiCCOut.value2 = 0;
}
MidiEventKind::NoteExpressionValue(expr) => {
event.r#type = K_NOTE_EXPRESSION_VALUE_EVENT;
event.__field0.noteExpressionValue.noteId = expr.note_id;
event.__field0.noteExpressionValue.typeId = expr.expression_type;
event.__field0.noteExpressionValue.value = expr.value;
}
MidiEventKind::NoteExpressionInt(expr) => {
event.r#type = K_NOTE_EXPRESSION_INT_VALUE_EVENT;
event.__field0.noteExpressionIntValue.noteId = expr.note_id;
event.__field0.noteExpressionIntValue.typeId = expr.expression_type;
event.__field0.noteExpressionIntValue.value = expr.value;
}
MidiEventKind::SysEx(sysex) => {
if let Some((ptr, len)) = sysex_pool.allocate(sysex.as_slice()) {
event.r#type = K_DATA_EVENT;
event.__field0.data.r#type = DATA_TYPE_MIDI_SYSEX;
event.__field0.data.size = len as u32;
event.__field0.data.bytes = ptr;
} else {
return None;
}
}
MidiEventKind::ChordInfo(_) => return None,
MidiEventKind::ScaleInfo(_) => return None,
MidiEventKind::NoteExpressionText(_) => return None,
}
Some(event)
}