More robust midi implementation
This commit is contained in:
@@ -4,6 +4,8 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::ops::Op;
|
||||
|
||||
pub const MAX_MIDI_DEVICES: usize = 4;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct SourceSpan {
|
||||
pub start: usize,
|
||||
@@ -42,7 +44,7 @@ pub type Variables = Arc<Mutex<HashMap<String, Value>>>;
|
||||
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
|
||||
pub type Rng = Arc<Mutex<StdRng>>;
|
||||
pub type Stack = Arc<Mutex<Vec<Value>>>;
|
||||
pub type CcMemory = Arc<Mutex<[[u8; 128]; 16]>>;
|
||||
pub type CcMemory = Arc<Mutex<[[[u8; 128]; 16]; MAX_MIDI_DEVICES]>>;
|
||||
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(String, Value)]);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -811,51 +811,94 @@ impl Forth {
|
||||
let chan = get_int("chan")
|
||||
.map(|c| (c.clamp(1, 16) - 1) as u8)
|
||||
.unwrap_or(0);
|
||||
let dev = get_int("dev")
|
||||
.map(|d| d.clamp(0, 3) as u8)
|
||||
.unwrap_or(0);
|
||||
|
||||
if let (Some(cc), Some(val)) = (get_int("ccnum"), get_int("ccout")) {
|
||||
let cc = cc.clamp(0, 127) as u8;
|
||||
let val = val.clamp(0, 127) as u8;
|
||||
outputs.push(format!("/midi/cc/{cc}/{val}/chan/{chan}"));
|
||||
outputs.push(format!("/midi/cc/{cc}/{val}/chan/{chan}/dev/{dev}"));
|
||||
} else if let Some(bend) = get_float("bend") {
|
||||
let bend_clamped = bend.clamp(-1.0, 1.0);
|
||||
let bend_14bit = ((bend_clamped + 1.0) * 8191.5) as u16;
|
||||
outputs.push(format!("/midi/bend/{bend_14bit}/chan/{chan}"));
|
||||
outputs.push(format!("/midi/bend/{bend_14bit}/chan/{chan}/dev/{dev}"));
|
||||
} else if let Some(pressure) = get_int("pressure") {
|
||||
let pressure = pressure.clamp(0, 127) as u8;
|
||||
outputs.push(format!("/midi/pressure/{pressure}/chan/{chan}"));
|
||||
outputs.push(format!("/midi/pressure/{pressure}/chan/{chan}/dev/{dev}"));
|
||||
} else if let Some(program) = get_int("program") {
|
||||
let program = program.clamp(0, 127) as u8;
|
||||
outputs.push(format!("/midi/program/{program}/chan/{chan}"));
|
||||
outputs.push(format!("/midi/program/{program}/chan/{chan}/dev/{dev}"));
|
||||
} else {
|
||||
let note = get_int("note").unwrap_or(60).clamp(0, 127) as u8;
|
||||
let velocity = get_int("velocity").unwrap_or(100).clamp(0, 127) as u8;
|
||||
let dur = get_float("dur").unwrap_or(1.0);
|
||||
let dur_secs = dur * ctx.step_duration();
|
||||
outputs.push(format!("/midi/note/{note}/vel/{velocity}/chan/{chan}/dur/{dur_secs}"));
|
||||
outputs.push(format!("/midi/note/{note}/vel/{velocity}/chan/{chan}/dur/{dur_secs}/dev/{dev}"));
|
||||
}
|
||||
}
|
||||
Op::MidiClock => {
|
||||
outputs.push("/midi/clock".to_string());
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == "dev")
|
||||
.and_then(|(_, v)| v.as_int().ok())
|
||||
.map(|d| d.clamp(0, 3) as u8)
|
||||
.unwrap_or(0);
|
||||
outputs.push(format!("/midi/clock/dev/{dev}"));
|
||||
}
|
||||
Op::MidiStart => {
|
||||
outputs.push("/midi/start".to_string());
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == "dev")
|
||||
.and_then(|(_, v)| v.as_int().ok())
|
||||
.map(|d| d.clamp(0, 3) as u8)
|
||||
.unwrap_or(0);
|
||||
outputs.push(format!("/midi/start/dev/{dev}"));
|
||||
}
|
||||
Op::MidiStop => {
|
||||
outputs.push("/midi/stop".to_string());
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == "dev")
|
||||
.and_then(|(_, v)| v.as_int().ok())
|
||||
.map(|d| d.clamp(0, 3) as u8)
|
||||
.unwrap_or(0);
|
||||
outputs.push(format!("/midi/stop/dev/{dev}"));
|
||||
}
|
||||
Op::MidiContinue => {
|
||||
outputs.push("/midi/continue".to_string());
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == "dev")
|
||||
.and_then(|(_, v)| v.as_int().ok())
|
||||
.map(|d| d.clamp(0, 3) as u8)
|
||||
.unwrap_or(0);
|
||||
outputs.push(format!("/midi/continue/dev/{dev}"));
|
||||
}
|
||||
Op::GetMidiCC => {
|
||||
let chan = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let cc = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let cc_clamped = (cc.clamp(0, 127)) as usize;
|
||||
let chan_clamped = (chan.clamp(1, 16) - 1) as usize;
|
||||
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
|
||||
let dev = params
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(k, _)| k == "dev")
|
||||
.and_then(|(_, v)| v.as_int().ok())
|
||||
.map(|d| d.clamp(0, 3) as usize)
|
||||
.unwrap_or(0);
|
||||
let val = ctx
|
||||
.cc_memory
|
||||
.as_ref()
|
||||
.and_then(|mem| mem.lock().ok())
|
||||
.map(|mem| mem[chan_clamped][cc_clamped])
|
||||
.map(|mem| mem[dev][chan_clamped][cc_clamped])
|
||||
.unwrap_or(0);
|
||||
stack.push(Value::Int(val as i64, None));
|
||||
}
|
||||
|
||||
@@ -2396,11 +2396,21 @@ pub const WORDS: &[Word] = &[
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(cc chan -- val)",
|
||||
desc: "Read CC value 0-127 from MIDI input",
|
||||
desc: "Read CC value 0-127 from MIDI input (uses dev param for device)",
|
||||
example: "1 1 ccval",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "dev",
|
||||
aliases: &[],
|
||||
category: "MIDI",
|
||||
stack: "(n --)",
|
||||
desc: "Set MIDI device slot 0-3 for output/input",
|
||||
example: "1 dev 60 note m.",
|
||||
compile: Param,
|
||||
varargs: false,
|
||||
},
|
||||
];
|
||||
|
||||
pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
|
||||
Reference in New Issue
Block a user