Feat: new harmony / melodic words and demo
Some checks failed
Deploy Website / deploy (push) Failing after 4m49s
Some checks failed
Deploy Website / deploy (push) Failing after 4m49s
This commit is contained in:
@@ -113,6 +113,14 @@ pub enum Op {
|
||||
EuclidRot,
|
||||
Times,
|
||||
Chord(&'static [i64]),
|
||||
Transpose,
|
||||
Invert,
|
||||
DownInvert,
|
||||
VoiceDrop2,
|
||||
VoiceDrop3,
|
||||
SetKey,
|
||||
DiatonicTriad(&'static [i64]),
|
||||
DiatonicSeventh(&'static [i64]),
|
||||
// Audio-rate modulation DSL
|
||||
ModLfo(u8),
|
||||
ModSlide(u8),
|
||||
|
||||
@@ -105,6 +105,47 @@ pub static CHORDS: &[Chord] = &[
|
||||
name: "madd9",
|
||||
intervals: &[0, 3, 7, 14],
|
||||
},
|
||||
// Power chord
|
||||
Chord {
|
||||
name: "pwr",
|
||||
intervals: &[0, 7],
|
||||
},
|
||||
// Suspended seventh
|
||||
Chord {
|
||||
name: "7sus4",
|
||||
intervals: &[0, 5, 7, 10],
|
||||
},
|
||||
Chord {
|
||||
name: "9sus4",
|
||||
intervals: &[0, 5, 7, 10, 14],
|
||||
},
|
||||
// Augmented major
|
||||
Chord {
|
||||
name: "augmaj7",
|
||||
intervals: &[0, 4, 8, 11],
|
||||
},
|
||||
// 6/9 chords
|
||||
Chord {
|
||||
name: "maj69",
|
||||
intervals: &[0, 4, 7, 9, 14],
|
||||
},
|
||||
Chord {
|
||||
name: "min69",
|
||||
intervals: &[0, 3, 7, 9, 14],
|
||||
},
|
||||
// Extended
|
||||
Chord {
|
||||
name: "maj11",
|
||||
intervals: &[0, 4, 7, 11, 14, 17],
|
||||
},
|
||||
Chord {
|
||||
name: "maj13",
|
||||
intervals: &[0, 4, 7, 11, 14, 21],
|
||||
},
|
||||
Chord {
|
||||
name: "min13",
|
||||
intervals: &[0, 3, 7, 10, 14, 21],
|
||||
},
|
||||
// Altered dominants
|
||||
Chord {
|
||||
name: "dom7b9",
|
||||
@@ -122,6 +163,10 @@ pub static CHORDS: &[Chord] = &[
|
||||
name: "dom7s5",
|
||||
intervals: &[0, 4, 8, 10],
|
||||
},
|
||||
Chord {
|
||||
name: "dom7s11",
|
||||
intervals: &[0, 4, 7, 10, 18],
|
||||
},
|
||||
];
|
||||
|
||||
pub fn lookup(name: &str) -> Option<&'static [i64]> {
|
||||
|
||||
@@ -133,6 +133,17 @@ impl Forth {
|
||||
let trace_cell = std::cell::RefCell::new(trace);
|
||||
let var_writes_cell = std::cell::RefCell::new(Some(var_writes));
|
||||
|
||||
let read_key = |vwc: &std::cell::RefCell<Option<&mut HashMap<String, Value>>>,
|
||||
vs: &VariablesMap|
|
||||
-> i64 {
|
||||
vwc.borrow()
|
||||
.as_ref()
|
||||
.and_then(|vw| vw.get("__key__"))
|
||||
.or_else(|| vs.get("__key__"))
|
||||
.and_then(|v| v.as_int().ok())
|
||||
.unwrap_or(60)
|
||||
};
|
||||
|
||||
let run_quotation = |quot: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
@@ -955,14 +966,18 @@ impl Forth {
|
||||
if pattern.is_empty() {
|
||||
return Err("empty scale pattern".into());
|
||||
}
|
||||
let val = pop(stack)?;
|
||||
ensure(stack, 1)?;
|
||||
let len = pattern.len() as i64;
|
||||
let result = lift_unary_int(val, |degree| {
|
||||
let octave_offset = degree.div_euclid(len);
|
||||
let idx = degree.rem_euclid(len) as usize;
|
||||
60 + octave_offset * 12 + pattern[idx]
|
||||
})?;
|
||||
stack.push(result);
|
||||
let key = read_key(&var_writes_cell, vars_snapshot);
|
||||
let values = std::mem::take(stack);
|
||||
for val in values {
|
||||
let result = lift_unary_int(val, |degree| {
|
||||
let octave_offset = degree.div_euclid(len);
|
||||
let idx = degree.rem_euclid(len) as usize;
|
||||
key + octave_offset * 12 + pattern[idx]
|
||||
})?;
|
||||
stack.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
Op::Chord(intervals) => {
|
||||
@@ -972,6 +987,87 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
Op::Transpose => {
|
||||
let n = pop_int(stack)?;
|
||||
for val in stack.iter_mut() {
|
||||
if let Value::Int(v, _) = val {
|
||||
*v += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Op::SetKey => {
|
||||
let key = pop_int(stack)?;
|
||||
var_writes_cell
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("var_writes taken")
|
||||
.insert("__key__".to_string(), Value::Int(key, None));
|
||||
}
|
||||
|
||||
Op::Invert => {
|
||||
ensure(stack, 2)?;
|
||||
let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
|
||||
let bottom = stack[start].as_int()? + 12;
|
||||
stack.remove(start);
|
||||
stack.push(Value::Int(bottom, None));
|
||||
}
|
||||
|
||||
Op::DownInvert => {
|
||||
ensure(stack, 2)?;
|
||||
let top = pop_int(stack)? - 12;
|
||||
let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
|
||||
stack.insert(start, Value::Int(top, None));
|
||||
}
|
||||
|
||||
Op::VoiceDrop2 => {
|
||||
ensure(stack, 3)?;
|
||||
let len = stack.len();
|
||||
let note = stack[len - 2].as_int()? - 12;
|
||||
stack.remove(len - 2);
|
||||
let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
|
||||
stack.insert(start, Value::Int(note, None));
|
||||
}
|
||||
|
||||
Op::VoiceDrop3 => {
|
||||
ensure(stack, 4)?;
|
||||
let len = stack.len();
|
||||
let note = stack[len - 3].as_int()? - 12;
|
||||
stack.remove(len - 3);
|
||||
let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
|
||||
stack.insert(start, Value::Int(note, None));
|
||||
}
|
||||
|
||||
Op::DiatonicTriad(pattern) => {
|
||||
if pattern.is_empty() {
|
||||
return Err("empty scale pattern".into());
|
||||
}
|
||||
let degree = pop_int(stack)?;
|
||||
let key = read_key(&var_writes_cell, vars_snapshot);
|
||||
let len = pattern.len() as i64;
|
||||
for offset in [0, 2, 4] {
|
||||
let d = degree + offset;
|
||||
let octave_offset = d.div_euclid(len);
|
||||
let idx = d.rem_euclid(len) as usize;
|
||||
stack.push(Value::Int(key + octave_offset * 12 + pattern[idx], None));
|
||||
}
|
||||
}
|
||||
|
||||
Op::DiatonicSeventh(pattern) => {
|
||||
if pattern.is_empty() {
|
||||
return Err("empty scale pattern".into());
|
||||
}
|
||||
let degree = pop_int(stack)?;
|
||||
let key = read_key(&var_writes_cell, vars_snapshot);
|
||||
let len = pattern.len() as i64;
|
||||
for offset in [0, 2, 4, 6] {
|
||||
let d = degree + offset;
|
||||
let octave_offset = d.div_euclid(len);
|
||||
let idx = d.rem_euclid(len) as usize;
|
||||
stack.push(Value::Int(key + octave_offset * 12 + pattern[idx], None));
|
||||
}
|
||||
}
|
||||
|
||||
Op::Oct => {
|
||||
let shift = pop(stack)?;
|
||||
let note = pop(stack)?;
|
||||
|
||||
@@ -108,6 +108,12 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"mstop" => Op::MidiStop,
|
||||
"mcont" => Op::MidiContinue,
|
||||
"forget" => Op::Forget,
|
||||
"key!" => Op::SetKey,
|
||||
"tp" => Op::Transpose,
|
||||
"inv" => Op::Invert,
|
||||
"dinv" => Op::DownInvert,
|
||||
"drop2" => Op::VoiceDrop2,
|
||||
"drop3" => Op::VoiceDrop3,
|
||||
"lfo" => Op::ModLfo(0),
|
||||
"tlfo" => Op::ModLfo(1),
|
||||
"wlfo" => Op::ModLfo(2),
|
||||
@@ -227,6 +233,20 @@ pub(crate) fn compile_word(
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if name == "triad" || name == "seventh" {
|
||||
if let Some(Op::Degree(pattern)) = ops.last() {
|
||||
let pattern = *pattern;
|
||||
ops.pop();
|
||||
ops.push(if name == "triad" {
|
||||
Op::DiatonicTriad(pattern)
|
||||
} else {
|
||||
Op::DiatonicSeventh(pattern)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(pattern) = theory::lookup(name) {
|
||||
ops.push(Op::Degree(pattern));
|
||||
return true;
|
||||
|
||||
@@ -23,7 +23,100 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
// Harmony
|
||||
Word {
|
||||
name: "key!",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(root --)",
|
||||
desc: "Set tonal center for scale operations",
|
||||
example: "g3 key! 0 major => 55",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "triad",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(degree -- n1 n2 n3)",
|
||||
desc: "Diatonic triad from scale degree (follows a scale word)",
|
||||
example: "0 major triad => 60 64 67",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "seventh",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(degree -- n1 n2 n3 n4)",
|
||||
desc: "Diatonic seventh from scale degree (follows a scale word)",
|
||||
example: "0 major seventh => 60 64 67 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chord voicings
|
||||
Word {
|
||||
name: "inv",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b c.. -- b c.. a+12)",
|
||||
desc: "Inversion: bottom note moves up an octave",
|
||||
example: "c4 maj inv => 64 67 72",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "dinv",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b.. z -- z-12 a b..)",
|
||||
desc: "Down inversion: top note moves down an octave",
|
||||
example: "c4 maj dinv => 55 60 64",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "drop2",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b c d -- b-12 a c d)",
|
||||
desc: "Drop-2 voicing: 2nd from top moves down an octave",
|
||||
example: "c4 maj7 drop2 => 55 60 64 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "drop3",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(a b c d -- c-12 a b d)",
|
||||
desc: "Drop-3 voicing: 3rd from top moves down an octave",
|
||||
example: "c4 maj7 drop3 => 52 60 67 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Transpose
|
||||
Word {
|
||||
name: "tp",
|
||||
aliases: &[],
|
||||
category: "Harmony",
|
||||
stack: "(n --)",
|
||||
desc: "Transpose all ints on stack by N semitones",
|
||||
example: "c4 maj 3 tp => 63 67 70",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Triads
|
||||
Word {
|
||||
name: "pwr",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root fifth)",
|
||||
desc: "Power chord",
|
||||
example: "c4 pwr => 60 67",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj",
|
||||
aliases: &[],
|
||||
@@ -155,6 +248,36 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "augmaj7",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh)",
|
||||
desc: "Augmented major 7th",
|
||||
example: "c4 augmaj7 => 60 64 68 71",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "7sus4",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root fourth fifth seventh)",
|
||||
desc: "Dominant 7 sus4",
|
||||
example: "c4 7sus4 => 60 65 67 70",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "9sus4",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root fourth fifth seventh ninth)",
|
||||
desc: "9 sus4",
|
||||
example: "c4 9sus4 => 60 65 67 70 74",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Sixth
|
||||
Word {
|
||||
name: "maj6",
|
||||
@@ -176,6 +299,26 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj69",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth sixth ninth)",
|
||||
desc: "Major 6/9",
|
||||
example: "c4 maj69 => 60 64 67 69 74",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "min69",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth sixth ninth)",
|
||||
desc: "Minor 6/9",
|
||||
example: "c4 min69 => 60 63 67 69 74",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Extended
|
||||
Word {
|
||||
name: "dom9",
|
||||
@@ -217,6 +360,16 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj11",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh ninth eleventh)",
|
||||
desc: "Major 11th",
|
||||
example: "c4 maj11 => 60 64 67 71 74 77",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "min11",
|
||||
aliases: &[],
|
||||
@@ -237,6 +390,26 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "maj13",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh ninth thirteenth)",
|
||||
desc: "Major 13th",
|
||||
example: "c4 maj13 => 60 64 67 71 74 81",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "min13",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh ninth thirteenth)",
|
||||
desc: "Minor 13th",
|
||||
example: "c4 min13 => 60 63 67 70 74 81",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
// Chords - Add
|
||||
Word {
|
||||
name: "add9",
|
||||
@@ -309,4 +482,14 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "dom7s11",
|
||||
aliases: &[],
|
||||
category: "Chord",
|
||||
stack: "(root -- root third fifth seventh sharpelev)",
|
||||
desc: "7th sharp 11 (lydian dominant)",
|
||||
example: "c4 dom7s11 => 60 64 67 70 78",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user