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]
|
[dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
hex = "0.4.3"
|
||||||
midir = "0.10.0"
|
midir = "0.10.0"
|
||||||
|
nom = "7.1.3"
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::io::stdin;
|
use std::io::stdin;
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use midir::{MidiInput, MidiInputPort};
|
use midir::{MidiInput, MidiInputPort};
|
||||||
|
@ -32,7 +31,8 @@ fn run() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_midi_event(timestamp: u64, message: &[u8], _extra_data: &mut ()) {
|
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
|
/// 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> {
|
fn find_first_midi_device(midi_in: &MidiInput) -> Result<MidiInputPort> {
|
||||||
let ports = midi_in.ports();
|
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| {
|
let mut physical_ports = ports.iter().filter(|p| {
|
||||||
midi_in
|
midi_in
|
||||||
.port_name(p)
|
.port_name(p)
|
||||||
|
@ -51,9 +56,3 @@ fn find_first_midi_device(midi_in: &MidiInput) -> Result<MidiInputPort> {
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(anyhow!("no physical MIDI devices found"))
|
.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