first parsing of MIDI: parse note on/off
This commit is contained in:
parent
56234e4e07
commit
7983bdab0b
5 changed files with 109 additions and 8 deletions
|
@ -5,5 +5,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.86"
|
||||
hex = "0.4.3"
|
||||
midir = "0.10.0"
|
||||
nom = "7.1.3"
|
||||
thiserror = "1.0.63"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::io::stdin;
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use midir::{MidiInput, MidiInputPort};
|
||||
|
@ -32,7 +31,8 @@ fn run() -> Result<()> {
|
|||
}
|
||||
|
||||
fn handle_midi_event(timestamp: u64, message: &[u8], _extra_data: &mut ()) {
|
||||
println!("{timestamp} > {message:?}")
|
||||
let hex_msg = hex::encode(message);
|
||||
println!("{timestamp} > {hex_msg}");
|
||||
}
|
||||
|
||||
/// Finds the first MIDI input port which corresponds to a physical MIDI device
|
||||
|
@ -40,6 +40,11 @@ fn handle_midi_event(timestamp: u64, message: &[u8], _extra_data: &mut ()) {
|
|||
fn find_first_midi_device(midi_in: &MidiInput) -> Result<MidiInputPort> {
|
||||
let ports = midi_in.ports();
|
||||
|
||||
// "Midi Through" is automatically created by the snd-seq-dummy kernel module.
|
||||
// We can ignore it, since it's not a physical MIDI device, which is what we
|
||||
// are looking for.
|
||||
//
|
||||
// Source: https://www.reddit.com/r/linuxaudio/comments/jsrl31/comment/gc16qwu/
|
||||
let mut physical_ports = ports.iter().filter(|p| {
|
||||
midi_in
|
||||
.port_name(p)
|
||||
|
@ -51,9 +56,3 @@ fn find_first_midi_device(midi_in: &MidiInput) -> Result<MidiInputPort> {
|
|||
.cloned()
|
||||
.ok_or(anyhow!("no physical MIDI devices found"))
|
||||
}
|
||||
|
||||
// "Midi Through" is automatically created bya the snd-seq-dummy kernel module.
|
||||
// We can ignore it, since it's not a physical MIDI device, which is what we
|
||||
// are looking for.
|
||||
//
|
||||
// Source: https://www.reddit.com/r/linuxaudio/comments/jsrl31/comment/gc16qwu/
|
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod midi;
|
||||
pub mod parser;
|
33
src/midi.rs
Normal file
33
src/midi.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Message {
|
||||
Voice(VoiceMessage),
|
||||
System(SystemMessage),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct VoiceMessage {
|
||||
pub category: VoiceCategory,
|
||||
pub channel: u8,
|
||||
}
|
||||
|
||||
impl VoiceMessage {
|
||||
pub fn new(category: VoiceCategory, channel: u8) -> VoiceMessage {
|
||||
VoiceMessage { category, channel }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum VoiceCategory {
|
||||
NoteOff { note: u8, velocity: u8 },
|
||||
NoteOn { note: u8, velocity: u8 },
|
||||
AfterTouch,
|
||||
ControlChange,
|
||||
ProgramChange,
|
||||
ChannelPressure,
|
||||
PitchWheel,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum SystemMessage {}
|
65
src/parser.rs
Normal file
65
src/parser.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use nom::{bytes::complete::take, IResult};
|
||||
|
||||
use crate::midi::{Message, VoiceCategory, VoiceMessage};
|
||||
|
||||
pub fn parse_message(bytes: &[u8]) -> IResult<&[u8], Message> {
|
||||
let (bytes, status_byte) = take(1usize)(bytes)?;
|
||||
let status_byte = status_byte[0];
|
||||
|
||||
if status_byte < 0xF0 {
|
||||
let (bytes, vm) = parse_voice_message(status_byte, bytes)?;
|
||||
Ok((bytes, Message::Voice(vm)))
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_voice_message(status_byte: u8, remainder: &[u8]) -> IResult<&[u8], VoiceMessage> {
|
||||
let category_nibble = 0xf0 & status_byte;
|
||||
let channel = 0x0f & status_byte;
|
||||
|
||||
let (remainder, category) = match category_nibble {
|
||||
0x80 => parse_voice_note(remainder, true)?,
|
||||
0x90 => parse_voice_note(remainder, false)?,
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
Ok((remainder, VoiceMessage::new(category, channel)))
|
||||
}
|
||||
|
||||
pub fn parse_voice_note(bytes: &[u8], off: bool) -> IResult<&[u8], VoiceCategory> {
|
||||
let (remainder, data) = take(2usize)(bytes)?;
|
||||
|
||||
let note = data[0];
|
||||
let velocity = data[1];
|
||||
|
||||
let category = if velocity == 0 || off {
|
||||
VoiceCategory::NoteOff { note, velocity }
|
||||
} else {
|
||||
VoiceCategory::NoteOn { note, velocity }
|
||||
};
|
||||
|
||||
Ok((remainder, category))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::midi::VoiceMessage;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parses_note_on_message() {
|
||||
let msg = [0x90, 0x24, 0x51];
|
||||
let (remainder, parsed) = parse_message(&msg).unwrap();
|
||||
|
||||
let note = 0x24;
|
||||
let velocity = 0x51;
|
||||
let expected = Message::Voice(VoiceMessage::new(
|
||||
VoiceCategory::NoteOn { note, velocity },
|
||||
0,
|
||||
));
|
||||
assert_eq!(parsed, expected,);
|
||||
assert_eq!(remainder.len(), 0);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue