use crate::note::Note;
use serde::{Deserialize, Serialize};
const CLOCK: u8 = 0xf8;
const START: u8 = 0xfa;
const CONTINUE: u8 = 0xfb;
const STOP: u8 = 0xfc;
const NOTE_ON: u8 = 0x90;
const NOTE_OFF: u8 = 0x80;
const CC: u8 = 0xB0;
const PC: u8 = 0xC0;
const PITCH_BEND: u8 = 0xE0;
pub(crate) fn is_valid_channel(channel: u8) -> bool {
(1..=16).contains(&channel)
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize, Hash)]
pub struct MidiNote {
pub note: Note,
pub octave: u8,
pub vel: u8,
}
impl MidiNote {
pub fn new(note: Note, octave: u8, vel: u8) -> Self {
Self { note, octave, vel }
}
pub fn from_midi_value(midi_value: u8, vel: u8) -> Self {
let octave = midi_value / 12;
let note = Note::from(midi_value % 12);
Self::new(note, octave, vel)
}
pub fn transpose(&self, transpose: i8) -> Self {
let (note, octave) = self.note.add_semitone(self.octave, transpose);
Self {
note,
octave,
vel: self.vel,
}
}
pub fn midi_value(&self) -> u8 {
u8::from(self.note) + 12 * self.octave
}
}
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum MidiMessage {
NoteOff {
channel: u8,
note: MidiNote,
},
NoteOn {
channel: u8,
note: MidiNote,
},
CC {
channel: u8,
controller: u8,
value: u8,
},
PC {
channel: u8,
value: u8,
},
PitchBend {
channel: u8,
value: u16,
},
Clock,
Start,
Continue,
Stop,
}
impl MidiMessage {
pub fn parse(bytes: &[u8]) -> Option<MidiMessage> {
if bytes.len() == 1 {
match bytes[0] {
CLOCK => Some(MidiMessage::Clock),
START => Some(MidiMessage::Start),
CONTINUE => Some(MidiMessage::Continue),
STOP => Some(MidiMessage::Stop),
_ => None,
}
} else if bytes.len() == 2 && bytes[0] & 0xF0 == PC {
let channel = (bytes[0] & 0x0F) + 1;
if is_valid_channel(channel) {
Some(MidiMessage::PC {
channel,
value: bytes[1],
})
} else {
None
}
} else if bytes.len() == 3 {
let channel = (bytes[0] & 0x0F) + 1;
if is_valid_channel(channel) {
match bytes[0] & 0xF0 {
NOTE_OFF => Some(MidiMessage::NoteOff {
channel,
note: MidiNote::from_midi_value(bytes[1], bytes[2]),
}),
NOTE_ON => Some(MidiMessage::NoteOn {
channel,
note: MidiNote::from_midi_value(bytes[1], bytes[2]),
}),
CC => Some(MidiMessage::CC {
channel,
controller: bytes[1],
value: bytes[2],
}),
PITCH_BEND => Some(MidiMessage::PitchBend {
channel,
value: ((bytes[2] as u16) << 7) | (bytes[1] as u16),
}),
_ => None,
}
} else {
None
}
} else {
None
}
}
}
pub fn param_value(v: f32) -> u8 {
if v < -1.0 {
return 0;
}
if v > 1.0 {
return 127;
}
63 + (v * 63.0) as u8
}