scales
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
3
crates/forth/src/theory/mod.rs
Normal file
3
crates/forth/src/theory/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod scales;
|
||||||
|
|
||||||
|
pub use scales::lookup;
|
||||||
130
crates/forth/src/theory/scales.rs
Normal file
130
crates/forth/src/theory/scales.rs
Normal 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)
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
(Value::Int(min_i, _), Value::Int(max_i, _)) => {
|
||||||
|
let val = self.rng.lock().unwrap().gen_range(*min_i..=*max_i);
|
||||||
|
stack.push(Value::Int(val, None));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let min_f = min.as_float()?;
|
||||||
|
let max_f = max.as_float()?;
|
||||||
|
let val = self.rng.lock().unwrap().gen_range(min_f..max_f);
|
||||||
stack.push(Value::Float(val, None));
|
stack.push(Value::Float(val, None));
|
||||||
}
|
}
|
||||||
Op::Rrand => {
|
}
|
||||||
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);
|
|
||||||
stack.push(Value::Int(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")?;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user