Feat: new harmony / melodic words and demo
This commit is contained in:
@@ -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)?;
|
||||
|
||||
Reference in New Issue
Block a user