More robust midi implementation
Some checks failed
Deploy Website / deploy (push) Failing after 4m58s
Some checks failed
Deploy Website / deploy (push) Failing after 4m58s
This commit is contained in:
@@ -53,16 +53,16 @@ pub enum AudioCommand {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MidiCommand {
|
||||
NoteOn { channel: u8, note: u8, velocity: u8 },
|
||||
NoteOff { channel: u8, note: u8 },
|
||||
CC { channel: u8, cc: u8, value: u8 },
|
||||
PitchBend { channel: u8, value: u16 },
|
||||
Pressure { channel: u8, value: u8 },
|
||||
ProgramChange { channel: u8, program: u8 },
|
||||
Clock,
|
||||
Start,
|
||||
Stop,
|
||||
Continue,
|
||||
NoteOn { device: u8, channel: u8, note: u8, velocity: u8 },
|
||||
NoteOff { device: u8, channel: u8, note: u8 },
|
||||
CC { device: u8, channel: u8, cc: u8, value: u8 },
|
||||
PitchBend { device: u8, channel: u8, value: u16 },
|
||||
Pressure { device: u8, channel: u8, value: u8 },
|
||||
ProgramChange { device: u8, channel: u8, program: u8 },
|
||||
Clock { device: u8 },
|
||||
Start { device: u8 },
|
||||
Stop { device: u8 },
|
||||
Continue { device: u8 },
|
||||
}
|
||||
|
||||
pub enum SeqCommand {
|
||||
@@ -484,7 +484,7 @@ pub(crate) struct SequencerState {
|
||||
key_cache: KeyCache,
|
||||
buf_audio_commands: Vec<TimestampedCommand>,
|
||||
cc_memory: Option<CcMemory>,
|
||||
active_notes: HashMap<(u8, u8), ActiveNote>,
|
||||
active_notes: HashMap<(u8, u8, u8), ActiveNote>,
|
||||
}
|
||||
|
||||
impl SequencerState {
|
||||
@@ -951,12 +951,12 @@ fn sequencer_loop(
|
||||
if let Some((midi_cmd, dur)) = parse_midi_command(&tsc.cmd) {
|
||||
match midi_tx.load().try_send(midi_cmd.clone()) {
|
||||
Ok(()) => {
|
||||
if let (MidiCommand::NoteOn { channel, note, .. }, Some(dur_secs)) =
|
||||
if let (MidiCommand::NoteOn { device, channel, note, .. }, Some(dur_secs)) =
|
||||
(&midi_cmd, dur)
|
||||
{
|
||||
let dur_us = (dur_secs * 1_000_000.0) as i64;
|
||||
seq_state.active_notes.insert(
|
||||
(*channel, *note),
|
||||
(*device, *channel, *note),
|
||||
ActiveNote {
|
||||
off_time_us: current_time_us + dur_us,
|
||||
start_time_us: current_time_us,
|
||||
@@ -985,22 +985,24 @@ fn sequencer_loop(
|
||||
const MAX_NOTE_DURATION_US: i64 = 30_000_000; // 30 second safety timeout
|
||||
|
||||
if output.flush_midi_notes {
|
||||
for ((channel, note), _) in seq_state.active_notes.drain() {
|
||||
let _ = midi_tx.load().try_send(MidiCommand::NoteOff { channel, note });
|
||||
for ((device, channel, note), _) in seq_state.active_notes.drain() {
|
||||
let _ = midi_tx.load().try_send(MidiCommand::NoteOff { device, channel, note });
|
||||
}
|
||||
// Send MIDI panic (CC 123 = All Notes Off) on all 16 channels
|
||||
for chan in 0..16u8 {
|
||||
let _ = midi_tx
|
||||
.load()
|
||||
.try_send(MidiCommand::CC { channel: chan, cc: 123, value: 0 });
|
||||
// Send MIDI panic (CC 123 = All Notes Off) on all 16 channels for all devices
|
||||
for dev in 0..4u8 {
|
||||
for chan in 0..16u8 {
|
||||
let _ = midi_tx
|
||||
.load()
|
||||
.try_send(MidiCommand::CC { device: dev, channel: chan, cc: 123, value: 0 });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
seq_state.active_notes.retain(|&(channel, note), active| {
|
||||
seq_state.active_notes.retain(|&(device, channel, note), active| {
|
||||
let should_release = current_time_us >= active.off_time_us;
|
||||
let timed_out = (current_time_us - active.start_time_us) > MAX_NOTE_DURATION_US;
|
||||
|
||||
if should_release || timed_out {
|
||||
let _ = midi_tx.load().try_send(MidiCommand::NoteOff { channel, note });
|
||||
let _ = midi_tx.load().try_send(MidiCommand::NoteOff { device, channel, note });
|
||||
false
|
||||
} else {
|
||||
true
|
||||
@@ -1026,15 +1028,25 @@ fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option<f64>)> {
|
||||
if parts.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let find_param = |key: &str| -> Option<&str> {
|
||||
parts.iter()
|
||||
.position(|&s| s == key)
|
||||
.and_then(|i| parts.get(i + 1).copied())
|
||||
};
|
||||
|
||||
let device: u8 = find_param("dev").and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
|
||||
match parts[1] {
|
||||
"note" => {
|
||||
// /midi/note/<note>/vel/<vel>/chan/<chan>/dur/<dur>
|
||||
// /midi/note/<note>/vel/<vel>/chan/<chan>/dur/<dur>/dev/<dev>
|
||||
let note: u8 = parts.get(2)?.parse().ok()?;
|
||||
let vel: u8 = parts.get(4)?.parse().ok()?;
|
||||
let chan: u8 = parts.get(6)?.parse().ok()?;
|
||||
let dur: Option<f64> = parts.get(8).and_then(|s| s.parse().ok());
|
||||
let vel: u8 = find_param("vel")?.parse().ok()?;
|
||||
let chan: u8 = find_param("chan")?.parse().ok()?;
|
||||
let dur: Option<f64> = find_param("dur").and_then(|s| s.parse().ok());
|
||||
Some((
|
||||
MidiCommand::NoteOn {
|
||||
device,
|
||||
channel: chan,
|
||||
note,
|
||||
velocity: vel,
|
||||
@@ -1043,12 +1055,13 @@ fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option<f64>)> {
|
||||
))
|
||||
}
|
||||
"cc" => {
|
||||
// /midi/cc/<cc>/<val>/chan/<chan>
|
||||
// /midi/cc/<cc>/<val>/chan/<chan>/dev/<dev>
|
||||
let cc: u8 = parts.get(2)?.parse().ok()?;
|
||||
let val: u8 = parts.get(3)?.parse().ok()?;
|
||||
let chan: u8 = parts.get(5)?.parse().ok()?;
|
||||
let chan: u8 = find_param("chan")?.parse().ok()?;
|
||||
Some((
|
||||
MidiCommand::CC {
|
||||
device,
|
||||
channel: chan,
|
||||
cc,
|
||||
value: val,
|
||||
@@ -1057,27 +1070,27 @@ fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option<f64>)> {
|
||||
))
|
||||
}
|
||||
"bend" => {
|
||||
// /midi/bend/<value>/chan/<chan>
|
||||
// /midi/bend/<value>/chan/<chan>/dev/<dev>
|
||||
let value: u16 = parts.get(2)?.parse().ok()?;
|
||||
let chan: u8 = parts.get(4)?.parse().ok()?;
|
||||
Some((MidiCommand::PitchBend { channel: chan, value }, None))
|
||||
let chan: u8 = find_param("chan")?.parse().ok()?;
|
||||
Some((MidiCommand::PitchBend { device, channel: chan, value }, None))
|
||||
}
|
||||
"pressure" => {
|
||||
// /midi/pressure/<value>/chan/<chan>
|
||||
// /midi/pressure/<value>/chan/<chan>/dev/<dev>
|
||||
let value: u8 = parts.get(2)?.parse().ok()?;
|
||||
let chan: u8 = parts.get(4)?.parse().ok()?;
|
||||
Some((MidiCommand::Pressure { channel: chan, value }, None))
|
||||
let chan: u8 = find_param("chan")?.parse().ok()?;
|
||||
Some((MidiCommand::Pressure { device, channel: chan, value }, None))
|
||||
}
|
||||
"program" => {
|
||||
// /midi/program/<value>/chan/<chan>
|
||||
// /midi/program/<value>/chan/<chan>/dev/<dev>
|
||||
let program: u8 = parts.get(2)?.parse().ok()?;
|
||||
let chan: u8 = parts.get(4)?.parse().ok()?;
|
||||
Some((MidiCommand::ProgramChange { channel: chan, program }, None))
|
||||
let chan: u8 = find_param("chan")?.parse().ok()?;
|
||||
Some((MidiCommand::ProgramChange { device, channel: chan, program }, None))
|
||||
}
|
||||
"clock" => Some((MidiCommand::Clock, None)),
|
||||
"start" => Some((MidiCommand::Start, None)),
|
||||
"stop" => Some((MidiCommand::Stop, None)),
|
||||
"continue" => Some((MidiCommand::Continue, None)),
|
||||
"clock" => Some((MidiCommand::Clock { device }, None)),
|
||||
"start" => Some((MidiCommand::Start { device }, None)),
|
||||
"stop" => Some((MidiCommand::Stop { device }, None)),
|
||||
"continue" => Some((MidiCommand::Continue { device }, None)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user