Feat: UI/UX and ducking compressor

This commit is contained in:
2026-02-24 02:57:27 +01:00
parent 848d0e773f
commit 2de49bdeba
24 changed files with 402 additions and 71 deletions

View File

@@ -76,6 +76,7 @@ pub enum Op {
PCycle(Option<SourceSpan>),
Choose(Option<SourceSpan>),
Bounce(Option<SourceSpan>),
PBounce(Option<SourceSpan>),
WChoose(Option<SourceSpan>),
ChanceExec(Option<SourceSpan>),
ProbExec(Option<SourceSpan>),
@@ -84,6 +85,9 @@ pub enum Op {
Ftom,
SetTempo,
Every(Option<SourceSpan>),
Except(Option<SourceSpan>),
EveryOffset(Option<SourceSpan>),
ExceptOffset(Option<SourceSpan>),
Bjork(Option<SourceSpan>),
PBjork(Option<SourceSpan>),
Quotation(Arc<[Op]>, Option<SourceSpan>),

View File

@@ -799,16 +799,20 @@ impl Forth {
drain_select_run(count, idx, stack, outputs, cmd)?;
}
Op::Bounce(word_span) => {
Op::Bounce(word_span) | Op::PBounce(word_span) => {
let count = pop_int(stack)? as usize;
if count == 0 {
return Err("bounce count must be > 0".into());
}
let counter = match &ops[pc] {
Op::Bounce(_) => ctx.runs,
_ => ctx.iter,
};
let idx = if count == 1 {
0
} else {
let period = 2 * (count - 1);
let raw = ctx.runs % period;
let raw = counter % period;
if raw < count { raw } else { period - raw }
};
if let Some(span) = word_span {
@@ -895,6 +899,47 @@ impl Forth {
}
}
Op::Except(word_span) => {
let n = pop_int(stack)?;
let quot = pop(stack)?;
if n <= 0 {
return Err("except count must be > 0".into());
}
let result = ctx.iter as i64 % n != 0;
record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
if result {
run_quotation(quot, stack, outputs, cmd)?;
}
}
Op::EveryOffset(word_span) => {
let offset = pop_int(stack)?;
let n = pop_int(stack)?;
let quot = pop(stack)?;
if n <= 0 {
return Err("every+ count must be > 0".into());
}
let result = ctx.iter as i64 % n == offset.rem_euclid(n);
record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
if result {
run_quotation(quot, stack, outputs, cmd)?;
}
}
Op::ExceptOffset(word_span) => {
let offset = pop_int(stack)?;
let n = pop_int(stack)?;
let quot = pop(stack)?;
if n <= 0 {
return Err("except+ count must be > 0".into());
}
let result = ctx.iter as i64 % n != offset.rem_euclid(n);
record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
if result {
run_quotation(quot, stack, outputs, cmd)?;
}
}
Op::Bjork(word_span) | Op::PBjork(word_span) => {
let n = pop_int(stack)?;
let k = pop_int(stack)?;

View File

@@ -67,8 +67,12 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
"pcycle" => Op::PCycle(None),
"choose" => Op::Choose(None),
"bounce" => Op::Bounce(None),
"pbounce" => Op::PBounce(None),
"wchoose" => Op::WChoose(None),
"every" => Op::Every(None),
"except" => Op::Except(None),
"every+" => Op::EveryOffset(None),
"except+" => Op::ExceptOffset(None),
"bjork" => Op::Bjork(None),
"pbjork" => Op::PBjork(None),
"chance" => Op::ChanceExec(None),
@@ -204,8 +208,8 @@ fn attach_span(op: &mut Op, span: SourceSpan) {
match op {
Op::Rand(s) | Op::ExpRand(s) | Op::LogRand(s) | Op::Coin(s)
| Op::Choose(s) | Op::WChoose(s) | Op::Cycle(s) | Op::PCycle(s)
| Op::Bounce(s) | Op::ChanceExec(s) | Op::ProbExec(s)
| Op::Every(s)
| Op::Bounce(s) | Op::PBounce(s) | Op::ChanceExec(s) | Op::ProbExec(s)
| Op::Every(s) | Op::Except(s) | Op::EveryOffset(s) | Op::ExceptOffset(s)
| Op::Bjork(s) | Op::PBjork(s)
| Op::Count(s) | Op::Index(s) => *s = Some(span),
_ => {}

View File

@@ -959,4 +959,45 @@ pub(super) const WORDS: &[Word] = &[
compile: Param,
varargs: true,
},
// Compressor
Word {
name: "comp",
aliases: &[],
category: "Compressor",
stack: "(v.. --)",
desc: "Set sidechain duck amount (0-1)",
example: "0.8 comp",
compile: Param,
varargs: true,
},
Word {
name: "compattack",
aliases: &["cattack"],
category: "Compressor",
stack: "(v.. --)",
desc: "Set compressor attack time in seconds",
example: "0.01 compattack",
compile: Param,
varargs: true,
},
Word {
name: "comprelease",
aliases: &["crelease"],
category: "Compressor",
stack: "(v.. --)",
desc: "Set compressor release time in seconds",
example: "0.15 comprelease",
compile: Param,
varargs: true,
},
Word {
name: "comporbit",
aliases: &["corbit"],
category: "Compressor",
stack: "(v.. --)",
desc: "Set sidechain source orbit",
example: "0 comporbit",
compile: Param,
varargs: true,
},
];

View File

@@ -113,6 +113,16 @@ pub(super) const WORDS: &[Word] = &[
compile: Simple,
varargs: true,
},
Word {
name: "pbounce",
aliases: &[],
category: "Probability",
stack: "(v1..vn n -- selected)",
desc: "Ping-pong cycle through n items by pattern iteration",
example: "60 64 67 72 4 pbounce",
compile: Simple,
varargs: true,
},
Word {
name: "index",
aliases: &[],
@@ -214,6 +224,36 @@ pub(super) const WORDS: &[Word] = &[
compile: Simple,
varargs: false,
},
Word {
name: "except",
aliases: &[],
category: "Time",
stack: "(quot n --)",
desc: "Execute quotation on all iterations except every nth",
example: "{ 2 distort } 4 except",
compile: Simple,
varargs: false,
},
Word {
name: "every+",
aliases: &[],
category: "Time",
stack: "(quot n offset --)",
desc: "Execute quotation every nth iteration with phase offset",
example: "{ snare } 4 2 every+ => fires at iter 2, 6, 10...",
compile: Simple,
varargs: false,
},
Word {
name: "except+",
aliases: &[],
category: "Time",
stack: "(quot n offset --)",
desc: "Skip quotation every nth iteration with phase offset",
example: "{ snare } 4 2 except+ => skips at iter 2, 6, 10...",
compile: Simple,
varargs: false,
},
Word {
name: "bjork",
aliases: &[],