Feat: new harmony / melodic words and demo

This commit is contained in:
2026-02-23 02:25:32 +01:00
parent d9e6505e07
commit e7137cc7ed
9 changed files with 624 additions and 9 deletions

View File

@@ -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)?;