This commit is contained in:
2026-01-25 20:43:12 +01:00
parent b1a982aaa0
commit ac83ceb2cb
6 changed files with 176 additions and 23 deletions

View File

@@ -1,5 +1,6 @@
mod compiler; mod compiler;
mod ops; mod ops;
mod theory;
mod types; mod types;
mod vm; mod vm;
mod words; mod words;

View File

@@ -43,7 +43,6 @@ pub enum Op {
Set, Set,
GetContext(String), GetContext(String),
Rand, Rand,
Rrand,
Seed, Seed,
Cycle, Cycle,
Choose, Choose,
@@ -81,4 +80,6 @@ pub enum Op {
Noise, Noise,
Chain, Chain,
Loop, Loop,
Degree(&'static [i64]),
Oct,
} }

View File

@@ -0,0 +1,3 @@
mod scales;
pub use scales::lookup;

View File

@@ -0,0 +1,130 @@
pub struct Scale {
pub name: &'static str,
pub pattern: &'static [i64],
}
pub static SCALES: &[Scale] = &[
Scale {
name: "major",
pattern: &[0, 2, 4, 5, 7, 9, 11],
},
Scale {
name: "minor",
pattern: &[0, 2, 3, 5, 7, 8, 10],
},
Scale {
name: "dorian",
pattern: &[0, 2, 3, 5, 7, 9, 10],
},
Scale {
name: "phrygian",
pattern: &[0, 1, 3, 5, 7, 8, 10],
},
Scale {
name: "lydian",
pattern: &[0, 2, 4, 6, 7, 9, 11],
},
Scale {
name: "mixolydian",
pattern: &[0, 2, 4, 5, 7, 9, 10],
},
Scale {
name: "aeolian",
pattern: &[0, 2, 3, 5, 7, 8, 10],
},
Scale {
name: "locrian",
pattern: &[0, 1, 3, 5, 6, 8, 10],
},
Scale {
name: "pentatonic",
pattern: &[0, 2, 4, 7, 9],
},
Scale {
name: "minpent",
pattern: &[0, 3, 5, 7, 10],
},
Scale {
name: "blues",
pattern: &[0, 3, 5, 6, 7, 10],
},
Scale {
name: "chromatic",
pattern: &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
},
Scale {
name: "wholetone",
pattern: &[0, 2, 4, 6, 8, 10],
},
Scale {
name: "harmonicminor",
pattern: &[0, 2, 3, 5, 7, 8, 11],
},
Scale {
name: "melodicminor",
pattern: &[0, 2, 3, 5, 7, 9, 11],
},
// Jazz/Bebop
Scale {
name: "bebop",
pattern: &[0, 2, 4, 5, 7, 9, 10, 11],
},
Scale {
name: "bebopmaj",
pattern: &[0, 2, 4, 5, 7, 8, 9, 11],
},
Scale {
name: "bebopmin",
pattern: &[0, 2, 3, 5, 7, 8, 9, 10],
},
Scale {
name: "altered",
pattern: &[0, 1, 3, 4, 6, 8, 10],
},
Scale {
name: "lyddom",
pattern: &[0, 2, 4, 6, 7, 9, 10],
},
Scale {
name: "halfwhole",
pattern: &[0, 1, 3, 4, 6, 7, 9, 10],
},
Scale {
name: "wholehalf",
pattern: &[0, 2, 3, 5, 6, 8, 9, 11],
},
// Symmetric
Scale {
name: "augmented",
pattern: &[0, 3, 4, 7, 8, 11],
},
Scale {
name: "tritone",
pattern: &[0, 1, 4, 6, 7, 10],
},
Scale {
name: "prometheus",
pattern: &[0, 2, 4, 6, 9, 10],
},
// Modal variants (from melodic minor)
Scale {
name: "dorianb2",
pattern: &[0, 1, 3, 5, 7, 9, 10],
},
Scale {
name: "lydianaug",
pattern: &[0, 2, 4, 6, 8, 9, 11],
},
Scale {
name: "mixb6",
pattern: &[0, 2, 4, 5, 7, 8, 10],
},
Scale {
name: "locrian2",
pattern: &[0, 2, 3, 5, 6, 8, 10],
},
];
pub fn lookup(name: &str) -> Option<&'static [i64]> {
SCALES.iter().find(|s| s.name == name).map(|s| s.pattern)
}

View File

@@ -315,16 +315,20 @@ impl Forth {
} }
Op::Rand => { Op::Rand => {
let max = stack.pop().ok_or("stack underflow")?.as_float()?; let max = stack.pop().ok_or("stack underflow")?;
let min = stack.pop().ok_or("stack underflow")?.as_float()?; let min = stack.pop().ok_or("stack underflow")?;
let val = self.rng.lock().unwrap().gen_range(min..max); match (&min, &max) {
stack.push(Value::Float(val, None)); (Value::Int(min_i, _), Value::Int(max_i, _)) => {
} let val = self.rng.lock().unwrap().gen_range(*min_i..=*max_i);
Op::Rrand => { stack.push(Value::Int(val, None));
let max = stack.pop().ok_or("stack underflow")?.as_int()?; }
let min = stack.pop().ok_or("stack underflow")?.as_int()?; _ => {
let val = self.rng.lock().unwrap().gen_range(min..=max); let min_f = min.as_float()?;
stack.push(Value::Int(val, None)); let max_f = max.as_float()?;
let val = self.rng.lock().unwrap().gen_range(min_f..max_f);
stack.push(Value::Float(val, None));
}
}
} }
Op::Seed => { Op::Seed => {
let s = stack.pop().ok_or("stack underflow")?.as_int()?; let s = stack.pop().ok_or("stack underflow")?.as_int()?;
@@ -536,6 +540,21 @@ impl Forth {
stack.push(Value::Float(note, None)); stack.push(Value::Float(note, None));
} }
Op::Degree(pattern) => {
let degree = stack.pop().ok_or("stack underflow")?.as_int()?;
let len = pattern.len() as i64;
let octave_offset = degree.div_euclid(len);
let idx = degree.rem_euclid(len) as usize;
let midi = 60 + octave_offset * 12 + pattern[idx];
stack.push(Value::Int(midi, None));
}
Op::Oct => {
let shift = stack.pop().ok_or("stack underflow")?.as_int()?;
let note = stack.pop().ok_or("stack underflow")?.as_int()?;
stack.push(Value::Int(note + shift * 12, None));
}
Op::At => { Op::At => {
let pos = stack.pop().ok_or("stack underflow")?.as_float()?; let pos = stack.pop().ok_or("stack underflow")?.as_float()?;
let parent = time_stack.last().ok_or("time stack underflow")?; let parent = time_stack.last().ok_or("time stack underflow")?;

View File

@@ -1,4 +1,5 @@
use super::ops::Op; use super::ops::Op;
use super::theory;
use super::types::{Dictionary, SourceSpan}; use super::types::{Dictionary, SourceSpan};
pub enum WordCompile { pub enum WordCompile {
@@ -281,16 +282,9 @@ pub const WORDS: &[Word] = &[
// Randomness // Randomness
Word { Word {
name: "rand", name: "rand",
stack: "(min max -- f)", stack: "(min max -- n|f)",
desc: "Random float in range", desc: "Random in range. Int if both args are int, float otherwise",
example: "0 1 rand => 0.42", example: "1 6 rand => 4 | 0.0 1.0 rand => 0.42",
compile: Simple,
},
Word {
name: "rrand",
stack: "(min max -- n)",
desc: "Random int in range",
example: "1 6 rrand => 4",
compile: Simple, compile: Simple,
}, },
Word { Word {
@@ -1537,7 +1531,6 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
"sound" => Op::NewCmd, "sound" => Op::NewCmd,
"emit" => Op::Emit, "emit" => Op::Emit,
"rand" => Op::Rand, "rand" => Op::Rand,
"rrand" => Op::Rrand,
"seed" => Op::Seed, "seed" => Op::Seed,
"cycle" => Op::Cycle, "cycle" => Op::Cycle,
"pcycle" => Op::PCycle, "pcycle" => Op::PCycle,
@@ -1573,6 +1566,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
"noise" => Op::Noise, "noise" => Op::Noise,
"chain" => Op::Chain, "chain" => Op::Chain,
"loop" => Op::Loop, "loop" => Op::Loop,
"oct" => Op::Oct,
_ => return None, _ => return None,
}) })
} }
@@ -1633,7 +1627,7 @@ fn parse_interval(name: &str) -> Option<i64> {
"M6" => 9, "M6" => 9,
"m7" => 10, "m7" => 10,
"M7" => 11, "M7" => 11,
"P8" | "oct" => 12, "P8" => 12,
// Compound intervals (octave + simple) // Compound intervals (octave + simple)
"m9" => 13, "m9" => 13,
"M9" => 14, "M9" => 14,
@@ -1660,6 +1654,11 @@ pub(super) fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<O
_ => {} _ => {}
} }
if let Some(pattern) = theory::lookup(name) {
ops.push(Op::Degree(pattern));
return true;
}
for word in WORDS { for word in WORDS {
if word.name == name { if word.name == name {
match &word.compile { match &word.compile {