Feat: parameter duration scaling
This commit is contained in:
@@ -86,7 +86,7 @@ impl Forth {
|
|||||||
|
|
||||||
// Resolve root scope at end of script
|
// Resolve root scope at end of script
|
||||||
if let Some(scope) = scope_stack.pop() {
|
if let Some(scope) = scope_stack.pop() {
|
||||||
resolve_scope(&scope, &mut outputs);
|
resolve_scope(&scope, ctx.step_duration(), &mut outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(outputs)
|
Ok(outputs)
|
||||||
@@ -536,7 +536,11 @@ impl Forth {
|
|||||||
let cond = stack.pop().ok_or("stack underflow")?;
|
let cond = stack.pop().ok_or("stack underflow")?;
|
||||||
let false_quot = stack.pop().ok_or("stack underflow")?;
|
let false_quot = stack.pop().ok_or("stack underflow")?;
|
||||||
let true_quot = stack.pop().ok_or("stack underflow")?;
|
let true_quot = stack.pop().ok_or("stack underflow")?;
|
||||||
let quot = if cond.is_truthy() { true_quot } else { false_quot };
|
let quot = if cond.is_truthy() {
|
||||||
|
true_quot
|
||||||
|
} else {
|
||||||
|
false_quot
|
||||||
|
};
|
||||||
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
|
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,10 +617,7 @@ impl Forth {
|
|||||||
} else {
|
} else {
|
||||||
let key = format!("__chain_{}_{}__", ctx.bank, ctx.pattern);
|
let key = format!("__chain_{}_{}__", ctx.bank, ctx.pattern);
|
||||||
let val = format!("{bank}:{pattern}");
|
let val = format!("{bank}:{pattern}");
|
||||||
self.vars
|
self.vars.lock().unwrap().insert(key, Value::Str(val, None));
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(key, Value::Str(val, None));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,7 +726,7 @@ impl Forth {
|
|||||||
let child = scope_stack.pop().unwrap();
|
let child = scope_stack.pop().unwrap();
|
||||||
|
|
||||||
if child.stacked {
|
if child.stacked {
|
||||||
resolve_scope(&child, outputs);
|
resolve_scope(&child, ctx.step_duration(), outputs);
|
||||||
} else {
|
} else {
|
||||||
let parent = scope_stack.last_mut().ok_or("scope stack underflow")?;
|
let parent = scope_stack.last_mut().ok_or("scope stack underflow")?;
|
||||||
let parent_slot = parent.claim_slot();
|
let parent_slot = parent.claim_slot();
|
||||||
@@ -750,7 +751,6 @@ impl Forth {
|
|||||||
emit_once(cmd, scope_stack)?;
|
emit_once(cmd, scope_stack)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
pc += 1;
|
pc += 1;
|
||||||
}
|
}
|
||||||
@@ -759,7 +759,7 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_scope(scope: &ScopeContext, outputs: &mut Vec<String>) {
|
fn resolve_scope(scope: &ScopeContext, step_duration: f64, outputs: &mut Vec<String>) {
|
||||||
let slot_dur = if scope.slot_count == 0 {
|
let slot_dur = if scope.slot_count == 0 {
|
||||||
scope.duration * scope.weight
|
scope.duration * scope.weight
|
||||||
} else {
|
} else {
|
||||||
@@ -797,10 +797,21 @@ fn resolve_scope(scope: &ScopeContext, outputs: &mut Vec<String>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
emissions.sort_by(|a, b| a.delta.partial_cmp(&b.delta).unwrap_or(std::cmp::Ordering::Equal));
|
emissions.sort_by(|a, b| {
|
||||||
|
a.delta
|
||||||
|
.partial_cmp(&b.delta)
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
});
|
||||||
|
|
||||||
for em in emissions {
|
for em in emissions {
|
||||||
emit_output(&em.sound, &em.params, em.delta, em.dur, outputs);
|
emit_output(
|
||||||
|
&em.sound,
|
||||||
|
&em.params,
|
||||||
|
em.delta,
|
||||||
|
em.dur,
|
||||||
|
step_duration,
|
||||||
|
outputs,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,7 +853,40 @@ fn resolve_scope_to_parent(child: &ScopeContext, parent_slot: usize, parent: &mu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_output(sound: &str, params: &[(String, String)], delta: f64, dur: f64, outputs: &mut Vec<String>) {
|
const TEMPO_SCALED_PARAMS: &[&str] = &[
|
||||||
|
"attack",
|
||||||
|
"decay",
|
||||||
|
"release",
|
||||||
|
"lpa",
|
||||||
|
"lpd",
|
||||||
|
"lpr",
|
||||||
|
"hpa",
|
||||||
|
"hpd",
|
||||||
|
"hpr",
|
||||||
|
"bpa",
|
||||||
|
"bpd",
|
||||||
|
"bpr",
|
||||||
|
"patt",
|
||||||
|
"pdec",
|
||||||
|
"prel",
|
||||||
|
"fma",
|
||||||
|
"fmd",
|
||||||
|
"fmr",
|
||||||
|
"glide",
|
||||||
|
"verbdecay",
|
||||||
|
"verbpredelay",
|
||||||
|
"chorusdelay",
|
||||||
|
"duration",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn emit_output(
|
||||||
|
sound: &str,
|
||||||
|
params: &[(String, String)],
|
||||||
|
delta: f64,
|
||||||
|
dur: f64,
|
||||||
|
step_duration: f64,
|
||||||
|
outputs: &mut Vec<String>,
|
||||||
|
) {
|
||||||
let mut pairs = vec![("sound".into(), sound.to_string())];
|
let mut pairs = vec![("sound".into(), sound.to_string())];
|
||||||
pairs.extend(params.iter().cloned());
|
pairs.extend(params.iter().cloned());
|
||||||
if delta > 0.0 {
|
if delta > 0.0 {
|
||||||
@@ -857,12 +901,20 @@ fn emit_output(sound: &str, params: &[(String, String)], delta: f64, dur: f64, o
|
|||||||
} else {
|
} else {
|
||||||
pairs.push(("delaytime".into(), dur.to_string()));
|
pairs.push(("delaytime".into(), dur.to_string()));
|
||||||
}
|
}
|
||||||
|
for pair in &mut pairs {
|
||||||
|
if TEMPO_SCALED_PARAMS.contains(&pair.0.as_str()) {
|
||||||
|
if let Ok(val) = pair.1.parse::<f64>() {
|
||||||
|
pair.1 = (val * step_duration).to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
outputs.push(format_cmd(&pairs));
|
outputs.push(format_cmd(&pairs));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perlin_grad(hash_input: i64) -> f64 {
|
fn perlin_grad(hash_input: i64) -> f64 {
|
||||||
let mut h =
|
let mut h = (hash_input as u64)
|
||||||
(hash_input as u64).wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
.wrapping_mul(6364136223846793005)
|
||||||
|
.wrapping_add(1442695040888963407);
|
||||||
h ^= h >> 33;
|
h ^= h >> 33;
|
||||||
h = h.wrapping_mul(0xff51afd7ed558ccd);
|
h = h.wrapping_mul(0xff51afd7ed558ccd);
|
||||||
h ^= h >> 33;
|
h ^= h >> 33;
|
||||||
@@ -924,7 +976,11 @@ where
|
|||||||
{
|
{
|
||||||
let b = stack.pop().ok_or("stack underflow")?;
|
let b = stack.pop().ok_or("stack underflow")?;
|
||||||
let a = stack.pop().ok_or("stack underflow")?;
|
let a = stack.pop().ok_or("stack underflow")?;
|
||||||
let result = if f(a.as_float()?, b.as_float()?) { 1 } else { 0 };
|
let result = if f(a.as_float()?, b.as_float()?) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
stack.push(Value::Int(result, None));
|
stack.push(Value::Int(result, None));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1222,6 +1222,54 @@ pub const WORDS: &[Word] = &[
|
|||||||
example: "0.3 bpr",
|
example: "0.3 bpr",
|
||||||
compile: Param,
|
compile: Param,
|
||||||
},
|
},
|
||||||
|
Word {
|
||||||
|
name: "llpf",
|
||||||
|
category: "Ladder Filter",
|
||||||
|
stack: "(f --)",
|
||||||
|
desc: "Set ladder lowpass frequency",
|
||||||
|
example: "2000 llpf",
|
||||||
|
compile: Param,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "llpq",
|
||||||
|
category: "Ladder Filter",
|
||||||
|
stack: "(f --)",
|
||||||
|
desc: "Set ladder lowpass resonance",
|
||||||
|
example: "0.5 llpq",
|
||||||
|
compile: Param,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "lhpf",
|
||||||
|
category: "Ladder Filter",
|
||||||
|
stack: "(f --)",
|
||||||
|
desc: "Set ladder highpass frequency",
|
||||||
|
example: "100 lhpf",
|
||||||
|
compile: Param,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "lhpq",
|
||||||
|
category: "Ladder Filter",
|
||||||
|
stack: "(f --)",
|
||||||
|
desc: "Set ladder highpass resonance",
|
||||||
|
example: "0.5 lhpq",
|
||||||
|
compile: Param,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "lbpf",
|
||||||
|
category: "Ladder Filter",
|
||||||
|
stack: "(f --)",
|
||||||
|
desc: "Set ladder bandpass frequency",
|
||||||
|
example: "1000 lbpf",
|
||||||
|
compile: Param,
|
||||||
|
},
|
||||||
|
Word {
|
||||||
|
name: "lbpq",
|
||||||
|
category: "Ladder Filter",
|
||||||
|
stack: "(f --)",
|
||||||
|
desc: "Set ladder bandpass resonance",
|
||||||
|
example: "0.5 lbpq",
|
||||||
|
compile: Param,
|
||||||
|
},
|
||||||
Word {
|
Word {
|
||||||
name: "ftype",
|
name: "ftype",
|
||||||
category: "Filter",
|
category: "Filter",
|
||||||
@@ -1902,11 +1950,28 @@ fn parse_interval(name: &str) -> Option<i64> {
|
|||||||
Some(simple)
|
Some(simple)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>, dict: &Dictionary) -> bool {
|
pub(super) fn compile_word(
|
||||||
|
name: &str,
|
||||||
|
span: Option<SourceSpan>,
|
||||||
|
ops: &mut Vec<Op>,
|
||||||
|
dict: &Dictionary,
|
||||||
|
) -> bool {
|
||||||
match name {
|
match name {
|
||||||
"linramp" => { ops.push(Op::PushFloat(1.0, span)); ops.push(Op::Ramp); return true; }
|
"linramp" => {
|
||||||
"expramp" => { ops.push(Op::PushFloat(3.0, span)); ops.push(Op::Ramp); return true; }
|
ops.push(Op::PushFloat(1.0, span));
|
||||||
"logramp" => { ops.push(Op::PushFloat(0.3, span)); ops.push(Op::Ramp); return true; }
|
ops.push(Op::Ramp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
"expramp" => {
|
||||||
|
ops.push(Op::PushFloat(3.0, span));
|
||||||
|
ops.push(Op::Ramp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
"logramp" => {
|
||||||
|
ops.push(Op::PushFloat(0.3, span));
|
||||||
|
ops.push(Op::Ramp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,14 +46,15 @@ fn multiple_emits() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn envelope_params() {
|
fn envelope_params() {
|
||||||
|
// Values are tempo-scaled: 0.01 * step_duration(0.125) = 0.00125, etc.
|
||||||
let outputs = expect_outputs(
|
let outputs = expect_outputs(
|
||||||
r#""synth" s 0.01 attack 0.1 decay 0.7 sustain 0.3 release ."#,
|
r#""synth" s 0.01 attack 0.1 decay 0.7 sustain 0.3 release ."#,
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
assert!(outputs[0].contains("attack/0.01"));
|
assert!(outputs[0].contains("attack/0.00125"));
|
||||||
assert!(outputs[0].contains("decay/0.1"));
|
assert!(outputs[0].contains("decay/0.0125"));
|
||||||
assert!(outputs[0].contains("sustain/0.7"));
|
assert!(outputs[0].contains("sustain/0.7"));
|
||||||
assert!(outputs[0].contains("release/0.3"));
|
assert!(outputs[0].contains("release/0.0375"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -66,17 +67,17 @@ fn filter_params() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn adsr_sets_all_envelope_params() {
|
fn adsr_sets_all_envelope_params() {
|
||||||
let outputs = expect_outputs(r#""synth" s 0.01 0.1 0.5 0.3 adsr ."#, 1);
|
let outputs = expect_outputs(r#""synth" s 0.01 0.1 0.5 0.3 adsr ."#, 1);
|
||||||
assert!(outputs[0].contains("attack/0.01"));
|
assert!(outputs[0].contains("attack/0.00125"));
|
||||||
assert!(outputs[0].contains("decay/0.1"));
|
assert!(outputs[0].contains("decay/0.0125"));
|
||||||
assert!(outputs[0].contains("sustain/0.5"));
|
assert!(outputs[0].contains("sustain/0.5"));
|
||||||
assert!(outputs[0].contains("release/0.3"));
|
assert!(outputs[0].contains("release/0.0375"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ad_sets_attack_decay_sustain_zero() {
|
fn ad_sets_attack_decay_sustain_zero() {
|
||||||
let outputs = expect_outputs(r#""synth" s 0.01 0.1 ad ."#, 1);
|
let outputs = expect_outputs(r#""synth" s 0.01 0.1 ad ."#, 1);
|
||||||
assert!(outputs[0].contains("attack/0.01"));
|
assert!(outputs[0].contains("attack/0.00125"));
|
||||||
assert!(outputs[0].contains("decay/0.1"));
|
assert!(outputs[0].contains("decay/0.0125"));
|
||||||
assert!(outputs[0].contains("sustain/0"));
|
assert!(outputs[0].contains("sustain/0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user