use crate::midi::{MidiChannel, MidiController, MidiControllerAssignment};
use vst3::{
ComPtr,
Steinberg::{kResultOk, tresult, Vst::*},
};
const MAX_ASSIGNMENTS: usize = 16_384;
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct Controller {
byte1: u8,
byte2: u8,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct Assignment {
parameter_id: u32,
bus: i32,
channel: u8,
controller: Controller,
}
impl Controller {
fn from_public(controller: MidiController) -> Option<Self> {
match controller {
MidiController::Registered { bank, index } => Some(Self {
byte1: bank | 0x80,
byte2: index,
}),
MidiController::Assignable { bank, index } => Some(Self {
byte1: bank,
byte2: index,
}),
MidiController::Midi1(_) => None,
}
}
fn public(self) -> MidiController {
if self.byte1 & 0x80 != 0 {
MidiController::Registered {
bank: self.byte1 & 0x7f,
index: self.byte2 & 0x7f,
}
} else {
MidiController::Assignable {
bank: self.byte1 & 0x7f,
index: self.byte2 & 0x7f,
}
}
}
}
pub(super) fn assignments(
controller: &ComPtr<IEditController>,
direction: BusDirections,
) -> Option<Vec<MidiControllerAssignment>> {
let mapping = controller.cast::<IMidiMapping2>()?;
unsafe {
let n1 = mapping.getNumMidi1ControllerAssignments(direction) as usize;
let n2 = mapping.getNumMidi2ControllerAssignments(direction) as usize;
if n1.checked_add(n2)? > MAX_ASSIGNMENTS {
return None;
}
let mut one = vec![std::mem::zeroed::<Midi1ControllerParamIDAssignment>(); n1];
let mut two = vec![Assignment::default(); n2];
let list1 = Midi1ControllerParamIDAssignmentList {
count: n1 as u32,
map: one.as_mut_ptr(),
};
let list2 = Midi2ControllerParamIDAssignmentList {
count: n2 as u32,
map: two.as_mut_ptr().cast(),
};
if (n1 > 0 && mapping.getMidi1ControllerAssignments(direction, &list1) != kResultOk)
|| (n2 > 0 && mapping.getMidi2ControllerAssignments(direction, &list2) != kResultOk)
{
return None;
}
let mut out = Vec::with_capacity(n1 + n2);
for entry in one {
let Some(channel) = MidiChannel::from_index(entry.channel) else {
continue;
};
let controller = MidiController::Midi1(entry.controller as u16);
if entry.busIndex >= 0 && controller.is_valid() {
out.push(MidiControllerAssignment {
bus: entry.busIndex,
channel,
controller,
parameter_id: entry.pId,
});
}
}
for entry in two {
let Some(channel) = MidiChannel::from_index(entry.channel) else {
continue;
};
if entry.bus >= 0 {
out.push(MidiControllerAssignment {
bus: entry.bus,
channel,
controller: entry.controller.public(),
parameter_id: entry.parameter_id,
});
}
}
Some(out)
}
}
pub(super) fn learn(
controller: &ComPtr<IEditController>,
bus: i32,
channel: MidiChannel,
address: MidiController,
) -> bool {
unsafe {
if let Some(learn) = controller.cast::<IMidiLearn2>() {
return match address {
MidiController::Midi1(cc) => {
learn.onLiveMidi1ControllerInput(bus, channel.as_index(), cc as i16)
== kResultOk
}
_ => {
let Some(address) = Controller::from_public(address) else {
return false;
};
learn_midi2(learn.as_ptr(), bus, channel.as_index(), address) == kResultOk
}
};
}
match (address, controller.cast::<IMidiLearn>()) {
(MidiController::Midi1(cc), Some(learn)) => {
learn.onLiveMIDIControllerInput(bus, channel.as_index() as i16, cc as i16)
== kResultOk
}
_ => false,
}
}
}
unsafe fn learn_midi2(
ptr: *mut IMidiLearn2,
bus: i32,
channel: u8,
address: Controller,
) -> tresult {
let call: unsafe extern "system" fn(*mut IMidiLearn2, i32, u8, Controller) -> tresult =
std::mem::transmute((*(*ptr).vtbl).onLiveMidi2ControllerInput);
call(ptr, bus, channel, address)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn released_learn2_by_value_call_preserves_bus_channel_and_address() {
use vst3::Steinberg::{kNoInterface, FUnknown, FUnknownVtbl, TUID};
unsafe extern "system" fn query(
_: *mut FUnknown,
_: *const TUID,
out: *mut *mut std::ffi::c_void,
) -> tresult {
*out = std::ptr::null_mut();
kNoInterface
}
unsafe extern "system" fn retain(_: *mut FUnknown) -> u32 {
1
}
unsafe extern "system" fn midi1(_: *mut IMidiLearn2, _: i32, _: u8, _: i16) -> tresult {
kNoInterface
}
unsafe extern "system" fn midi2(
_: *mut IMidiLearn2,
bus: i32,
channel: u8,
address: Controller,
) -> tresult {
if (bus, channel, address.byte1, address.byte2) == (3, 15, 0x91, 63) {
kResultOk
} else {
kNoInterface
}
}
#[repr(C)]
struct ReleasedVtbl {
base: FUnknownVtbl,
midi2: unsafe extern "system" fn(*mut IMidiLearn2, i32, u8, Controller) -> tresult,
midi1: unsafe extern "system" fn(*mut IMidiLearn2, i32, u8, i16) -> tresult,
}
let vtbl = ReleasedVtbl {
base: FUnknownVtbl {
queryInterface: query,
addRef: retain,
release: retain,
},
midi2,
midi1,
};
let mut interface = IMidiLearn2 {
vtbl: (&vtbl as *const ReleasedVtbl).cast(),
};
assert_eq!(
unsafe {
learn_midi2(
&mut interface,
3,
15,
Controller {
byte1: 0x91,
byte2: 63,
},
)
},
kResultOk
);
}
#[test]
fn released_midi2_abi_layout_and_encoding() {
assert_eq!(std::mem::size_of::<Controller>(), 2);
assert_eq!(std::mem::size_of::<Assignment>(), 12);
assert_eq!(std::mem::offset_of!(Assignment, controller), 9);
let address = MidiController::Registered {
bank: 17,
index: 63,
};
let raw = Controller::from_public(address).unwrap();
assert_eq!((raw.byte1, raw.byte2), (0x91, 63));
assert_eq!(raw.public(), address);
}
}