From 02990127256e45c96b0a95e63780263958c25bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Sat, 31 Jan 2026 13:46:43 +0100 Subject: [PATCH] Work on documentation --- crates/forth/src/compiler.rs | 70 ++++++++++-- crates/forth/src/ops.rs | 2 +- crates/forth/src/vm.rs | 133 +++++++++++++--------- crates/forth/src/words.rs | 22 ++-- docs/about_forth.md | 23 ++-- docs/arithmetic.md | 84 -------------- docs/chaining.md | 2 - docs/chords.md | 2 - docs/comparison.md | 41 ------- docs/conditionals.md | 2 - docs/context.md | 2 - docs/cycles.md | 2 - docs/definitions.md | 95 +++++++++++++++- docs/delay_reverb.md | 51 --------- docs/dictionary.md | 22 ++-- docs/emitting.md | 48 -------- docs/engine_intro.md | 55 +++++++++ docs/engine_settings.md | 58 ++++++++++ docs/envelopes.md | 2 - docs/eq_stereo.md | 65 ----------- docs/filters.md | 2 - docs/generators.md | 65 ----------- docs/ladder_filters.md | 47 -------- docs/lfo.md | 74 ------------ docs/link.md | 2 - docs/lofi.md | 58 ---------- docs/logic.md | 89 --------------- docs/mod_fx.md | 61 ---------- docs/modulation.md | 2 - docs/notes.md | 2 - docs/oddities.md | 214 +++++++++++++++++++++++++++++++++++ docs/oscillators.md | 75 ------------ docs/patterns.md | 2 - docs/pitch_envelope.md | 44 ------- docs/probability.md | 2 - docs/randomness.md | 2 - docs/samples.md | 76 ------------- docs/scales.md | 2 - docs/selection.md | 62 ---------- docs/stack.md | 159 +++++++++++--------------- docs/timing.md | 2 - docs/variables.md | 2 - docs/vibrato.md | 44 ------- docs/wavetables.md | 55 --------- src/views/help_view.rs | 62 +--------- 45 files changed, 668 insertions(+), 1318 deletions(-) delete mode 100644 docs/arithmetic.md delete mode 100644 docs/chaining.md delete mode 100644 docs/chords.md delete mode 100644 docs/comparison.md delete mode 100644 docs/conditionals.md delete mode 100644 docs/context.md delete mode 100644 docs/cycles.md delete mode 100644 docs/delay_reverb.md delete mode 100644 docs/emitting.md create mode 100644 docs/engine_intro.md create mode 100644 docs/engine_settings.md delete mode 100644 docs/envelopes.md delete mode 100644 docs/eq_stereo.md delete mode 100644 docs/filters.md delete mode 100644 docs/generators.md delete mode 100644 docs/ladder_filters.md delete mode 100644 docs/lfo.md delete mode 100644 docs/link.md delete mode 100644 docs/lofi.md delete mode 100644 docs/logic.md delete mode 100644 docs/mod_fx.md delete mode 100644 docs/modulation.md delete mode 100644 docs/notes.md create mode 100644 docs/oddities.md delete mode 100644 docs/oscillators.md delete mode 100644 docs/patterns.md delete mode 100644 docs/pitch_envelope.md delete mode 100644 docs/probability.md delete mode 100644 docs/randomness.md delete mode 100644 docs/samples.md delete mode 100644 docs/scales.md delete mode 100644 docs/selection.md delete mode 100644 docs/timing.md delete mode 100644 docs/variables.md delete mode 100644 docs/vibrato.md delete mode 100644 docs/wavetables.md diff --git a/crates/forth/src/compiler.rs b/crates/forth/src/compiler.rs index 7f8cee3..524fd4a 100644 --- a/crates/forth/src/compiler.rs +++ b/crates/forth/src/compiler.rs @@ -61,7 +61,13 @@ fn tokenize(input: &str) -> Vec { continue; } // single ; is a word, create token - tokens.push(Token::Word(";".to_string(), SourceSpan { start: pos, end: pos + 1 })); + tokens.push(Token::Word( + ";".to_string(), + SourceSpan { + start: pos, + end: pos + 1, + }, + )); continue; } @@ -96,15 +102,33 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result, String> { while i < tokens.len() { match &tokens[i] { - Token::Int(n, span) => ops.push(Op::PushInt(*n, Some(*span))), - Token::Float(f, span) => ops.push(Op::PushFloat(*f, Some(*span))), + Token::Int(n, span) => { + let key = n.to_string(); + if let Some(body) = dict.lock().unwrap().get(&key).cloned() { + ops.extend(body); + } else { + ops.push(Op::PushInt(*n, Some(*span))); + } + } + Token::Float(f, span) => { + let key = f.to_string(); + if let Some(body) = dict.lock().unwrap().get(&key).cloned() { + ops.extend(body); + } else { + ops.push(Op::PushFloat(*f, Some(*span))); + } + } Token::Str(s, span) => ops.push(Op::PushStr(s.clone(), Some(*span))), Token::Word(w, span) => { let word = w.as_str(); if word == "{" { - let (quote_ops, consumed, end_span) = compile_quotation(&tokens[i + 1..], dict)?; + let (quote_ops, consumed, end_span) = + compile_quotation(&tokens[i + 1..], dict)?; i += consumed; - let body_span = SourceSpan { start: span.start, end: end_span.end }; + let body_span = SourceSpan { + start: span.start, + end: end_span.end, + }; ops.push(Op::Quotation(quote_ops, Some(body_span))); } else if word == "}" { return Err("unexpected }".into()); @@ -115,7 +139,8 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result, String> { } else if word == ";" { return Err("unexpected ;".into()); } else if word == "if" { - let (then_ops, else_ops, consumed, then_span, else_span) = compile_if(&tokens[i + 1..], dict)?; + let (then_ops, else_ops, consumed, then_span, else_span) = + compile_if(&tokens[i + 1..], dict)?; i += consumed; if else_ops.is_empty() { ops.push(Op::BranchIfZero(then_ops.len(), then_span, None)); @@ -137,7 +162,10 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result, String> { Ok(ops) } -fn compile_quotation(tokens: &[Token], dict: &Dictionary) -> Result<(Vec, usize, SourceSpan), String> { +fn compile_quotation( + tokens: &[Token], + dict: &Dictionary, +) -> Result<(Vec, usize, SourceSpan), String> { let mut depth = 1; let mut end_idx = None; @@ -172,13 +200,18 @@ fn token_span(tok: &Token) -> Option { } } -fn compile_colon_def(tokens: &[Token], dict: &Dictionary) -> Result<(usize, String, Vec), String> { +fn compile_colon_def( + tokens: &[Token], + dict: &Dictionary, +) -> Result<(usize, String, Vec), String> { if tokens.is_empty() { return Err("expected word name after ':'".into()); } let name = match &tokens[0] { Token::Word(w, _) => w.clone(), - _ => return Err("expected word name after ':'".into()), + Token::Int(n, _) => n.to_string(), + Token::Float(f, _) => f.to_string(), + Token::Str(s, _) => s.clone(), }; let mut semi_pos = None; for (i, tok) in tokens[1..].iter().enumerate() { @@ -198,11 +231,26 @@ fn compile_colon_def(tokens: &[Token], dict: &Dictionary) -> Result<(usize, Stri fn tokens_span(tokens: &[Token]) -> Option { let first = tokens.first().and_then(token_span)?; let last = tokens.last().and_then(token_span)?; - Some(SourceSpan { start: first.start, end: last.end }) + Some(SourceSpan { + start: first.start, + end: last.end, + }) } #[allow(clippy::type_complexity)] -fn compile_if(tokens: &[Token], dict: &Dictionary) -> Result<(Vec, Vec, usize, Option, Option), String> { +fn compile_if( + tokens: &[Token], + dict: &Dictionary, +) -> Result< + ( + Vec, + Vec, + usize, + Option, + Option, + ), + String, +> { let mut depth = 1; let mut else_pos = None; let mut then_pos = None; diff --git a/crates/forth/src/ops.rs b/crates/forth/src/ops.rs index ecbad3b..f71a3f0 100644 --- a/crates/forth/src/ops.rs +++ b/crates/forth/src/ops.rs @@ -79,11 +79,11 @@ pub enum Op { Loop, Degree(&'static [i64]), Oct, - EmitN, ClearCmd, SetSpeed, At, IntRange, Generate, GeomRange, + Times, } diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 0a4d0e0..2850274 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -91,45 +91,51 @@ impl Forth { let mut pc = 0; let trace_cell = std::cell::RefCell::new(trace); - let run_quotation = - |quot: Value, stack: &mut Vec, outputs: &mut Vec, cmd: &mut CmdRegister| -> Result<(), String> { - match quot { - Value::Quotation(quot_ops, body_span) => { - if let Some(span) = body_span { - if let Some(trace) = trace_cell.borrow_mut().as_mut() { - trace.executed_spans.push(span); - } + let run_quotation = |quot: Value, + stack: &mut Vec, + outputs: &mut Vec, + cmd: &mut CmdRegister| + -> Result<(), String> { + match quot { + Value::Quotation(quot_ops, body_span) => { + if let Some(span) = body_span { + if let Some(trace) = trace_cell.borrow_mut().as_mut() { + trace.executed_spans.push(span); } - let mut trace_opt = trace_cell.borrow_mut().take(); - self.execute_ops( - "_ops, - ctx, - stack, - outputs, - cmd, - trace_opt.as_deref_mut(), - )?; - *trace_cell.borrow_mut() = trace_opt; - Ok(()) } - _ => Err("expected quotation".into()), - } - }; - - let select_and_run = - |selected: Value, stack: &mut Vec, outputs: &mut Vec, cmd: &mut CmdRegister| -> Result<(), String> { - if let Some(span) = selected.span() { - if let Some(trace) = trace_cell.borrow_mut().as_mut() { - trace.selected_spans.push(span); - } - } - if matches!(selected, Value::Quotation(..)) { - run_quotation(selected, stack, outputs, cmd) - } else { - stack.push(selected); + let mut trace_opt = trace_cell.borrow_mut().take(); + self.execute_ops( + "_ops, + ctx, + stack, + outputs, + cmd, + trace_opt.as_deref_mut(), + )?; + *trace_cell.borrow_mut() = trace_opt; Ok(()) } - }; + _ => Err("expected quotation".into()), + } + }; + + let select_and_run = |selected: Value, + stack: &mut Vec, + outputs: &mut Vec, + cmd: &mut CmdRegister| + -> Result<(), String> { + if let Some(span) = selected.span() { + if let Some(trace) = trace_cell.borrow_mut().as_mut() { + trace.selected_spans.push(span); + } + } + if matches!(selected, Value::Quotation(..)) { + run_quotation(selected, stack, outputs, cmd) + } else { + stack.push(selected); + Ok(()) + } + }; let drain_select_run = |count: usize, idx: usize, @@ -146,15 +152,20 @@ impl Forth { select_and_run(selected, stack, outputs, cmd) }; - let emit_with_cycling = |cmd: &CmdRegister, emit_idx: usize, delta_secs: f64, outputs: &mut Vec| -> Result, String> { + let emit_with_cycling = |cmd: &CmdRegister, + emit_idx: usize, + delta_secs: f64, + outputs: &mut Vec| + -> Result, String> { let (sound_opt, params) = cmd.snapshot().ok_or("nothing to emit")?; let resolved_sound_val = sound_opt.map(|sv| resolve_cycling(sv, emit_idx)); let sound_str = match &resolved_sound_val { Some(v) => Some(v.as_str()?.to_string()), None => None, }; - let resolved_params: Vec<(String, String)> = - params.iter().map(|(k, v)| { + let resolved_params: Vec<(String, String)> = params + .iter() + .map(|(k, v)| { let resolved = resolve_cycling(v, emit_idx); if let Value::CycleList(_) = v { if let Some(span) = resolved.span() { @@ -164,8 +175,15 @@ impl Forth { } } (k.clone(), resolved.to_param_string()) - }).collect(); - emit_output(sound_str.as_deref(), &resolved_params, ctx.step_duration(), delta_secs, outputs); + }) + .collect(); + emit_output( + sound_str.as_deref(), + &resolved_params, + ctx.step_duration(), + delta_secs, + outputs, + ); Ok(resolved_sound_val.map(|v| v.into_owned())) }; @@ -369,7 +387,9 @@ impl Forth { trace.selected_spans.push(span); } } - if let Some(sound_val) = emit_with_cycling(cmd, emit_idx, delta_secs, outputs)? { + if let Some(sound_val) = + emit_with_cycling(cmd, emit_idx, delta_secs, outputs)? + { if let Some(span) = sound_val.span() { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.selected_spans.push(span); @@ -417,7 +437,11 @@ impl Forth { let a = stack.pop().ok_or("stack underflow")?; match (&a, &b) { (Value::Int(a_i, _), Value::Int(b_i, _)) => { - let (lo, hi) = if a_i <= b_i { (*a_i, *b_i) } else { (*b_i, *a_i) }; + let (lo, hi) = if a_i <= b_i { + (*a_i, *b_i) + } else { + (*b_i, *a_i) + }; let val = self.rng.lock().unwrap().gen_range(lo..=hi); stack.push(Value::Int(val, None)); } @@ -708,16 +732,6 @@ impl Forth { cmd.clear(); } - Op::EmitN => { - let n = stack.pop().ok_or("stack underflow")?.as_int()?; - if n < 0 { - return Err("emit count must be >= 0".into()); - } - for i in 0..n as usize { - emit_with_cycling(cmd, i, ctx.nudge_secs, outputs)?; - } - } - Op::IntRange => { let end = stack.pop().ok_or("stack underflow")?.as_int()?; let start = stack.pop().ok_or("stack underflow")?.as_int()?; @@ -748,6 +762,21 @@ impl Forth { } } + Op::Times => { + let quot = stack.pop().ok_or("stack underflow")?; + let count = stack.pop().ok_or("stack underflow")?.as_int()?; + if count < 0 { + return Err("times count must be >= 0".into()); + } + for i in 0..count { + self.vars + .lock() + .unwrap() + .insert("i".to_string(), Value::Int(i, None)); + run_quotation(quot.clone(), stack, outputs, cmd)?; + } + } + Op::GeomRange => { let count = stack.pop().ok_or("stack underflow")?.as_int()?; let ratio = stack.pop().ok_or("stack underflow")?.as_float()?; diff --git a/crates/forth/src/words.rs b/crates/forth/src/words.rs index 3b062b5..eea5e36 100644 --- a/crates/forth/src/words.rs +++ b/crates/forth/src/words.rs @@ -438,16 +438,6 @@ pub const WORDS: &[Word] = &[ compile: Simple, varargs: false, }, - Word { - name: ".!", - aliases: &[], - category: "Sound", - stack: "(n --)", - desc: "Emit current sound n times", - example: "\"kick\" s 4 .!", - compile: Simple, - varargs: true, - }, // Variables (prefix syntax: @name to fetch, !name to store) Word { name: "@", @@ -2280,6 +2270,16 @@ pub const WORDS: &[Word] = &[ compile: Simple, varargs: false, }, + Word { + name: "times", + aliases: &[], + category: "Control", + stack: "(n quot --)", + desc: "Execute quotation n times, @i holds current index", + example: "4 { @i . } times => 0 1 2 3", + compile: Simple, + varargs: false, + }, ]; pub(super) fn simple_op(name: &str) -> Option { @@ -2352,11 +2352,11 @@ pub(super) fn simple_op(name: &str) -> Option { "chain" => Op::Chain, "loop" => Op::Loop, "oct" => Op::Oct, - ".!" => Op::EmitN, "clear" => Op::ClearCmd, ".." => Op::IntRange, "gen" => Op::Generate, "geom.." => Op::GeomRange, + "times" => Op::Times, _ => return None, }) } diff --git a/docs/about_forth.md b/docs/about_forth.md index 9cbe28a..7b91063 100644 --- a/docs/about_forth.md +++ b/docs/about_forth.md @@ -1,16 +1,16 @@ # About Forth -Forth is a stack-based programming language created by Charles H. Moore in the early 1970s. It was designed for simplicity, directness, and interactive exploration. Forth has been used for many years to do scientific work and program embedded systems: controlling telescopes, running on devices used in space missions, etc. Forth quickly evolved into multiple implementations. None of them really reached an incredible popularity. Nonetheless, the ideas behind Forth continue to garner the interest of many different people in very different (often unrelated) fields. Nowadays, Forth languages are sometimes used by hackers and artists for their peculiarities. A Forth implementation is often simple, direct and beautiful. Forth is an elegant and minimal language to learn. It is easy to understand, to extend and to apply to a specific task. The Forth we use in Cagire is specialized in making live music. +Forth is a _stack-based_ programming language created by Charles H. Moore in the early 1970s. It was designed with simplicity, directness, and interactive exploration in mind. Forth has been used for many years to do scientific work and program embedded systems: it was used to control telescopes and was running on some devices used in space missions among other things. Forth quickly evolved into multiple implementations targetting various computer architectures. None of them really took off and became popular. Nonetheless, the ideas behind Forth continue to garner the interest of many different people in very different (often unrelated) fields. Nowadays, Forth languages are used by hackers and artists for their peculiarity. Forth is simple, direct and beautiful to implement. Forth is an elegant and minimal language to learn. It is easy to understand, to extend and to apply to a specific task. The Forth we use in Cagire is specialized in making live music. We think of it as a DSL: a _Domain Specific Language_. ## Why Forth? -Most programming languages nowadays use a complex syntax made of `variables`, `expressions` and `statements` like `x = 3 + 4`. Forth works differently. It is way more simple than that, has almost no syntax, and performs computations in a quite unique way. You push values onto a `stack` and apply `words` that transform them: +Most programming languages nowadays use a complex syntax made of `variables`, `expressions` and `statements` like `x = 3 + 4`. Forth works differently. It is way more simple than that, has almost no syntax and performs computations in a quite unique way. You push values onto a `stack` and apply `words` that transform them: ```forth 3 4 + ``` -This program leaves the number `7` on the stack. There are no variables, no parentheses, no syntax to remember. You just end up with words and numbers separated by spaces. For live coding music, this directness is quite exciting. All you do is thinking in terms of transformations and adding things to the stack: take a note, shift it up, add reverb, play it. +This program leaves the number `7` on the stack. There are no variables, no parentheses, no syntax to remember. You just end up with words and numbers separated by spaces. For live coding music, this directness is quite exciting. All you do is think in terms of transformations and add things to the stack: take a note, shift it up, add reverb, play it. ## The Stack @@ -40,7 +40,7 @@ Words compose naturally on the stack. To double a number: 3 dup + ( 3 3 +) ``` -There are a lot of words in a Forth, and thus, Cagire has a `Dictionary` embedded directly into the application. You can also create your own words. They will work just like the already existing words. There are good reasons to create new words on-the-fly: +There are a lot of words in a Forth and thus, Cagire has a `Dictionary` embedded directly into the application. You can also create your own words. They will work just like the already existing words. There are good reasons to create new words on-the-fly: - To make synth definitions. - To abstract _some piece of code_ that you use frequently. @@ -69,11 +69,9 @@ For example, `+` has the signature `( a b -- sum )`. It takes two values and lea ## The Command Register -Traditional Forth programs print text to a terminal. Cagire's Forth builds sound commands instead. This happens through an invisible accumulator called the command register. - -The command register has two parts: -- A **sound name** (what instrument to play) -- A list of **parameters** (how to play it) +Traditional Forth programs print text to a terminal. Cagire's Forth builds sound commands instead. This happens through an invisible accumulator called the command register. The command register has two parts: +- a **sound name** (what instrument to play) +- a list of **parameters** (how to play it) Three types of words interact with it: @@ -102,7 +100,7 @@ Each line adds something to the register. The final `.` triggers the sound. You "sine" s c4 note 0.5 gain 0.3 decay 0.4 verb . ``` -The order of parameters does not matter. You can even emit multiple times in a single step: If you need to discard the register without emitting, use `clear`: +The order of parameters does not matter. You can even emit multiple times in a single step. If you need to discard the register without emitting, use `clear`: ```forth "kick" s 0.5 gain clear ;; nothing plays, register is emptied @@ -110,3 +108,8 @@ The order of parameters does not matter. You can even emit multiple times in a s ``` This is useful when conditionals might cancel a sound before it emits. + +## More details + +- Each step has its own stack and independant runtime. +- Word definitions and variable definitions are shared by all steps. diff --git a/docs/arithmetic.md b/docs/arithmetic.md deleted file mode 100644 index 3b6bcd1..0000000 --- a/docs/arithmetic.md +++ /dev/null @@ -1,84 +0,0 @@ -# Arithmetic - -Basic math operations. All arithmetic words pop their operands and push the result. - -## Basic Operations - -``` -3 4 + ( 7 ) -10 3 - ( 7 ) -3 4 * ( 12 ) -10 3 / ( 3.333... ) -10 3 mod ( 1 ) -``` - -Division always produces a float. Use `floor` if you need an integer result. - -## Negative Numbers - -``` -5 neg ( -5 ) --3 abs ( 3 ) -``` - -## Rounding - -``` -3.7 floor ( 3 ) -3.2 ceil ( 4 ) -3.5 round ( 4 ) -``` - -## Min and Max - -``` -3 7 min ( 3 ) -3 7 max ( 7 ) -``` - -## Power and Root - -``` -2 3 pow ( 8 ) -9 sqrt ( 3 ) -``` - -## Examples - -Calculate a frequency ratio: - -``` -440 2 12 / pow * ( 440 * 2^(1/12) ≈ 466.16 ) -``` - -Clamp a value between 0 and 1: - -``` -1.5 0 max 1 min ( 1 ) --0.5 0 max 1 min ( 0 ) -``` - -Scale a 0-1 range to 200-800: - -``` -0.5 600 * 200 + ( 500 ) -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `+` | (a b -- sum) | Add | -| `-` | (a b -- diff) | Subtract | -| `*` | (a b -- prod) | Multiply | -| `/` | (a b -- quot) | Divide | -| `mod` | (a b -- rem) | Modulo | -| `neg` | (a -- -a) | Negate | -| `abs` | (a -- \|a\|) | Absolute value | -| `floor` | (a -- n) | Round down | -| `ceil` | (a -- n) | Round up | -| `round` | (a -- n) | Round to nearest | -| `min` | (a b -- min) | Minimum | -| `max` | (a b -- max) | Maximum | -| `pow` | (a b -- a^b) | Power | -| `sqrt` | (a -- √a) | Square root | diff --git a/docs/chaining.md b/docs/chaining.md deleted file mode 100644 index 34dadc2..0000000 --- a/docs/chaining.md +++ /dev/null @@ -1,2 +0,0 @@ -# Chaining - diff --git a/docs/chords.md b/docs/chords.md deleted file mode 100644 index 560aa02..0000000 --- a/docs/chords.md +++ /dev/null @@ -1,2 +0,0 @@ -# Chords - diff --git a/docs/comparison.md b/docs/comparison.md deleted file mode 100644 index e154c78..0000000 --- a/docs/comparison.md +++ /dev/null @@ -1,41 +0,0 @@ -# Comparison - -Compare values and produce boolean results (0 or 1). - -## Equality - -```forth -3 3 = ( 1 - equal ) -3 4 = ( 0 - not equal ) -3 4 != ( 1 - not equal ) -3 4 <> ( 1 - not equal, alternative ) -``` - -## Ordering - -```forth -2 3 lt ( 1 - less than ) -3 2 gt ( 1 - greater than ) -3 3 <= ( 1 - less or equal ) -3 3 >= ( 1 - greater or equal ) -``` - -## With Conditionals - -```forth -step 4 lt { "kick" s . } ? ( kick on first 4 steps ) - -beat 8 >= { 0.5 gain } ? ( quieter after beat 8 ) -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `=` | (a b -- bool) | Equal | -| `!=` | (a b -- bool) | Not equal | -| `<>` | (a b -- bool) | Not equal (alias) | -| `lt` | (a b -- bool) | Less than | -| `gt` | (a b -- bool) | Greater than | -| `<=` | (a b -- bool) | Less or equal | -| `>=` | (a b -- bool) | Greater or equal | diff --git a/docs/conditionals.md b/docs/conditionals.md deleted file mode 100644 index 6db3aa1..0000000 --- a/docs/conditionals.md +++ /dev/null @@ -1,2 +0,0 @@ -# Conditionals - diff --git a/docs/context.md b/docs/context.md deleted file mode 100644 index 8e6b9ba..0000000 --- a/docs/context.md +++ /dev/null @@ -1,2 +0,0 @@ -# Context - diff --git a/docs/cycles.md b/docs/cycles.md deleted file mode 100644 index c261741..0000000 --- a/docs/cycles.md +++ /dev/null @@ -1,2 +0,0 @@ -# Cycles - diff --git a/docs/definitions.md b/docs/definitions.md index 45a4d49..2daee4c 100644 --- a/docs/definitions.md +++ b/docs/definitions.md @@ -1,2 +1,95 @@ -# Custom Words +# Creating Words +One of Forth's most powerful features is the ability to define new words. A word definition gives a name to a sequence of operations. Once defined, you can use the new word just like any built-in word. + +## The Syntax + +Use `:` to start a definition and `;` to end it: + +```forth +: double dup + ; +``` + +This creates a word called `double` that duplicates the top value and adds it to itself. Now you can use it: + +```forth +3 double ;; leaves 6 on the stack +5 double ;; leaves 10 on the stack +``` + +The definition is simple: everything between `:` and `;` becomes the body of the word. + +## Definitions Are Shared + +When you define a word in one step, it becomes available to all other steps. This is how you share code across your pattern. Define your synths, rhythms, and utilities once, then use them everywhere. + +Step 0: +```forth +: bass "saw" s 0.8 gain 800 lpf ; +``` + +Step 4: +```forth +c2 note bass . +``` + +Step 8: +```forth +g2 note bass . +``` + +The `bass` word carries the sound design. Each step just adds a note and plays. + +## Redefining Words + +You can redefine any word, including built-in ones: + +```forth +: dup drop ; +``` + +Now `dup` does the opposite of what it used to do. This is powerful but dangerous. Redefining core words can break things in subtle ways. + +You can even redefine numbers: + +```forth +: 2 4 ; +``` + +Now `2` pushes `4` onto the stack. The number two no longer exists in your session. This is a classic Forth demonstration: nothing is sacred, everything can be redefined. + +## Practical Uses + +**Synth definitions** save you from repeating sound design: + +```forth +: pad "sine" s 0.3 gain 2 attack 0.5 verb ; +``` + +**Transpositions** and musical helpers: + +```forth +: octup 12 + ; +: octdn 12 - ; +``` + +## Words That Emit + +A word can contain `.` to emit sounds directly: + +```forth +: kick "kick" s . ; +: hat "hat" s 0.4 gain . ; +``` + +Then a step becomes trivial: + +```forth +kick hat +``` + +Two sounds, two words, no clutter. + +## Stack Effects + +When you create a word, think about what it expects on the stack and what it leaves behind. The word `double` expects one number and leaves one number. The word `kick` expects nothing and leaves nothing (it emits a sound as a side effect). Well-designed words have clear stack effects. This makes them easy to combine. diff --git a/docs/delay_reverb.md b/docs/delay_reverb.md deleted file mode 100644 index b6dde16..0000000 --- a/docs/delay_reverb.md +++ /dev/null @@ -1,51 +0,0 @@ -# Delay & Reverb - -Add space and depth to your sounds with time-based effects. - -## Delay - -```forth -"snare" s 0.3 delay . ( delay mix ) -"snare" s 0.25 delaytime . ( delay time in seconds ) -"snare" s 0.5 delayfeedback . ( feedback amount ) -"snare" s 1 delaytype . ( delay type ) -``` - -## Reverb - -```forth -"pad" s 0.3 verb . ( reverb mix ) -"pad" s 2 verbdecay . ( decay time ) -"pad" s 0.5 verbdamp . ( high frequency damping ) -"pad" s 0.02 verbpredelay . ( predelay time ) -"pad" s 0.7 verbdiff . ( diffusion ) -"pad" s 1 size . ( room size ) -``` - -## Combined Example - -```forth -"keys" s - c4 note - 0.2 delay - 0.375 delaytime - 0.4 delayfeedback - 0.25 verb - 1.5 verbdecay -. -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `delay` | (f --) | Set delay mix | -| `delaytime` | (f --) | Set delay time | -| `delayfeedback` | (f --) | Set delay feedback | -| `delaytype` | (n --) | Set delay type | -| `verb` | (f --) | Set reverb mix | -| `verbdecay` | (f --) | Set reverb decay | -| `verbdamp` | (f --) | Set reverb damping | -| `verbpredelay` | (f --) | Set reverb predelay | -| `verbdiff` | (f --) | Set reverb diffusion | -| `size` | (f --) | Set reverb size | diff --git a/docs/dictionary.md b/docs/dictionary.md index 9c42b0f..621aca4 100644 --- a/docs/dictionary.md +++ b/docs/dictionary.md @@ -6,13 +6,15 @@ Cagire includes a built-in dictionary of all the internal Forth words. Press `Ct The dictionary shows every available word organized by category: -- **Stack**: Manipulation words like `dup`, `swap`, `drop` -- **Arithmetic**: Math operations -- **Sound**: Sound sources and emission -- **Filter**, **Envelope**, **Effects**: Sound shaping -- **Context**: Sequencer state like `step`, `beat`, `tempo` +- **Stack**: Manipulation words like `dup`, `swap`, `drop`. +- **Arithmetic**: Math operations. +- **Sound**: Sound sources and emission. +- **Filter**, **Envelope**, **Effects**: Sound shaping. +- **Context**: Sequencer state like `step`, `beat`, `tempo`. - And many more... +This tutorial will not teach you how to use all words. The syntax is very uniform and you can quickly learn a new word when necessary. We encourage you to explore as you play, this is the best way to learn. The tutorial will remain focused on various topics that require you to apply knowledge to a given task or specific context. + ## Navigation | Key | Action | @@ -23,8 +25,6 @@ The dictionary shows every available word organized by category: | `/` or `Ctrl+F` | Search | | `Esc` | Clear search | -## Word Information - Each word entry shows: - **Name** and aliases @@ -32,12 +32,6 @@ Each word entry shows: - **Description**: What the word does - **Example**: How to use it -## Search - Press `/` to search across all words. The search matches word names, aliases, and descriptions. Press `Esc` to clear and return to browsing. -## Tips - -- Use the dictionary while writing scripts to check stack effects -- Categories group related words together -- Some words have shorter aliases (e.g., `sound` → `s`) +Use the dictionary while writing scripts to check stack effects and study their behavior. Some words also come with shorter aliases (e.g., `sound` → `s`). You will learn aliases quite naturally, because aliases are usually reserved for very common words. diff --git a/docs/emitting.md b/docs/emitting.md deleted file mode 100644 index 16907e6..0000000 --- a/docs/emitting.md +++ /dev/null @@ -1,48 +0,0 @@ -# Emitting Sounds - -The core of Cagire is emitting sounds. Every step script builds up sound commands and emits them to the audio engine. - -## The Sound Register - -Before emitting, you must select a sound source using `sound` (or its alias `s`): - -```forth -"kick" sound -"kick" s ( same thing, shorter ) -``` - -This sets the current sound register. All subsequent parameter words modify this sound until you emit or clear it. - -## Emitting - -The `.` word emits the current sound: - -```forth -"kick" s . ( emit one kick ) -"kick" s . . . ( emit three kicks ) -``` - -Use `.!` to emit multiple times: - -```forth -"kick" s 4 .! ( emit four kicks ) -``` - -## Clearing - -The `clear` word resets the sound register and all parameters: - -```forth -"kick" s 0.5 gain . clear "hat" s . -``` - -This is useful when you want to emit different sounds with independent parameters in the same step. - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `sound` / `s` | (name --) | Set sound source | -| `.` | (--) | Emit current sound | -| `.!` | (n --) | Emit current sound n times | -| `clear` | (--) | Clear sound register and params | diff --git a/docs/engine_intro.md b/docs/engine_intro.md new file mode 100644 index 0000000..0da48ac --- /dev/null +++ b/docs/engine_intro.md @@ -0,0 +1,55 @@ +# Introduction + +Cagire includes an audio engine called `Doux`. No external software is needed to make sound. `Doux` is an opinionated, semi-modular synthesizer. It was designed for live coding environments and works by receiving command strings that describe sounds. Despite its fixed architecture,`Doux` is extremely versatile and will likely cover most of the audio needs of a live coder. + +## How It Works + +When you write a Forth script and emit (`.`), the script produces a command string. This command travels to the audio engine, which interprets it and creates a voice. The voice plays until its envelope finishes or until it is killed by another voice. You can also spawn infinite voices, but you will need to manage their lifecycle manually, otherwise they will never stop. + +```forth +saw s c4 note 0.8 gain 0.3 verb . +``` + +## Voices + +Each `emit` (`.`) creates or manages a voice by sending parameters. Voices are independent sound generators with their own oscillator, envelope, and effects. The engine can run many voices at once (up to `128`, default `32`). When you exceed the voice limit, the oldest voice is stolen (a process called _round robin scheduling_). You can monitor voice usage on the Engine page: + +- **Active voices**: how many are playing right now. +- **Peak voices**: the maximum reached since last reset. + +Press `r` on the Engine page to reset the peak counter. + +## Parameters + +After selecting a sound source, you add parameters. Each parameter word takes a value from the stack and stores it in the command register: + +```forth +saw s + c4 note ;; pitch + 0.5 gain ;; volume + 0.1 attack ;; envelope attack time + 2000 lpf ;; lowpass filter at 2kHz + 0.3 verb ;; reverb mix +. +``` + +Parameters can appear in any order. They accumulate until you emit. You can clear the register using the `clear` word. + +## Controlling Existing Voices + +You can emit without a sound name. In this case, no new voice is created. Instead, the parameters are sent to control an existing voice. Use `voice` with an ID to target a specific voice: + +```forth +0 voice 500 freq . ;; change frequency on voice 0 +``` + +This is useful for modulating long-running or infinite voices. Set up a drone on one step with a known voice ID, then tweak its parameters from other steps. + +## Hush and Panic + +Two emergency controls exist on the Engine page: + +- `h` - **Hush**: gracefully fade out all voices +- `p` - **Panic**: immediately kill all voices + +Use hush when things get too loud. Use panic when things go wrong. diff --git a/docs/engine_settings.md b/docs/engine_settings.md new file mode 100644 index 0000000..cbc4f8a --- /dev/null +++ b/docs/engine_settings.md @@ -0,0 +1,58 @@ +# Settings + +The audio engine can be configured through the Engine page or via command-line arguments. Settings are saved and restored between sessions. + +## Engine Page + +Press `Ctrl+Right` until you reach the Engine page. Here you can see the engine status and adjust settings. + +### Display + +The right side of the page shows visualizations: + +- **Scope**: oscilloscope showing the audio waveform +- **Spectrum**: 32-band frequency analyzer + +### Settings + +Navigate with arrow keys, adjust values with left/right: + +- **Output Device**: where sound goes (speakers, headphones, interface). +- **Input Device**: what audio input source to use (microphone, line-in, etc.). +- **Channels**: number of output channels (2 for stereo). +- **Buffer Size**: audio buffer in samples (64-4096). +- **Max Voices**: polyphony limit (1-128, default 32). +- **Lookahead**: scheduling lookahead in milliseconds (0-50, default 15). + +### Buffer Size + +Smaller buffers mean lower latency but higher CPU load. Larger buffers are safer but feel sluggish. + +| Buffer | Latency at 44.1kHz | +|--------|-------------------| +| 64 | ~1.5ms | +| 128 | ~3ms | +| 256 | ~6ms | +| 512 | ~12ms | +| 1024 | ~23ms | + +Start with 512. Lower it if you need tighter timing. Raise it if you hear glitches. + +### Samples + +The engine indexes audio files from your sample directories. Add directories with `A`, remove with `D`. The sample count shows how many files are indexed. You can load as many sample banks as you want, they are not really loaded into memory but _lazily loaded_. They are loaded on demand when you play a sample. Samples are referenced by filename without extension: + +```forth +kick s . ;; plays kick.wav, kick.mp3, etc. +``` + +## Troubleshooting + +* **No sound**: Check output device selection. + * Try the test sound (`t`) on Engine page). + +* **Glitches/crackling**: Increase buffer size, restart the Engine. + +* **High CPU**: Reduce max voices. Disable scope/spectrum. Increase buffer size. + +* **Samples not found**: Check sample directories on Engine page. Filenames are case-sensitive on some systems. diff --git a/docs/envelopes.md b/docs/envelopes.md deleted file mode 100644 index e19ce71..0000000 --- a/docs/envelopes.md +++ /dev/null @@ -1,2 +0,0 @@ -# Envelopes - diff --git a/docs/eq_stereo.md b/docs/eq_stereo.md deleted file mode 100644 index cf92723..0000000 --- a/docs/eq_stereo.md +++ /dev/null @@ -1,65 +0,0 @@ -# EQ & Stereo - -Shape the frequency balance and stereo image of your sounds. - -## Three-Band EQ - -```forth -"mix" s - 3 eqlo ( boost low shelf by 3dB ) - -2 eqmid ( cut mids by 2dB ) - 1 eqhi ( boost high shelf by 1dB ) -. -``` - -## Tilt EQ - -A simple one-knob EQ that tilts the frequency balance: - -```forth -"bright" s 0.5 tilt . ( brighter ) -"dark" s -0.5 tilt . ( darker ) -``` - -Range: -1 (dark) to 1 (bright) - -## Gain and Pan - -```forth -"kick" s 0.8 gain . ( volume 0-1 ) -"hat" s 0.5 pan . ( pan right ) -"hat" s -0.5 pan . ( pan left ) -"snare" s 1.2 postgain . ( post-processing gain ) -"lead" s 100 velocity . ( velocity/dynamics ) -``` - -## Stereo Width - -```forth -"pad" s 0 width . ( mono ) -"pad" s 1 width . ( normal stereo ) -"pad" s 2 width . ( extra wide ) -``` - -## Haas Effect - -Create spatial positioning with subtle delay between channels: - -```forth -"vocal" s 8 haas . ( 8ms delay for spatial effect ) -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `eqlo` | (f --) | Set low shelf gain (dB) | -| `eqmid` | (f --) | Set mid peak gain (dB) | -| `eqhi` | (f --) | Set high shelf gain (dB) | -| `tilt` | (f --) | Set tilt EQ (-1 dark, 1 bright) | -| `gain` | (f --) | Set volume (0-1) | -| `postgain` | (f --) | Set post gain | -| `velocity` | (f --) | Set velocity | -| `pan` | (f --) | Set pan (-1 to 1) | -| `width` | (f --) | Set stereo width | -| `haas` | (f --) | Set Haas delay (ms) | diff --git a/docs/filters.md b/docs/filters.md deleted file mode 100644 index fb31732..0000000 --- a/docs/filters.md +++ /dev/null @@ -1,2 +0,0 @@ -# Filters - diff --git a/docs/generators.md b/docs/generators.md deleted file mode 100644 index 59a468b..0000000 --- a/docs/generators.md +++ /dev/null @@ -1,65 +0,0 @@ -# Generators - -Create sequences of values on the stack. - -## Arithmetic Range - -The `..` operator pushes an arithmetic sequence: - -```forth -1 4 .. ( pushes 1 2 3 4 ) -60 67 .. ( pushes 60 61 62 63 64 65 66 67 ) -``` - -## Geometric Range - -The `geom..` operator creates a geometric sequence: - -```forth -1 2 4 geom.. ( pushes 1 2 4 8 ) -100 0.5 4 geom.. ( pushes 100 50 25 12.5 ) -``` - -Stack: `(start ratio count -- values...)` - -## Generate - -The `gen` word executes a quotation multiple times: - -```forth -{ 1 6 rand } 4 gen ( 4 random values from 1-6 ) -{ coin } 8 gen ( 8 random 0/1 values ) -``` - -## Examples - -Random melody: - -```forth -"pluck" s - { 48 72 rand } 4 gen 4 choose note -. -``` - -Chord from range: - -```forth -"pad" s - 60 67 .. 8 choose note ( random note in octave ) -. -``` - -Euclidean-style rhythm values: - -```forth -0 1 0 0 1 0 1 0 ( manual ) -{ coin } 8 gen ( random 8-step pattern ) -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `..` | (start end -- values...) | Arithmetic sequence | -| `geom..` | (start ratio count -- values...) | Geometric sequence | -| `gen` | (quot n -- results...) | Execute quotation n times | diff --git a/docs/ladder_filters.md b/docs/ladder_filters.md deleted file mode 100644 index 929fcd5..0000000 --- a/docs/ladder_filters.md +++ /dev/null @@ -1,47 +0,0 @@ -# Ladder Filters - -Ladder filters provide a classic analog-style filter sound with stronger resonance character than the standard SVF filters. - -## Ladder Lowpass - -```forth -"saw" s 2000 llpf . ( ladder lowpass frequency ) -"saw" s 0.7 llpq . ( ladder lowpass resonance ) -``` - -## Ladder Highpass - -```forth -"noise" s 500 lhpf . ( ladder highpass frequency ) -"noise" s 0.5 lhpq . ( ladder highpass resonance ) -``` - -## Ladder Bandpass - -```forth -"pad" s 1000 lbpf . ( ladder bandpass frequency ) -"pad" s 0.6 lbpq . ( ladder bandpass resonance ) -``` - -## Comparison with SVF - -Ladder filters have a different resonance character: -- More aggressive self-oscillation at high resonance -- Classic "squelchy" acid sound -- 24dB/octave slope - -Standard SVF filters (`lpf`, `hpf`, `bpf`) have: -- Cleaner resonance -- Full envelope control (attack, decay, sustain, release) -- 12dB/octave slope - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `llpf` | (f --) | Set ladder lowpass frequency | -| `llpq` | (f --) | Set ladder lowpass resonance | -| `lhpf` | (f --) | Set ladder highpass frequency | -| `lhpq` | (f --) | Set ladder highpass resonance | -| `lbpf` | (f --) | Set ladder bandpass frequency | -| `lbpq` | (f --) | Set ladder bandpass resonance | diff --git a/docs/lfo.md b/docs/lfo.md deleted file mode 100644 index 7246c0f..0000000 --- a/docs/lfo.md +++ /dev/null @@ -1,74 +0,0 @@ -# LFO & Ramps - -Generate time-varying values synchronized to the beat for modulation. - -## Ramp - -The `ramp` word creates a sawtooth wave from 0 to 1: - -```forth -0.25 1.0 ramp ( one cycle per 4 beats, linear ) -1.0 2.0 ramp ( one cycle per beat, exponential curve ) -``` - -Stack: `(freq curve -- val)` - -## Shortcut Ramps - -```forth -0.5 linramp ( linear ramp, curve=1 ) -0.25 expramp ( exponential ramp, curve=3 ) -2.0 logramp ( logarithmic ramp, curve=0.3 ) -``` - -## Triangle Wave - -```forth -0.5 tri ( triangle wave 0→1→0 ) -``` - -## Perlin Noise - -Smooth random modulation: - -```forth -0.25 perlin ( smooth noise at 0.25x beat rate ) -``` - -## Range Scaling - -Scale a 0-1 value to any range: - -```forth -0.5 200 800 range ( 0.5 becomes 500 ) -``` - -## Examples - -Modulate filter cutoff: - -```forth -"saw" s - 0.25 1.0 ramp 500 2000 range lpf -. -``` - -Random panning: - -```forth -"hat" s - 0.5 perlin -1 1 range pan -. -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `ramp` | (freq curve -- val) | Ramp 0-1: fract(freq*beat)^curve | -| `linramp` | (freq -- val) | Linear ramp (curve=1) | -| `expramp` | (freq -- val) | Exponential ramp (curve=3) | -| `logramp` | (freq -- val) | Logarithmic ramp (curve=0.3) | -| `tri` | (freq -- val) | Triangle wave 0→1→0 | -| `perlin` | (freq -- val) | Perlin noise 0-1 | -| `range` | (val min max -- scaled) | Scale 0-1 to min-max | diff --git a/docs/link.md b/docs/link.md deleted file mode 100644 index 5b7068b..0000000 --- a/docs/link.md +++ /dev/null @@ -1,2 +0,0 @@ -# Ableton Link - diff --git a/docs/lofi.md b/docs/lofi.md deleted file mode 100644 index 1d1b20b..0000000 --- a/docs/lofi.md +++ /dev/null @@ -1,58 +0,0 @@ -# Lo-fi Effects - -Add grit, warmth, and character with bit crushing, folding, and distortion. - -## Bit Crush - -Reduce bit depth for digital artifacts: - -```forth -"drum" s 8 crush . ( 8-bit crush ) -"drum" s 4 crush . ( heavy 4-bit crush ) -``` - -## Wave Folding - -Fold the waveform back on itself for complex harmonics: - -```forth -"sine" s 2 fold . ( moderate folding ) -"sine" s 4 fold . ( aggressive folding ) -``` - -## Wave Wrap - -Wrap the waveform for a different flavor of distortion: - -```forth -"bass" s 0.5 wrap . -``` - -## Distortion - -Classic overdrive/distortion: - -```forth -"guitar" s 0.5 distort . ( distortion amount ) -"guitar" s 0.8 distortvol . ( output volume ) -``` - -## Combining Effects - -```forth -"drum" s - 12 crush - 1.5 fold - 0.3 distort -. -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `crush` | (f --) | Set bit crush depth | -| `fold` | (f --) | Set wave fold amount | -| `wrap` | (f --) | Set wave wrap amount | -| `distort` | (f --) | Set distortion amount | -| `distortvol` | (f --) | Set distortion output volume | diff --git a/docs/logic.md b/docs/logic.md deleted file mode 100644 index 8503abb..0000000 --- a/docs/logic.md +++ /dev/null @@ -1,89 +0,0 @@ -# Logic - -Boolean operations and conditional execution. - -## Boolean Operators - -```forth -1 1 and ( 1 ) -0 1 or ( 1 ) -1 not ( 0 ) -1 0 xor ( 1 ) -1 1 nand ( 0 ) -0 0 nor ( 1 ) -``` - -## Conditional Execution - -Execute a quotation if condition is true: - -```forth -{ "kick" s . } coin ? ( 50% chance ) -{ 0.5 gain } step 0 = ? ( only on step 0 ) -``` - -Execute if false: - -```forth -{ "snare" s . } coin !? ( if NOT coin ) -``` - -## If-Else - -```forth -{ "kick" s } { "snare" s } coin ifelse . -``` - -Stack: `(true-quot false-quot bool --)` - -## Pick - -Choose from multiple quotations: - -```forth -{ 60 } { 64 } { 67 } step 3 mod pick note -``` - -Stack: `(quot1 quot2 ... quotN n -- result)` - -## Apply - -Execute a quotation unconditionally: - -```forth -{ 2 * } apply ( doubles top of stack ) -``` - -## Examples - -Conditional parameter: - -```forth -"kick" s - { 0.8 } { 0.4 } iter 2 mod = ifelse gain -. -``` - -Multi-way branching: - -```forth -"synth" s - { c4 } { e4 } { g4 } { c5 } step 4 mod pick note -. -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `and` | (a b -- bool) | Logical and | -| `or` | (a b -- bool) | Logical or | -| `not` | (a -- bool) | Logical not | -| `xor` | (a b -- bool) | Exclusive or | -| `nand` | (a b -- bool) | Not and | -| `nor` | (a b -- bool) | Not or | -| `?` | (quot bool --) | Execute if true | -| `!?` | (quot bool --) | Execute if false | -| `ifelse` | (t-quot f-quot bool --) | If-else | -| `pick` | (..quots n --) | Execute nth quotation | -| `apply` | (quot --) | Execute unconditionally | diff --git a/docs/mod_fx.md b/docs/mod_fx.md deleted file mode 100644 index 2135736..0000000 --- a/docs/mod_fx.md +++ /dev/null @@ -1,61 +0,0 @@ -# Modulation Effects - -Phaser, flanger, and chorus effects for movement and width. - -## Phaser - -```forth -"pad" s - 1 phaser ( rate in Hz ) - 0.5 phaserdepth ( depth ) - 0.5 phasersweep ( sweep range ) - 1000 phasercenter ( center frequency ) -. -``` - -## Flanger - -```forth -"guitar" s - 0.5 flanger ( rate ) - 0.5 flangerdepth ( depth ) - 0.5 flangerfeedback ( feedback for metallic sound ) -. -``` - -## Chorus - -```forth -"keys" s - 1 chorus ( rate ) - 0.5 chorusdepth ( depth ) - 0.02 chorusdelay ( base delay time ) -. -``` - -## Combining Effects - -```forth -"pad" s - c4 note - 0.3 phaserdepth - 0.5 phaser - 0.2 chorus - 0.3 chorusdepth -. -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `phaser` | (f --) | Set phaser rate | -| `phaserdepth` | (f --) | Set phaser depth | -| `phasersweep` | (f --) | Set phaser sweep | -| `phasercenter` | (f --) | Set phaser center frequency | -| `flanger` | (f --) | Set flanger rate | -| `flangerdepth` | (f --) | Set flanger depth | -| `flangerfeedback` | (f --) | Set flanger feedback | -| `chorus` | (f --) | Set chorus rate | -| `chorusdepth` | (f --) | Set chorus depth | -| `chorusdelay` | (f --) | Set chorus delay | diff --git a/docs/modulation.md b/docs/modulation.md deleted file mode 100644 index 32a53b2..0000000 --- a/docs/modulation.md +++ /dev/null @@ -1,2 +0,0 @@ -# Modulation - diff --git a/docs/notes.md b/docs/notes.md deleted file mode 100644 index d33d76d..0000000 --- a/docs/notes.md +++ /dev/null @@ -1,2 +0,0 @@ -# Notes & MIDI - diff --git a/docs/oddities.md b/docs/oddities.md new file mode 100644 index 0000000..b313482 --- /dev/null +++ b/docs/oddities.md @@ -0,0 +1,214 @@ +# Oddities + +Cagire's Forth is not a classic Forth. It borrows the core ideas (stack-based evaluation, postfix notation, word definitions) but adds modern features and domain-specific extensions. If you know traditional Forth, here are the differences. + +## Comments + +Classic Forth uses parentheses for comments: + +```forth +( this is a comment ) +``` + +Cagire uses double semicolons: + +```forth +;; this is a comment +``` + +Everything after `;;` until the end of the line is ignored. + +## Quotations + +Classic Forth has no quotations. Code is not a value you can pass around. + +Cagire has first-class quotations using curly braces: + +```forth +{ dup + } +``` + +This pushes a block of code onto the stack. You can store it, pass it to other words, and execute it later. Quotations enable conditionals, probability, and cycling. + +## Conditionals + +Classic Forth uses `IF ... ELSE ... THEN`: + +```forth +x 0 > IF 1 ELSE -1 THEN +``` + +Cagire supports this syntax but also provides quotation-based conditionals: + +```forth +{ 1 } { -1 } x 0 > ifelse +``` + +The words `?` and `!?` execute a quotation based on a condition: + +```forth +{ "kick" s . } coin ? ;; execute if coin is 1 +{ "snare" s . } coin !? ;; execute if coin is 0 +``` + +## Strings + +Classic Forth has limited string support. You print strings with `."`: + +```forth +." Hello World" +``` + +Cagire has first-class strings: + +```forth +"hello" +``` + +This pushes a string value onto the stack. Strings are used for sound names, sample names, and variable keys. You often do not need quotes at all. Any unrecognized word becomes a string automatically: + +```forth +kick s . ;; "kick" is not a word, so it becomes the string "kick" +myweirdname ;; pushes "myweirdname" onto the stack +``` + +This makes scripts cleaner. You only need quotes when the string contains spaces or conflicts with a real word. + +## Variables + +Classic Forth declares variables explicitly: + +```forth +VARIABLE x +10 x ! +x @ +``` + +Cagire uses prefix syntax: + +```forth +10 !x ;; store 10 in x +@x ;; fetch x (returns 0 if undefined) +``` + +No declaration needed. Variables spring into existence when you store to them. + +## Floating Point + +Classic Forth (in its original form) has no floating point. Numbers are integers. Floating point was added later as an optional extension with separate words. Cagire has native floating point: + +```forth +3.14159 +0.5 0.3 + ;; 0.8 +``` + +Integers and floats mix freely. Division always produces a float. + +## Loops + +Classic Forth has `DO ... LOOP`: + +```forth +10 0 DO I . LOOP +``` + +Cagire uses a quotation-based loop with `times`: + +```forth +4 { @i . } times ;; prints 0 1 2 3 +``` + +The loop counter is stored in the variable `i`, accessed with `@i`. This fits Cagire's style where control flow uses quotations. + +```forth +4 { @i 4 / at hat s . } times ;; hat at 0, 0.25, 0.5, 0.75 +4 { c4 @i + note sine s . } times ;; ascending notes +``` + +For generating sequences without side effects, use `..` or `gen`: + +```forth +1 5 .. ;; pushes 1 2 3 4 5 +{ dup * } 4 gen ;; pushes 0 1 4 9 (squares) +``` + +## The Command Register + +This is completely unique to Cagire. Traditional Forth programs print text. Cagire programs build sound commands. + +The command register accumulates a sound name and parameters: + +```forth +"sine" sound ;; set sound +440 freq ;; add parameter +0.5 gain ;; add parameter +. ;; emit and clear +``` + +Nothing is sent to the audio engine until you emit with `.`. This is unlike any classic Forth. + +## Context Words + +Cagire provides words that read the current sequencer state: + +```forth +step ;; current step index (0-127) +beat ;; current beat position +iter ;; pattern iteration count +tempo ;; current BPM +phase ;; position in bar (0-1) +``` + +These have no equivalent in classic Forth. They connect your script to the sequencer's timeline. + +## Probability + +Classic Forth is deterministic. Cagire has built-in randomness: + +```forth +{ "snare" s . } 50 prob ;; 50% chance +{ "clap" s . } 0.25 chance ;; 25% chance +{ "hat" s . } often ;; 75% chance +{ "rim" s . } sometimes ;; 50% chance +{ "tom" s . } rarely ;; 25% chance +``` + +These words take a quotation and execute it probabilistically. + +## Cycling + +Cagire has built-in support for cycling through values. Push values onto the stack, then select one based on pattern state: + +```forth +60 64 67 3 cycle note +``` + +Each time the step runs, a different note is selected. The `3` tells `cycle` how many values to pick from. + +You can also use quotations if you need to execute code: + +```forth +{ c4 note } { e4 note } { g4 note } 3 cycle +``` + +When the selected value is a quotation, it gets executed. When it is a plain value, it gets pushed onto the stack. + +Three cycling words exist: + +- `cycle` - selects based on `runs` (how many times this step has played) +- `pcycle` - selects based on `iter` (how many times the pattern has looped) +- `tcycle` - creates a cycle list that resolves at emit time + +The difference between `cycle` and `pcycle` matters when patterns have different lengths. `cycle` counts per-step, `pcycle` counts per-pattern. + +`tcycle` is special. It does not select immediately. Instead it creates a value that cycles when emitted: + +```forth +0.3 0.5 0.7 3 tcycle gain +``` + +If you emit multiple times in one step (using `at`), each emit gets the next value from the cycle. + +## Summary + +Cagire's Forth is a domain-specific language for music. It keeps Forth's elegance (stack, postfix, definitions) but adapts it for live coding. diff --git a/docs/oscillators.md b/docs/oscillators.md deleted file mode 100644 index c047d9e..0000000 --- a/docs/oscillators.md +++ /dev/null @@ -1,75 +0,0 @@ -# Oscillators - -Control synthesis oscillator parameters for synth sounds. - -## Frequency and Pitch - -```forth -"sine" s 440 freq . ( set frequency in Hz ) -"saw" s 60 note . ( set MIDI note ) -"square" s 0.01 detune . ( slight detune ) -"lead" s 12 coarse . ( coarse tune in semitones ) -"bass" s 0.1 glide . ( portamento ) -``` - -## Pulse Width - -For pulse/square oscillators: - -```forth -"pulse" s 0.3 pw . ( narrow pulse ) -"pulse" s 0.5 pw . ( square wave ) -``` - -## Stereo Spread - -```forth -"pad" s 0.5 spread . ( stereo spread amount ) -``` - -## Harmonics and Timbre - -For oscillators that support it (like mutable): - -```forth -"mutable" s 4 harmonics . ( harmonic content ) -"mutable" s 0.5 timbre . ( timbre control ) -"mutable" s 0.3 morph . ( morph parameter ) -``` - -## Multiplier and Warp - -```forth -"fm" s 2 mult . ( frequency multiplier ) -"wt" s 0.5 warp . ( warp amount ) -"wt" s 1 mirror . ( mirror waveform ) -``` - -## Sub Oscillator - -```forth -"bass" s 0.5 sub . ( sub oscillator level ) -"bass" s 2 suboct . ( sub octave down ) -"bass" s 1 subwave . ( sub waveform ) -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `freq` | (f --) | Set frequency (Hz) | -| `note` | (n --) | Set MIDI note | -| `detune` | (f --) | Set detune amount | -| `coarse` | (f --) | Set coarse tune | -| `glide` | (f --) | Set glide/portamento | -| `pw` | (f --) | Set pulse width | -| `spread` | (f --) | Set stereo spread | -| `mult` | (f --) | Set multiplier | -| `warp` | (f --) | Set warp amount | -| `mirror` | (f --) | Set mirror | -| `harmonics` | (f --) | Set harmonics | -| `timbre` | (f --) | Set timbre | -| `morph` | (f --) | Set morph | -| `sub` | (f --) | Set sub oscillator level | -| `suboct` | (n --) | Set sub octave | -| `subwave` | (n --) | Set sub waveform | diff --git a/docs/patterns.md b/docs/patterns.md deleted file mode 100644 index dbd9700..0000000 --- a/docs/patterns.md +++ /dev/null @@ -1,2 +0,0 @@ -# Pattern Management - diff --git a/docs/pitch_envelope.md b/docs/pitch_envelope.md deleted file mode 100644 index 88869f9..0000000 --- a/docs/pitch_envelope.md +++ /dev/null @@ -1,44 +0,0 @@ -# Pitch Envelope - -Control pitch modulation over time with a dedicated ADSR envelope. - -## Envelope Amount - -```forth -"bass" s 0.5 penv . ( pitch envelope depth ) -"bass" s -0.5 penv . ( negative envelope ) -``` - -## Envelope Shape - -```forth -"kick" s - 1.0 penv ( full envelope depth ) - 0.001 patt ( instant attack ) - 0.1 pdec ( fast decay ) - 0 psus ( no sustain ) - 0.1 prel ( short release ) -. -``` - -## Classic Kick Drum - -```forth -"sine" s - 40 note - 2.0 penv - 0.001 patt - 0.05 pdec - 0 psus -. -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `penv` | (f --) | Set pitch envelope amount | -| `patt` | (f --) | Set pitch attack time | -| `pdec` | (f --) | Set pitch decay time | -| `psus` | (f --) | Set pitch sustain level | -| `prel` | (f --) | Set pitch release time | diff --git a/docs/probability.md b/docs/probability.md deleted file mode 100644 index 67240ef..0000000 --- a/docs/probability.md +++ /dev/null @@ -1,2 +0,0 @@ -# Probability - diff --git a/docs/randomness.md b/docs/randomness.md deleted file mode 100644 index 7152f45..0000000 --- a/docs/randomness.md +++ /dev/null @@ -1,2 +0,0 @@ -# Randomness - diff --git a/docs/samples.md b/docs/samples.md deleted file mode 100644 index 8201191..0000000 --- a/docs/samples.md +++ /dev/null @@ -1,76 +0,0 @@ -# Samples - -Control sample playback with timing, looping, and slice parameters. - -## Basic Playback - -```forth -"break" s . ( play sample from start ) -"break" s 0.5 speed . ( half speed ) -"break" s -1 speed . ( reverse ) -``` - -## Time and Position - -Control where in the sample to start and end: - -```forth -"break" s 0.25 begin . ( start at 25% ) -"break" s 0.5 end . ( end at 50% ) -"break" s 0.1 time . ( offset start time ) -``` - -## Duration and Gate - -```forth -"pad" s 0.5 dur . ( play for 0.5 seconds ) -"pad" s 0.8 gate . ( gate time as fraction ) -``` - -## Looping - -Fit a sample to a number of beats: - -```forth -"break" s 4 loop . ( fit to 4 beats ) -``` - -## Repetition - -```forth -"hat" s 4 repeat . ( trigger 4 times ) -``` - -## Voice and Routing - -```forth -"kick" s 1 voice . ( assign to voice 1 ) -"snare" s 0 orbit . ( route to bus 0 ) -``` - -## Sample Selection - -```forth -"kick" s 2 n . ( select sample #2 from folder ) -"kit" s "a" bank . ( use bank suffix ) -1 cut ( cut group - stops other sounds in same group ) -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `time` | (f --) | Set time offset | -| `repeat` | (n --) | Set repeat count | -| `dur` | (f --) | Set duration | -| `gate` | (f --) | Set gate time | -| `speed` | (f --) | Set playback speed | -| `begin` | (f --) | Set sample start (0-1) | -| `end` | (f --) | Set sample end (0-1) | -| `loop` | (n --) | Fit sample to n beats | -| `voice` | (n --) | Set voice number | -| `orbit` | (n --) | Set orbit/bus | -| `n` | (n --) | Set sample number | -| `bank` | (str --) | Set sample bank suffix | -| `cut` | (n --) | Set cut group | -| `reset` | (n --) | Reset parameter | diff --git a/docs/scales.md b/docs/scales.md deleted file mode 100644 index 8af8485..0000000 --- a/docs/scales.md +++ /dev/null @@ -1,2 +0,0 @@ -# Scales - diff --git a/docs/selection.md b/docs/selection.md deleted file mode 100644 index 78daf9c..0000000 --- a/docs/selection.md +++ /dev/null @@ -1,62 +0,0 @@ -# Selection - -Cycle through values over time for evolving patterns. - -## Step Cycle - -`cycle` cycles through values based on step runs: - -```forth -60 64 67 3 cycle note ( cycle through C, E, G ) -``` - -Each time the step runs, it picks the next value. - -## Pattern Cycle - -`pcycle` cycles based on pattern iteration: - -```forth -60 64 67 3 pcycle note ( change note each pattern loop ) -``` - -## Emit-Time Cycle - -`tcycle` creates a cycle list resolved at emit time, useful with `.!`: - -```forth -60 64 67 3 tcycle note 3 .! ( emit C, E, G in sequence ) -``` - -## Examples - -Rotating bass notes: - -```forth -"bass" s - c3 e3 g3 b3 4 cycle note -. -``` - -Evolving pattern over loops: - -```forth -"lead" s - 0.5 1.0 0.75 0.25 4 pcycle gain -. -``` - -Arpeggiated chord: - -```forth -"pluck" s - c4 e4 g4 c5 4 tcycle note 4 .! -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `cycle` | (v1..vn n -- val) | Cycle by step runs | -| `pcycle` | (v1..vn n -- val) | Cycle by pattern iteration | -| `tcycle` | (v1..vn n -- list) | Create cycle list for emit-time | diff --git a/docs/stack.md b/docs/stack.md index 0c52852..29f17c1 100644 --- a/docs/stack.md +++ b/docs/stack.md @@ -1,118 +1,95 @@ # The Stack -Forth is a stack-based language. Instead of variables and expressions, you push values onto a stack and use words that consume and produce values. +The stack is the heart of Forth. Every value you type goes onto the stack. Every word you call takes values from the stack and puts results back. There are no variables in the traditional sense, just this pile of values that grows and shrinks as your program runs. -## How It Works +## Pushing Values -The stack is a last-in, first-out (LIFO) structure. Values you type get pushed on top. Words pop values off and push results back. +When you type a number or a string, it goes on top of the stack: -``` -3 4 + -``` +| Input | Stack (top on right) | +|-------|---------------------| +| `3` | `3` | +| `4` | `3 4` | +| `5` | `3 4 5` | -Step by step: -1. `3` → push 3 onto stack: `[3]` -2. `4` → push 4 onto stack: `[3, 4]` -3. `+` → pop two values, add them, push result: `[7]` +The stack grows to the right. The rightmost value is the top. -## Values +## Words Consume and Produce -Three types can live on the stack: +Words take values from the top and push results back. The `+` word pops two numbers and pushes their sum: -- **Integers**: `42`, `-7`, `0` -- **Floats**: `3.14`, `0.5`, `-1.0` -- **Strings**: `"kick"`, `"hello"` +| Input | Stack | +|-------|-------| +| `3` | `3` | +| `4` | `3 4` | +| `+` | `7` | + +This is why Forth uses postfix notation: operands come first, then the operator. ## Stack Notation -Documentation uses stack effect notation: +Documentation describes what words do using stack effect notation: ``` ( before -- after ) ``` -For example, `+` has effect `( a b -- sum )` meaning it takes two values and leaves one. +The word `+` has the effect `( a b -- sum )`. It takes two values and leaves one. +The word `dup` has the effect `( a -- a a )`. It takes one value and leaves two. -## Core Words +## Thinking in Stack -### dup +The key to Forth is learning to visualize the stack as you write. Consider this program: -Duplicate the top value. +| Input | Stack | What happens | +|-------|-------|--------------| +| `3` | `3` | Push 3 | +| `4` | `3 4` | Push 4 | +| `+` | `7` | Add them | +| `2` | `7 2` | Push 2 | +| `*` | `14` | Multiply | -``` -3 dup ( 3 3 ) +This computes `(3 + 4) * 2`. The parentheses are implicit in the order of operations. You can use line breaks and white spaces to keep organized, and the editor will also show the stack at each step for you if you ask it nicely :) + +## Rearranging Values + +Sometimes you need values in a different order. Stack manipulation words like `dup`, `swap`, `drop`, and `over` let you shuffle things around. You will find them in the dictionary. Here is a common pattern. You want to use a value twice: + +| Input | Stack | +|-------|-------| +| `3` | `3` | +| `dup` | `3 3` | +| `+` | `6` | + +The word `dup` duplicates the top, so `3 dup +` doubles the number. + +Another pattern. You have two values but need them swapped: + +| Input | Stack | +|-------|-------| +| `3` | `3` | +| `4` | `3 4` | +| `swap` | `4 3` | +| `-` | `1` | + +Without `swap`, `3 4 -` would compute `3 - 4 = -1`. With `swap`, you get `4 - 3 = 1`. + +## Stack Errors + +Two things can go wrong with the stack: + +* **Stack underflow** happens when a word needs more values than the stack has. If you write `+` with only one number on the stack, there is nothing to add. The script stops with an error. + +```forth +3 + ;; error: stack underflow ``` -### drop +The fix is simple: make sure you push enough values before calling a word. Check the stack effect in the dictionary if you are unsure. -Discard the top value. +* **Stack overflow** is the opposite: too many values left on the stack. This is less critical but indicates sloppy code. If your script leaves unused values behind, you probably made a mistake somewhere. -``` -3 4 drop ( 3 ) +```forth +3 4 5 + . ;; plays a sound, but 3 is still on the stack ``` -### swap - -Swap the top two values. - -``` -3 4 swap ( 4 3 ) -``` - -### over - -Copy the second value to the top. - -``` -3 4 over ( 3 4 3 ) -``` - -### rot - -Rotate the top three values. - -``` -1 2 3 rot ( 2 3 1 ) -``` - -### nip - -Drop the second value. - -``` -3 4 nip ( 4 ) -``` - -### tuck - -Copy top value below second. - -``` -3 4 tuck ( 4 3 4 ) -``` - -## Examples - -Build a chord by duplicating and adding: - -``` -60 dup 4 + swap 7 + ( 64 67 60 ) -``` - -Use `over` to keep a base value: - -``` -c4 over M3 swap P5 ( e4 g4 c4 ) -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `dup` | (a -- a a) | Duplicate top | -| `drop` | (a --) | Discard top | -| `swap` | (a b -- b a) | Swap top two | -| `over` | (a b -- a b a) | Copy second to top | -| `rot` | (a b c -- b c a) | Rotate three | -| `nip` | (a b -- b) | Drop second | -| `tuck` | (a b -- b a b) | Copy top below second | +The `3` was never used. Either it should not be there, or you forgot a word that consumes it. diff --git a/docs/timing.md b/docs/timing.md deleted file mode 100644 index 31cae64..0000000 --- a/docs/timing.md +++ /dev/null @@ -1,2 +0,0 @@ -# Timing - diff --git a/docs/variables.md b/docs/variables.md deleted file mode 100644 index 5df2cd1..0000000 --- a/docs/variables.md +++ /dev/null @@ -1,2 +0,0 @@ -# Variables - diff --git a/docs/vibrato.md b/docs/vibrato.md deleted file mode 100644 index 53b12fa..0000000 --- a/docs/vibrato.md +++ /dev/null @@ -1,44 +0,0 @@ -# Vibrato - -Add pitch vibrato to oscillator sounds. - -## Basic Vibrato - -```forth -"lead" s - 60 note - 5 vib ( vibrato rate in Hz ) - 0.5 vibmod ( vibrato depth ) -. -``` - -## Vibrato Shape - -Control the LFO waveform: - -```forth -"pad" s - c4 note - 4 vib - 0.3 vibmod - 0 vibshape ( 0=sine, other values for different shapes ) -. -``` - -## Subtle vs Expressive - -```forth -( subtle vibrato for pads ) -"pad" s 3 vib 0.1 vibmod . - -( expressive vibrato for leads ) -"lead" s 6 vib 0.8 vibmod . -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `vib` | (f --) | Set vibrato rate (Hz) | -| `vibmod` | (f --) | Set vibrato depth | -| `vibshape` | (f --) | Set vibrato LFO shape | diff --git a/docs/wavetables.md b/docs/wavetables.md deleted file mode 100644 index a37c0af..0000000 --- a/docs/wavetables.md +++ /dev/null @@ -1,55 +0,0 @@ -# Wavetables - -Control wavetable synthesis parameters for scanning through waveforms. - -## Scan Position - -The `scan` parameter controls which waveform in the wavetable is active: - -```forth -"wt" s 0.0 scan . ( first waveform ) -"wt" s 0.5 scan . ( middle of table ) -"wt" s 1.0 scan . ( last waveform ) -``` - -## Wavetable Length - -Set the cycle length in samples: - -```forth -"wt" s 2048 wtlen . ( standard wavetable size ) -``` - -## Scan Modulation - -Animate the scan position with an LFO: - -```forth -"wt" s 0.2 scanlfo . ( LFO rate in Hz ) -"wt" s 0.4 scandepth . ( LFO depth 0-1 ) -"wt" s "tri" scanshape . ( LFO shape ) -``` - -Available shapes: `sine`, `tri`, `saw`, `square`, `sh` (sample & hold) - -## Example - -```forth -"wavetable" s - 60 note - 0.25 scan - 0.1 scanlfo - 0.3 scandepth - "sine" scanshape -. -``` - -## Words - -| Word | Stack | Description | -|------|-------|-------------| -| `scan` | (f --) | Set wavetable scan position (0-1) | -| `wtlen` | (n --) | Set wavetable cycle length in samples | -| `scanlfo` | (f --) | Set scan LFO rate (Hz) | -| `scandepth` | (f --) | Set scan LFO depth (0-1) | -| `scanshape` | (s --) | Set scan LFO shape | diff --git a/src/views/help_view.rs b/src/views/help_view.rs index 903f6fc..6c0c170 100644 --- a/src/views/help_view.rs +++ b/src/views/help_view.rs @@ -39,62 +39,12 @@ const DOCS: &[DocEntry] = &[ Topic("About Forth", include_str!("../../docs/about_forth.md")), Topic("The Dictionary", include_str!("../../docs/dictionary.md")), Topic("The Stack", include_str!("../../docs/stack.md")), - Topic("Arithmetic", include_str!("../../docs/arithmetic.md")), - Topic("Comparison", include_str!("../../docs/comparison.md")), - Topic("Logic", include_str!("../../docs/logic.md")), - // Sound generation - Section("Sounds"), - Topic("Emitting", include_str!("../../docs/emitting.md")), - Topic("Samples", include_str!("../../docs/samples.md")), - Topic("Oscillators", include_str!("../../docs/oscillators.md")), - Topic("Wavetables", include_str!("../../docs/wavetables.md")), - // Sound shaping - Section("Shaping"), - Topic("Envelopes", include_str!("../../docs/envelopes.md")), - Topic( - "Pitch Envelope", - include_str!("../../docs/pitch_envelope.md"), - ), - Topic("Filters", include_str!("../../docs/filters.md")), - Topic( - "Ladder Filters", - include_str!("../../docs/ladder_filters.md"), - ), - // Movement and modulation - Section("Movement"), - Topic("LFO & Ramps", include_str!("../../docs/lfo.md")), - Topic("Modulation", include_str!("../../docs/modulation.md")), - Topic("Vibrato", include_str!("../../docs/vibrato.md")), - // Effects - Section("Effects"), - Topic("Delay & Reverb", include_str!("../../docs/delay_reverb.md")), - Topic("Mod FX", include_str!("../../docs/mod_fx.md")), - Topic("EQ & Stereo", include_str!("../../docs/eq_stereo.md")), - Topic("Lo-fi", include_str!("../../docs/lofi.md")), - // Variation and randomness - Section("Variation"), - Topic("Randomness", include_str!("../../docs/randomness.md")), - Topic("Probability", include_str!("../../docs/probability.md")), - Topic("Selection", include_str!("../../docs/selection.md")), - // Timing - Section("Timing"), - Topic("Context", include_str!("../../docs/context.md")), - Topic("Cycles", include_str!("../../docs/cycles.md")), - Topic("Timing", include_str!("../../docs/timing.md")), - Topic("Patterns", include_str!("../../docs/patterns.md")), - Topic("Chaining", include_str!("../../docs/chaining.md")), - // Music theory - Section("Music"), - Topic("Notes", include_str!("../../docs/notes.md")), - Topic("Scales", include_str!("../../docs/scales.md")), - Topic("Chords", include_str!("../../docs/chords.md")), - Topic("Generators", include_str!("../../docs/generators.md")), - // Advanced - Section("Advanced"), - Topic("Variables", include_str!("../../docs/variables.md")), - Topic("Conditionals", include_str!("../../docs/conditionals.md")), - Topic("Custom Words", include_str!("../../docs/definitions.md")), - Topic("Ableton Link", include_str!("../../docs/link.md")), + Topic("Creating Words", include_str!("../../docs/definitions.md")), + Topic("Oddities", include_str!("../../docs/oddities.md")), + // Audio Engine + Section("Audio Engine"), + Topic("Introduction", include_str!("../../docs/engine_intro.md")), + Topic("Settings", include_str!("../../docs/engine_settings.md")), // Reference Section("Reference"), Topic("Audio Engine", include_str!("../../docs/audio_engine.md")),