#[cfg(feature = "sysex-2048")]
pub const MAX_SYSEX_SIZE: usize = 2048;
#[cfg(all(feature = "sysex-1024", not(feature = "sysex-2048")))]
pub const MAX_SYSEX_SIZE: usize = 1024;
#[cfg(all(feature = "sysex-256", not(feature = "sysex-1024"), not(feature = "sysex-2048")))]
pub const MAX_SYSEX_SIZE: usize = 256;
#[cfg(not(any(feature = "sysex-256", feature = "sysex-1024", feature = "sysex-2048")))]
pub const MAX_SYSEX_SIZE: usize = 512;
pub const MAX_EXPRESSION_TEXT_SIZE: usize = 64;
pub const MAX_CHORD_NAME_SIZE: usize = 32;
pub const MAX_SCALE_NAME_SIZE: usize = 32;
pub type MidiChannel = u8;
pub type MidiNote = u8;
pub type NoteId = i32;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NoteOn {
pub channel: MidiChannel,
pub pitch: MidiNote,
pub velocity: f32,
pub note_id: NoteId,
pub tuning: f32,
pub length: i32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NoteOff {
pub channel: MidiChannel,
pub pitch: MidiNote,
pub velocity: f32,
pub note_id: NoteId,
pub tuning: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PolyPressure {
pub channel: MidiChannel,
pub pitch: MidiNote,
pub pressure: f32,
pub note_id: NoteId,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ControlChange {
pub channel: MidiChannel,
pub controller: u8,
pub value: f32,
}
impl ControlChange {
#[inline]
pub const fn is_mod_wheel(&self) -> bool {
self.controller == cc::MOD_WHEEL
}
#[inline]
pub const fn is_sustain_pedal(&self) -> bool {
self.controller == cc::SUSTAIN_PEDAL
}
#[inline]
pub const fn is_expression(&self) -> bool {
self.controller == cc::EXPRESSION
}
#[inline]
pub const fn is_volume(&self) -> bool {
self.controller == cc::VOLUME
}
#[inline]
pub fn is_sustain_on(&self) -> bool {
self.is_sustain_pedal() && self.value >= 0.5
}
#[inline]
pub const fn is_bank_select_msb(&self) -> bool {
self.controller == cc::BANK_SELECT_MSB
}
#[inline]
pub const fn is_bank_select_lsb(&self) -> bool {
self.controller == cc::BANK_SELECT_LSB
}
#[inline]
pub const fn is_bank_select(&self) -> bool {
self.is_bank_select_msb() || self.is_bank_select_lsb()
}
#[inline]
pub const fn is_14bit_msb(&self) -> bool {
self.controller < 32
}
#[inline]
pub const fn is_14bit_lsb(&self) -> bool {
self.controller >= 32 && self.controller < 64
}
#[inline]
pub const fn lsb_pair(&self) -> Option<u8> {
if self.controller < 32 {
Some(self.controller + 32)
} else {
None
}
}
#[inline]
pub const fn msb_pair(&self) -> Option<u8> {
if self.controller >= 32 && self.controller < 64 {
Some(self.controller - 32)
} else {
None
}
}
#[inline]
pub const fn is_rpn_msb(&self) -> bool {
self.controller == cc::RPN_MSB
}
#[inline]
pub const fn is_rpn_lsb(&self) -> bool {
self.controller == cc::RPN_LSB
}
#[inline]
pub const fn is_rpn_select(&self) -> bool {
self.controller == cc::RPN_MSB || self.controller == cc::RPN_LSB
}
#[inline]
pub const fn is_nrpn_msb(&self) -> bool {
self.controller == cc::NRPN_MSB
}
#[inline]
pub const fn is_nrpn_lsb(&self) -> bool {
self.controller == cc::NRPN_LSB
}
#[inline]
pub const fn is_nrpn_select(&self) -> bool {
self.controller == cc::NRPN_MSB || self.controller == cc::NRPN_LSB
}
#[inline]
pub const fn is_data_entry_msb(&self) -> bool {
self.controller == cc::DATA_ENTRY_MSB
}
#[inline]
pub const fn is_data_entry_lsb(&self) -> bool {
self.controller == cc::DATA_ENTRY_LSB
}
#[inline]
pub const fn is_data_entry(&self) -> bool {
self.controller == cc::DATA_ENTRY_MSB || self.controller == cc::DATA_ENTRY_LSB
}
#[inline]
pub const fn is_data_increment(&self) -> bool {
self.controller == cc::DATA_INCREMENT
}
#[inline]
pub const fn is_data_decrement(&self) -> bool {
self.controller == cc::DATA_DECREMENT
}
#[inline]
pub const fn is_rpn_nrpn_related(&self) -> bool {
matches!(
self.controller,
cc::DATA_ENTRY_MSB
| cc::DATA_ENTRY_LSB
| cc::DATA_INCREMENT
| cc::DATA_DECREMENT
| cc::NRPN_LSB
| cc::NRPN_MSB
| cc::RPN_LSB
| cc::RPN_MSB
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PitchBend {
pub channel: MidiChannel,
pub value: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ChannelPressure {
pub channel: MidiChannel,
pub pressure: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ProgramChange {
pub channel: MidiChannel,
pub program: u8,
}
#[derive(Clone, Copy)]
pub struct SysEx {
pub data: [u8; MAX_SYSEX_SIZE],
pub len: u16,
}
impl SysEx {
pub const fn new() -> Self {
Self {
data: [0u8; MAX_SYSEX_SIZE],
len: 0,
}
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
&self.data[..self.len as usize]
}
}
impl Default for SysEx {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for SysEx {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SysEx")
.field("len", &self.len)
.field("data", &self.as_slice())
.finish()
}
}
impl PartialEq for SysEx {
fn eq(&self, other: &Self) -> bool {
self.len == other.len && self.as_slice() == other.as_slice()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NoteExpressionValue {
pub note_id: NoteId,
pub expression_type: u32,
pub value: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NoteExpressionInt {
pub note_id: NoteId,
pub expression_type: u32,
pub value: u64,
}
#[derive(Clone, Copy)]
pub struct NoteExpressionText {
pub note_id: NoteId,
pub expression_type: u32,
pub text: [u8; MAX_EXPRESSION_TEXT_SIZE],
pub text_len: u8,
}
impl NoteExpressionText {
#[inline]
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.text[..self.text_len as usize]).unwrap_or("")
}
}
impl core::fmt::Debug for NoteExpressionText {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("NoteExpressionText")
.field("note_id", &self.note_id)
.field("expression_type", &self.expression_type)
.field("text", &self.as_str())
.finish()
}
}
impl PartialEq for NoteExpressionText {
fn eq(&self, other: &Self) -> bool {
self.note_id == other.note_id
&& self.expression_type == other.expression_type
&& self.text_len == other.text_len
&& self.text[..self.text_len as usize] == other.text[..other.text_len as usize]
}
}
#[derive(Clone, Copy)]
pub struct ChordInfo {
pub root: i8,
pub bass_note: i8,
pub mask: u16,
pub name: [u8; MAX_CHORD_NAME_SIZE],
pub name_len: u8,
}
impl ChordInfo {
#[inline]
pub fn name_str(&self) -> &str {
core::str::from_utf8(&self.name[..self.name_len as usize]).unwrap_or("")
}
#[inline]
pub fn is_valid(&self) -> bool {
self.root >= 0 && self.root < 12
}
}
impl core::fmt::Debug for ChordInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ChordInfo")
.field("root", &self.root)
.field("bass_note", &self.bass_note)
.field("mask", &format_args!("{:#06x}", self.mask))
.field("name", &self.name_str())
.finish()
}
}
impl PartialEq for ChordInfo {
fn eq(&self, other: &Self) -> bool {
self.root == other.root
&& self.bass_note == other.bass_note
&& self.mask == other.mask
&& self.name_len == other.name_len
&& self.name[..self.name_len as usize] == other.name[..other.name_len as usize]
}
}
#[derive(Clone, Copy)]
pub struct ScaleInfo {
pub root: i8,
pub mask: u16,
pub name: [u8; MAX_SCALE_NAME_SIZE],
pub name_len: u8,
}
impl ScaleInfo {
#[inline]
pub fn name_str(&self) -> &str {
core::str::from_utf8(&self.name[..self.name_len as usize]).unwrap_or("")
}
#[inline]
pub fn is_valid(&self) -> bool {
self.root >= 0 && self.root < 12
}
#[inline]
pub fn contains(&self, pitch_class: u8) -> bool {
if pitch_class >= 12 {
return false;
}
let rotated = if self.root >= 0 {
let shift = self.root as u32;
(self.mask >> shift) | (self.mask << (12 - shift))
} else {
self.mask
};
(rotated & (1 << pitch_class)) != 0
}
}
impl core::fmt::Debug for ScaleInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ScaleInfo")
.field("root", &self.root)
.field("mask", &format_args!("{:#06x}", self.mask))
.field("name", &self.name_str())
.finish()
}
}
impl PartialEq for ScaleInfo {
fn eq(&self, other: &Self) -> bool {
self.root == other.root
&& self.mask == other.mask
&& self.name_len == other.name_len
&& self.name[..self.name_len as usize] == other.name[..other.name_len as usize]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Midi2Controller {
pub bank: u8,
pub registered: bool,
pub index: u8,
}
impl Midi2Controller {
pub const fn new(bank: u8, registered: bool, index: u8) -> Self {
Self {
bank,
registered,
index,
}
}
pub const fn registered(bank: u8, index: u8) -> Self {
Self::new(bank, true, index)
}
pub const fn assignable(bank: u8, index: u8) -> Self {
Self::new(bank, false, index)
}
}
pub mod cc {
pub const BANK_SELECT: u8 = 0;
pub const BANK_SELECT_MSB: u8 = 0;
pub const BANK_SELECT_LSB: u8 = 32;
pub const MOD_WHEEL: u8 = 1;
pub const BREATH: u8 = 2;
pub const VOLUME: u8 = 7;
pub const PAN: u8 = 10;
pub const EXPRESSION: u8 = 11;
pub const SUSTAIN_PEDAL: u8 = 64;
pub const PORTAMENTO: u8 = 65;
pub const SOSTENUTO: u8 = 66;
pub const SOFT_PEDAL: u8 = 67;
pub const ALL_SOUND_OFF: u8 = 120;
pub const RESET_ALL_CONTROLLERS: u8 = 121;
pub const ALL_NOTES_OFF: u8 = 123;
pub const POLY_PRESSURE: u8 = 131;
pub const QUARTER_FRAME: u8 = 132;
pub const SONG_SELECT: u8 = 133;
pub const SONG_POSITION: u8 = 134;
pub const CABLE_SELECT: u8 = 135;
pub const TUNE_REQUEST: u8 = 136;
pub const CLOCK_START: u8 = 137;
pub const CLOCK_CONTINUE: u8 = 138;
pub const CLOCK_STOP: u8 = 139;
pub const ACTIVE_SENSING: u8 = 140;
pub const DATA_ENTRY_MSB: u8 = 6;
pub const DATA_ENTRY_LSB: u8 = 38;
pub const DATA_INCREMENT: u8 = 96;
pub const DATA_DECREMENT: u8 = 97;
pub const NRPN_LSB: u8 = 98;
pub const NRPN_MSB: u8 = 99;
pub const RPN_LSB: u8 = 100;
pub const RPN_MSB: u8 = 101;
}
pub mod rpn {
pub const PITCH_BEND_SENSITIVITY: u16 = 0x0000;
pub const FINE_TUNING: u16 = 0x0001;
pub const COARSE_TUNING: u16 = 0x0002;
pub const TUNING_PROGRAM: u16 = 0x0003;
pub const TUNING_BANK: u16 = 0x0004;
pub const MODULATION_DEPTH: u16 = 0x0005;
pub const MPE_CONFIGURATION: u16 = 0x0006;
pub const NULL: u16 = 0x7F7F;
#[inline]
pub const fn is_null(parameter: u16) -> bool {
parameter == NULL
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParameterNumberKind {
Rpn,
Nrpn,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ParameterNumberMessage {
pub channel: MidiChannel,
pub kind: ParameterNumberKind,
pub parameter: u16,
pub value: f32,
pub is_increment: bool,
pub is_decrement: bool,
}
impl ParameterNumberMessage {
pub const fn rpn(channel: MidiChannel, parameter: u16, value: f32) -> Self {
Self {
channel,
kind: ParameterNumberKind::Rpn,
parameter,
value,
is_increment: false,
is_decrement: false,
}
}
pub const fn nrpn(channel: MidiChannel, parameter: u16, value: f32) -> Self {
Self {
channel,
kind: ParameterNumberKind::Nrpn,
parameter,
value,
is_increment: false,
is_decrement: false,
}
}
#[inline]
pub const fn is_rpn(&self) -> bool {
matches!(self.kind, ParameterNumberKind::Rpn)
}
#[inline]
pub const fn is_nrpn(&self) -> bool {
matches!(self.kind, ParameterNumberKind::Nrpn)
}
#[inline]
pub fn is_pitch_bend_sensitivity(&self) -> bool {
self.is_rpn() && self.parameter == rpn::PITCH_BEND_SENSITIVITY
}
#[inline]
pub fn is_null(&self) -> bool {
self.is_rpn() && rpn::is_null(self.parameter)
}
#[inline]
pub fn raw_value(&self) -> u16 {
(self.value.clamp(0.0, 1.0) * 16383.0) as u16
}
#[inline]
pub fn pitch_bend_sensitivity(&self) -> (u8, u8) {
let raw = self.raw_value();
let msb = ((raw >> 7) & 0x7F) as u8;
let lsb = (raw & 0x7F) as u8;
(msb, lsb)
}
}
#[derive(Debug, Clone, Copy, Default)]
struct RpnChannelState {
parameter_msb: Option<u8>,
parameter_lsb: Option<u8>,
data_msb: Option<u8>,
data_lsb: Option<u8>,
is_rpn: bool,
}
impl RpnChannelState {
fn reset(&mut self) {
*self = Self::default();
}
fn has_parameter(&self) -> bool {
self.parameter_msb.is_some() && self.parameter_lsb.is_some()
}
fn parameter(&self) -> Option<u16> {
match (self.parameter_msb, self.parameter_lsb) {
(Some(msb), Some(lsb)) => Some(combine_14bit_raw(msb, lsb)),
_ => None,
}
}
fn data_value(&self) -> Option<u16> {
self.data_msb.map(|msb| {
let lsb = self.data_lsb.unwrap_or(0);
combine_14bit_raw(msb, lsb)
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct RpnTracker {
channels: [RpnChannelState; 16],
}
impl Default for RpnTracker {
fn default() -> Self {
Self::new()
}
}
impl RpnTracker {
pub const fn new() -> Self {
Self {
channels: [RpnChannelState {
parameter_msb: None,
parameter_lsb: None,
data_msb: None,
data_lsb: None,
is_rpn: false,
}; 16],
}
}
pub fn reset(&mut self) {
for channel in &mut self.channels {
channel.reset();
}
}
pub fn reset_channel(&mut self, channel: MidiChannel) {
if (channel as usize) < 16 {
self.channels[channel as usize].reset();
}
}
pub fn process_cc(&mut self, cc: &ControlChange) -> Option<ParameterNumberMessage> {
let channel_idx = (cc.channel as usize) & 0x0F;
let state = &mut self.channels[channel_idx];
let value_7bit = (cc.value.clamp(0.0, 1.0) * 127.0) as u8;
match cc.controller {
cc::RPN_MSB => {
state.parameter_msb = Some(value_7bit);
state.is_rpn = true;
state.data_msb = None;
state.data_lsb = None;
None
}
cc::RPN_LSB => {
state.parameter_lsb = Some(value_7bit);
state.is_rpn = true;
if let Some(parameter) = state.parameter() {
if rpn::is_null(parameter) {
state.reset();
}
}
None
}
cc::NRPN_MSB => {
state.parameter_msb = Some(value_7bit);
state.is_rpn = false;
state.data_msb = None;
state.data_lsb = None;
None
}
cc::NRPN_LSB => {
state.parameter_lsb = Some(value_7bit);
state.is_rpn = false;
None
}
cc::DATA_ENTRY_MSB => {
state.data_msb = Some(value_7bit);
self.try_emit_message(channel_idx, false, false)
}
cc::DATA_ENTRY_LSB => {
state.data_lsb = Some(value_7bit);
if self.channels[channel_idx].data_msb.is_some() {
self.try_emit_message(channel_idx, false, false)
} else {
None
}
}
cc::DATA_INCREMENT => {
if self.channels[channel_idx].has_parameter() {
self.try_emit_message(channel_idx, true, false)
} else {
None
}
}
cc::DATA_DECREMENT => {
if self.channels[channel_idx].has_parameter() {
self.try_emit_message(channel_idx, false, true)
} else {
None
}
}
_ => None,
}
}
fn try_emit_message(
&self,
channel_idx: usize,
is_increment: bool,
is_decrement: bool,
) -> Option<ParameterNumberMessage> {
let state = &self.channels[channel_idx];
let parameter = state.parameter()?;
let value = if is_increment || is_decrement {
0.0 } else {
let raw = state.data_value()?;
raw as f32 / 16383.0
};
Some(ParameterNumberMessage {
channel: channel_idx as u8,
kind: if state.is_rpn {
ParameterNumberKind::Rpn
} else {
ParameterNumberKind::Nrpn
},
parameter,
value,
is_increment,
is_decrement,
})
}
pub fn current_parameter(&self, channel: MidiChannel) -> Option<(ParameterNumberKind, u16)> {
let state = &self.channels[(channel as usize) & 0x0F];
state.parameter().map(|p| {
let kind = if state.is_rpn {
ParameterNumberKind::Rpn
} else {
ParameterNumberKind::Nrpn
};
(kind, p)
})
}
}
#[inline]
pub fn combine_14bit_cc(msb_value: f32, lsb_value: f32) -> f32 {
let msb = (msb_value.clamp(0.0, 1.0) * 127.0) as u16;
let lsb = (lsb_value.clamp(0.0, 1.0) * 127.0) as u16;
let combined = (msb << 7) | (lsb & 0x7F);
combined as f32 / 16383.0
}
#[inline]
pub fn split_14bit_cc(value: f32) -> (f32, f32) {
let raw = (value.clamp(0.0, 1.0) * 16383.0) as u16;
let msb = ((raw >> 7) & 0x7F) as f32 / 127.0;
let lsb = (raw & 0x7F) as f32 / 127.0;
(msb, lsb)
}
#[inline]
pub const fn combine_14bit_raw(msb: u8, lsb: u8) -> u16 {
((msb as u16) << 7) | ((lsb as u16) & 0x7F)
}
#[inline]
pub const fn split_14bit_raw(value: u16) -> (u8, u8) {
let msb = ((value >> 7) & 0x7F) as u8;
let lsb = (value & 0x7F) as u8;
(msb, lsb)
}
pub mod note_expression {
pub const VOLUME: u32 = 0;
pub const PAN: u32 = 1;
pub const TUNING: u32 = 2;
pub const VIBRATO: u32 = 3;
pub const EXPRESSION: u32 = 4;
pub const BRIGHTNESS: u32 = 5;
pub const TEXT: u32 = 6;
pub const PHONEME: u32 = 7;
pub const CUSTOM_START: u32 = 100000;
pub const CUSTOM_END: u32 = 200000;
pub const INVALID: u32 = u32::MAX;
}
#[derive(Debug, Clone, PartialEq)]
pub enum MidiEventKind {
NoteOn(NoteOn),
NoteOff(NoteOff),
PolyPressure(PolyPressure),
ControlChange(ControlChange),
PitchBend(PitchBend),
ChannelPressure(ChannelPressure),
ProgramChange(ProgramChange),
SysEx(Box<SysEx>),
NoteExpressionValue(NoteExpressionValue),
NoteExpressionInt(NoteExpressionInt),
NoteExpressionText(NoteExpressionText),
ChordInfo(ChordInfo),
ScaleInfo(ScaleInfo),
}
#[derive(Debug, Clone, PartialEq)]
pub struct MidiEvent {
pub sample_offset: u32,
pub event: MidiEventKind,
}
impl Default for MidiEvent {
fn default() -> Self {
Self {
sample_offset: 0,
event: MidiEventKind::NoteOff(NoteOff {
channel: 0,
pitch: 0,
velocity: 0.0,
note_id: -1,
tuning: 0.0,
}),
}
}
}
impl MidiEvent {
pub const fn note_on(
sample_offset: u32,
channel: MidiChannel,
pitch: MidiNote,
velocity: f32,
note_id: NoteId,
tuning: f32,
length: i32,
) -> Self {
Self {
sample_offset,
event: MidiEventKind::NoteOn(NoteOn {
channel,
pitch,
velocity,
note_id,
tuning,
length,
}),
}
}
pub const fn note_off(
sample_offset: u32,
channel: MidiChannel,
pitch: MidiNote,
velocity: f32,
note_id: NoteId,
tuning: f32,
) -> Self {
Self {
sample_offset,
event: MidiEventKind::NoteOff(NoteOff {
channel,
pitch,
velocity,
note_id,
tuning,
}),
}
}
pub const fn poly_pressure(
sample_offset: u32,
channel: MidiChannel,
pitch: MidiNote,
pressure: f32,
note_id: NoteId,
) -> Self {
Self {
sample_offset,
event: MidiEventKind::PolyPressure(PolyPressure {
channel,
pitch,
pressure,
note_id,
}),
}
}
pub const fn control_change(
sample_offset: u32,
channel: MidiChannel,
controller: u8,
value: f32,
) -> Self {
Self {
sample_offset,
event: MidiEventKind::ControlChange(ControlChange {
channel,
controller,
value,
}),
}
}
pub const fn pitch_bend(sample_offset: u32, channel: MidiChannel, value: f32) -> Self {
Self {
sample_offset,
event: MidiEventKind::PitchBend(PitchBend { channel, value }),
}
}
pub const fn channel_pressure(
sample_offset: u32,
channel: MidiChannel,
pressure: f32,
) -> Self {
Self {
sample_offset,
event: MidiEventKind::ChannelPressure(ChannelPressure { channel, pressure }),
}
}
pub const fn program_change(sample_offset: u32, channel: MidiChannel, program: u8) -> Self {
Self {
sample_offset,
event: MidiEventKind::ProgramChange(ProgramChange { channel, program }),
}
}
#[inline]
pub fn from_midi1_bytes(
sample_offset: u32,
status: u8,
channel: MidiChannel,
data1: u8,
data2: u8,
) -> Option<Self> {
match status {
0x80 => Some(Self::note_off(
sample_offset,
channel,
data1,
data2 as f32 / 127.0,
data1 as NoteId,
0.0,
)),
0x90 => {
if data2 == 0 {
Some(Self::note_off(
sample_offset,
channel,
data1,
0.0,
data1 as NoteId,
0.0,
))
} else {
Some(Self::note_on(
sample_offset,
channel,
data1,
data2 as f32 / 127.0,
data1 as NoteId,
0.0,
0,
))
}
}
0xA0 => Some(Self::poly_pressure(
sample_offset,
channel,
data1,
data2 as f32 / 127.0,
data1 as NoteId,
)),
0xB0 => Some(Self::control_change(
sample_offset,
channel,
data1,
data2 as f32 / 127.0,
)),
0xC0 => Some(Self::program_change(sample_offset, channel, data1)),
0xD0 => Some(Self::channel_pressure(
sample_offset,
channel,
data1 as f32 / 127.0,
)),
0xE0 => {
let raw_value = ((data2 as u16) << 7) | (data1 as u16);
let normalized = (raw_value as f32 - 8192.0) / 8192.0;
Some(Self::pitch_bend(sample_offset, channel, normalized))
}
_ => None, }
}
pub fn sysex(sample_offset: u32, data: &[u8]) -> Self {
let mut sysex = SysEx::new();
let copy_len = data.len().min(MAX_SYSEX_SIZE);
sysex.data[..copy_len].copy_from_slice(&data[..copy_len]);
sysex.len = copy_len as u16;
Self {
sample_offset,
event: MidiEventKind::SysEx(Box::new(sysex)),
}
}
pub const fn note_expression_value(
sample_offset: u32,
note_id: NoteId,
expression_type: u32,
value: f64,
) -> Self {
Self {
sample_offset,
event: MidiEventKind::NoteExpressionValue(NoteExpressionValue {
note_id,
expression_type,
value,
}),
}
}
pub const fn note_expression_int(
sample_offset: u32,
note_id: NoteId,
expression_type: u32,
value: u64,
) -> Self {
Self {
sample_offset,
event: MidiEventKind::NoteExpressionInt(NoteExpressionInt {
note_id,
expression_type,
value,
}),
}
}
pub fn note_expression_text(
sample_offset: u32,
note_id: NoteId,
expression_type: u32,
text: &str,
) -> Self {
let mut expr = NoteExpressionText {
note_id,
expression_type,
text: [0u8; MAX_EXPRESSION_TEXT_SIZE],
text_len: 0,
};
let bytes = text.as_bytes();
let copy_len = bytes.len().min(MAX_EXPRESSION_TEXT_SIZE);
expr.text[..copy_len].copy_from_slice(&bytes[..copy_len]);
expr.text_len = copy_len as u8;
Self {
sample_offset,
event: MidiEventKind::NoteExpressionText(expr),
}
}
pub fn chord_info(
sample_offset: u32,
root: i8,
bass_note: i8,
mask: u16,
name: &str,
) -> Self {
let mut info = ChordInfo {
root,
bass_note,
mask,
name: [0u8; MAX_CHORD_NAME_SIZE],
name_len: 0,
};
let bytes = name.as_bytes();
let copy_len = bytes.len().min(MAX_CHORD_NAME_SIZE);
info.name[..copy_len].copy_from_slice(&bytes[..copy_len]);
info.name_len = copy_len as u8;
Self {
sample_offset,
event: MidiEventKind::ChordInfo(info),
}
}
pub fn scale_info(sample_offset: u32, root: i8, mask: u16, name: &str) -> Self {
let mut info = ScaleInfo {
root,
mask,
name: [0u8; MAX_SCALE_NAME_SIZE],
name_len: 0,
};
let bytes = name.as_bytes();
let copy_len = bytes.len().min(MAX_SCALE_NAME_SIZE);
info.name[..copy_len].copy_from_slice(&bytes[..copy_len]);
info.name_len = copy_len as u8;
Self {
sample_offset,
event: MidiEventKind::ScaleInfo(info),
}
}
pub fn with(self, kind: MidiEventKind) -> Self {
MidiEvent {
sample_offset: self.sample_offset,
event: kind,
}
}
}
pub const MAX_MIDI_EVENTS: usize = 1024;
#[derive(Debug)]
pub struct MidiBuffer {
events: [MidiEvent; MAX_MIDI_EVENTS],
len: usize,
overflowed: bool,
}
impl MidiBuffer {
pub fn new() -> Self {
Self {
events: std::array::from_fn(|_| MidiEvent::default()),
len: 0,
overflowed: false,
}
}
#[inline]
pub fn clear(&mut self) {
self.len = 0;
self.overflowed = false;
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn has_overflowed(&self) -> bool {
self.overflowed
}
#[inline]
pub fn push(&mut self, event: MidiEvent) -> bool {
if self.len < MAX_MIDI_EVENTS {
self.events[self.len] = event;
self.len += 1;
true
} else {
self.overflowed = true;
false
}
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &MidiEvent> {
self.events[..self.len].iter()
}
#[inline]
pub fn as_slice(&self) -> &[MidiEvent] {
&self.events[..self.len]
}
}
impl Default for MidiBuffer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct NoteExpressionTypeFlags(pub i32);
impl NoteExpressionTypeFlags {
pub const NONE: Self = Self(0);
pub const IS_BIPOLAR: Self = Self(1 << 0);
pub const IS_ONE_SHOT: Self = Self(1 << 1);
pub const IS_ABSOLUTE: Self = Self(1 << 2);
pub const ASSOCIATED_PARAMETER_ID_VALID: Self = Self(1 << 3);
pub const fn contains(&self, flag: Self) -> bool {
(self.0 & flag.0) != 0
}
pub const fn or(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct NoteExpressionValueDesc {
pub minimum: f64,
pub maximum: f64,
pub default_value: f64,
pub step_count: i32,
}
impl NoteExpressionValueDesc {
pub const fn unipolar() -> Self {
Self {
minimum: 0.0,
maximum: 1.0,
default_value: 0.0,
step_count: 0,
}
}
pub const fn bipolar() -> Self {
Self {
minimum: -1.0,
maximum: 1.0,
default_value: 0.0,
step_count: 0,
}
}
pub const fn tuning(range_semitones: f64) -> Self {
Self {
minimum: -range_semitones,
maximum: range_semitones,
default_value: 0.0,
step_count: 0,
}
}
}
pub const MAX_NOTE_EXPRESSION_TITLE_SIZE: usize = 64;
#[derive(Clone, Copy)]
pub struct NoteExpressionTypeInfo {
pub type_id: u32,
pub title: [u8; MAX_NOTE_EXPRESSION_TITLE_SIZE],
pub title_len: u8,
pub short_title: [u8; MAX_NOTE_EXPRESSION_TITLE_SIZE],
pub short_title_len: u8,
pub units: [u8; MAX_NOTE_EXPRESSION_TITLE_SIZE],
pub units_len: u8,
pub unit_id: i32,
pub value_desc: NoteExpressionValueDesc,
pub associated_parameter_id: i32,
pub flags: NoteExpressionTypeFlags,
}
impl NoteExpressionTypeInfo {
pub fn new(type_id: u32, title: &str, short_title: &str) -> Self {
let mut info = Self {
type_id,
title: [0u8; MAX_NOTE_EXPRESSION_TITLE_SIZE],
title_len: 0,
short_title: [0u8; MAX_NOTE_EXPRESSION_TITLE_SIZE],
short_title_len: 0,
units: [0u8; MAX_NOTE_EXPRESSION_TITLE_SIZE],
units_len: 0,
unit_id: -1,
value_desc: NoteExpressionValueDesc::unipolar(),
associated_parameter_id: -1,
flags: NoteExpressionTypeFlags::NONE,
};
info.set_title(title);
info.set_short_title(short_title);
info
}
pub fn set_title(&mut self, title: &str) {
let bytes = title.as_bytes();
let len = bytes.len().min(MAX_NOTE_EXPRESSION_TITLE_SIZE);
self.title[..len].copy_from_slice(&bytes[..len]);
self.title_len = len as u8;
}
pub fn set_short_title(&mut self, short_title: &str) {
let bytes = short_title.as_bytes();
let len = bytes.len().min(MAX_NOTE_EXPRESSION_TITLE_SIZE);
self.short_title[..len].copy_from_slice(&bytes[..len]);
self.short_title_len = len as u8;
}
pub fn set_units(&mut self, units: &str) {
let bytes = units.as_bytes();
let len = bytes.len().min(MAX_NOTE_EXPRESSION_TITLE_SIZE);
self.units[..len].copy_from_slice(&bytes[..len]);
self.units_len = len as u8;
}
pub fn title_str(&self) -> &str {
core::str::from_utf8(&self.title[..self.title_len as usize]).unwrap_or("")
}
pub fn short_title_str(&self) -> &str {
core::str::from_utf8(&self.short_title[..self.short_title_len as usize]).unwrap_or("")
}
pub fn units_str(&self) -> &str {
core::str::from_utf8(&self.units[..self.units_len as usize]).unwrap_or("")
}
pub fn with_value_desc(mut self, desc: NoteExpressionValueDesc) -> Self {
self.value_desc = desc;
self
}
pub fn with_flags(mut self, flags: NoteExpressionTypeFlags) -> Self {
self.flags = flags;
self
}
pub fn with_units(mut self, units: &str) -> Self {
self.set_units(units);
self
}
pub fn with_associated_parameter(mut self, parameter_id: i32) -> Self {
self.associated_parameter_id = parameter_id;
self.flags = self.flags.or(NoteExpressionTypeFlags::ASSOCIATED_PARAMETER_ID_VALID);
self
}
}
impl core::fmt::Debug for NoteExpressionTypeInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("NoteExpressionTypeInfo")
.field("type_id", &self.type_id)
.field("title", &self.title_str())
.field("short_title", &self.short_title_str())
.field("units", &self.units_str())
.field("value_desc", &self.value_desc)
.field("flags", &self.flags)
.finish()
}
}
impl Default for NoteExpressionTypeInfo {
fn default() -> Self {
Self::new(note_expression::INVALID, "", "")
}
}
pub mod keyswitch_type {
pub const NOTE_ON_KEY: u32 = 0;
pub const ON_THE_FLY: u32 = 1;
pub const ON_RELEASE: u32 = 2;
pub const KEY_RANGE: u32 = 3;
}
pub const MAX_KEYSWITCH_TITLE_SIZE: usize = 64;
#[derive(Clone, Copy)]
pub struct KeyswitchInfo {
pub type_id: u32,
pub title: [u8; MAX_KEYSWITCH_TITLE_SIZE],
pub title_len: u8,
pub short_title: [u8; MAX_KEYSWITCH_TITLE_SIZE],
pub short_title_len: u8,
pub keyswitch_min: i32,
pub keyswitch_max: i32,
pub key_remapped: i32,
pub unit_id: i32,
pub flags: i32,
}
impl KeyswitchInfo {
pub fn new(type_id: u32, title: &str, key: i32) -> Self {
let mut info = Self {
type_id,
title: [0u8; MAX_KEYSWITCH_TITLE_SIZE],
title_len: 0,
short_title: [0u8; MAX_KEYSWITCH_TITLE_SIZE],
short_title_len: 0,
keyswitch_min: key,
keyswitch_max: key,
key_remapped: -1,
unit_id: -1,
flags: 0,
};
info.set_title(title);
info
}
pub fn key_range(type_id: u32, title: &str, min_key: i32, max_key: i32) -> Self {
let mut info = Self::new(type_id, title, min_key);
info.keyswitch_max = max_key;
info
}
pub fn set_title(&mut self, title: &str) {
let bytes = title.as_bytes();
let len = bytes.len().min(MAX_KEYSWITCH_TITLE_SIZE);
self.title[..len].copy_from_slice(&bytes[..len]);
self.title_len = len as u8;
}
pub fn set_short_title(&mut self, short_title: &str) {
let bytes = short_title.as_bytes();
let len = bytes.len().min(MAX_KEYSWITCH_TITLE_SIZE);
self.short_title[..len].copy_from_slice(&bytes[..len]);
self.short_title_len = len as u8;
}
pub fn title_str(&self) -> &str {
core::str::from_utf8(&self.title[..self.title_len as usize]).unwrap_or("")
}
pub fn short_title_str(&self) -> &str {
core::str::from_utf8(&self.short_title[..self.short_title_len as usize]).unwrap_or("")
}
pub fn with_short_title(mut self, short_title: &str) -> Self {
self.set_short_title(short_title);
self
}
pub fn with_key_remapped(mut self, key: i32) -> Self {
self.key_remapped = key;
self
}
}
impl core::fmt::Debug for KeyswitchInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("KeyswitchInfo")
.field("type_id", &self.type_id)
.field("title", &self.title_str())
.field("keyswitch_min", &self.keyswitch_min)
.field("keyswitch_max", &self.keyswitch_max)
.finish()
}
}
impl Default for KeyswitchInfo {
fn default() -> Self {
Self::new(keyswitch_type::NOTE_ON_KEY, "", 0)
}
}
pub mod physical_ui {
pub const X_MOVEMENT: u32 = 0;
pub const Y_MOVEMENT: u32 = 1;
pub const PRESSURE: u32 = 2;
pub const TYPE_FACE: u32 = 3;
pub const INVALID: u32 = u32::MAX;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PhysicalUIMap {
pub physical_ui_type_id: u32,
pub note_expression_type_id: u32,
}
impl PhysicalUIMap {
pub const fn new(physical_ui_type_id: u32, note_expression_type_id: u32) -> Self {
Self {
physical_ui_type_id,
note_expression_type_id,
}
}
pub const fn x_axis(note_expression_type_id: u32) -> Self {
Self::new(physical_ui::X_MOVEMENT, note_expression_type_id)
}
pub const fn y_axis(note_expression_type_id: u32) -> Self {
Self::new(physical_ui::Y_MOVEMENT, note_expression_type_id)
}
pub const fn pressure(note_expression_type_id: u32) -> Self {
Self::new(physical_ui::PRESSURE, note_expression_type_id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MpeInputDeviceSettings {
pub master_channel: i32,
pub member_begin_channel: i32,
pub member_end_channel: i32,
}
impl Default for MpeInputDeviceSettings {
fn default() -> Self {
Self {
master_channel: 0,
member_begin_channel: 1,
member_end_channel: 14,
}
}
}
impl MpeInputDeviceSettings {
pub const fn lower_zone() -> Self {
Self {
master_channel: 0,
member_begin_channel: 1,
member_end_channel: 14,
}
}
pub const fn upper_zone() -> Self {
Self {
master_channel: 15,
member_begin_channel: 14,
member_end_channel: 1,
}
}
pub const fn new(master: i32, begin: i32, end: i32) -> Self {
Self {
master_channel: master,
member_begin_channel: begin,
member_end_channel: end,
}
}
}