Feat: begin slight refactoring
This commit is contained in:
@@ -5,6 +5,8 @@ mod types;
|
||||
mod vm;
|
||||
mod words;
|
||||
|
||||
pub use types::{CcMemory, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables};
|
||||
pub use types::{
|
||||
CcAccess, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables,
|
||||
};
|
||||
pub use vm::Forth;
|
||||
pub use words::{Word, WordCompile, WORDS};
|
||||
|
||||
@@ -4,7 +4,11 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::ops::Op;
|
||||
|
||||
pub const MAX_MIDI_DEVICES: usize = 4;
|
||||
/// Trait for accessing MIDI CC values. Implement this to provide CC memory to the Forth VM.
|
||||
pub trait CcAccess: Send + Sync {
|
||||
/// Get the CC value for a given device, channel (0-15), and CC number (0-127).
|
||||
fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct SourceSpan {
|
||||
@@ -31,7 +35,7 @@ pub struct StepContext {
|
||||
pub speed: f64,
|
||||
pub fill: bool,
|
||||
pub nudge_secs: f64,
|
||||
pub cc_memory: Option<CcMemory>,
|
||||
pub cc_access: Option<Arc<dyn CcAccess>>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mouse_x: f64,
|
||||
#[cfg(feature = "desktop")]
|
||||
@@ -50,7 +54,6 @@ 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]; MAX_MIDI_DEVICES]>>;
|
||||
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(String, Value)]);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -134,7 +137,6 @@ pub(super) struct CmdRegister {
|
||||
deltas: Vec<Value>,
|
||||
}
|
||||
|
||||
|
||||
impl CmdRegister {
|
||||
pub(super) fn set_sound(&mut self, val: Value) {
|
||||
self.sound = Some(val);
|
||||
@@ -165,4 +167,3 @@ impl CmdRegister {
|
||||
self.params.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -817,9 +817,7 @@ 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);
|
||||
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;
|
||||
@@ -840,51 +838,29 @@ impl Forth {
|
||||
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}/dev/{dev}"));
|
||||
outputs.push(format!(
|
||||
"/midi/note/{note}/vel/{velocity}/chan/{chan}/dur/{dur_secs}/dev/{dev}"
|
||||
));
|
||||
}
|
||||
}
|
||||
Op::MidiClock => {
|
||||
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);
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/clock/dev/{dev}"));
|
||||
}
|
||||
Op::MidiStart => {
|
||||
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);
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/start/dev/{dev}"));
|
||||
}
|
||||
Op::MidiStop => {
|
||||
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);
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/stop/dev/{dev}"));
|
||||
}
|
||||
Op::MidiContinue => {
|
||||
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);
|
||||
let dev = extract_dev_param(params);
|
||||
outputs.push(format!("/midi/continue/dev/{dev}"));
|
||||
}
|
||||
Op::GetMidiCC => {
|
||||
@@ -893,18 +869,11 @@ impl Forth {
|
||||
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 dev = extract_dev_param(params) as usize;
|
||||
let val = ctx
|
||||
.cc_memory
|
||||
.cc_access
|
||||
.as_ref()
|
||||
.and_then(|mem| mem.lock().ok())
|
||||
.map(|mem| mem[dev][chan_clamped][cc_clamped])
|
||||
.map(|cc| cc.get_cc(dev, chan_clamped, cc_clamped))
|
||||
.unwrap_or(0);
|
||||
stack.push(Value::Int(val as i64, None));
|
||||
}
|
||||
@@ -916,6 +885,16 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_dev_param(params: &[(String, Value)]) -> u8 {
|
||||
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)
|
||||
}
|
||||
|
||||
fn is_tempo_scaled_param(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use super::ops::Op;
|
||||
use super::theory;
|
||||
use super::types::{Dictionary, SourceSpan};
|
||||
@@ -2446,6 +2449,21 @@ pub const WORDS: &[Word] = &[
|
||||
},
|
||||
];
|
||||
|
||||
static WORD_MAP: LazyLock<HashMap<&'static str, &'static Word>> = LazyLock::new(|| {
|
||||
let mut map = HashMap::with_capacity(WORDS.len() * 2);
|
||||
for word in WORDS {
|
||||
map.insert(word.name, word);
|
||||
for alias in word.aliases {
|
||||
map.insert(alias, word);
|
||||
}
|
||||
}
|
||||
map
|
||||
});
|
||||
|
||||
fn lookup_word(name: &str) -> Option<&'static Word> {
|
||||
WORD_MAP.get(name).copied()
|
||||
}
|
||||
|
||||
pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
Some(match name {
|
||||
"dup" => Op::Dup,
|
||||
@@ -2636,23 +2654,21 @@ pub(super) fn compile_word(
|
||||
return true;
|
||||
}
|
||||
|
||||
for word in WORDS {
|
||||
if word.name == name || word.aliases.contains(&name) {
|
||||
match &word.compile {
|
||||
Simple => {
|
||||
if let Some(op) = simple_op(word.name) {
|
||||
ops.push(op);
|
||||
}
|
||||
}
|
||||
Context(ctx) => ops.push(Op::GetContext((*ctx).into())),
|
||||
Param => ops.push(Op::SetParam(word.name.into())),
|
||||
Probability(p) => {
|
||||
ops.push(Op::PushFloat(*p, None));
|
||||
ops.push(Op::ChanceExec);
|
||||
if let Some(word) = lookup_word(name) {
|
||||
match &word.compile {
|
||||
Simple => {
|
||||
if let Some(op) = simple_op(word.name) {
|
||||
ops.push(op);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
Context(ctx) => ops.push(Op::GetContext((*ctx).into())),
|
||||
Param => ops.push(Op::SetParam(word.name.into())),
|
||||
Probability(p) => {
|
||||
ops.push(Op::PushFloat(*p, None));
|
||||
ops.push(Op::ChanceExec);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// @varname - fetch variable
|
||||
|
||||
Reference in New Issue
Block a user