From b75b9562af400e2d74ace9c52203275956371ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Wed, 4 Feb 2026 23:50:38 +0100 Subject: [PATCH] Feat: refactoring by breaking words in multiple files --- CHANGELOG.md | 2 + crates/forth/src/types.rs | 1 + crates/forth/src/vm.rs | 2 +- crates/forth/src/words.rs | 3078 -------------------------- crates/forth/src/words/compile.rs | 260 +++ crates/forth/src/words/core.rs | 532 +++++ crates/forth/src/words/effects.rs | 792 +++++++ crates/forth/src/words/midi.rs | 135 ++ crates/forth/src/words/mod.rs | 58 + crates/forth/src/words/music.rs | 312 +++ crates/forth/src/words/sequencing.rs | 413 ++++ crates/forth/src/words/sound.rs | 622 ++++++ tests/forth/temporal.rs | 11 +- 13 files changed, 3138 insertions(+), 3080 deletions(-) delete mode 100644 crates/forth/src/words.rs create mode 100644 crates/forth/src/words/compile.rs create mode 100644 crates/forth/src/words/core.rs create mode 100644 crates/forth/src/words/effects.rs create mode 100644 crates/forth/src/words/midi.rs create mode 100644 crates/forth/src/words/mod.rs create mode 100644 crates/forth/src/words/music.rs create mode 100644 crates/forth/src/words/sequencing.rs create mode 100644 crates/forth/src/words/sound.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf9795..3614bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. ### Changed +- Split `words.rs` (3,078 lines) into a `words/` directory module with category-based files: `core.rs`, `sound.rs`, `effects.rs`, `sequencing.rs`, `music.rs`, `midi.rs`, plus `compile.rs` and `mod.rs`. - Renamed `tri` Forth word to `triangle`. - Sequencer rewritten with prospective lookahead scheduling. Instead of sleeping until a substep, waking late, and detecting past events, the sequencer now pre-computes all events within a ~20ms forward window. Events arrive at doux with positive time deltas, scheduled before they need to fire. Sleep+spin-wait replaced by `recv_timeout(3ms)` on the command channel. Timing no longer depends on OS sleep precision. - `audio_sample_pos` updated at buffer start instead of end, so `engine_time` reflects current playback position. @@ -28,6 +29,7 @@ All notable changes to this project will be documented in this file. - Changing pattern properties is now a stage/commit operation. - Changing pattern speed only happens at pattern boundaries. - `mlockall` warning no longer appears on macOS; memory locking is now Linux-only. +- `clear` now resets `at` deltas, so subsequent emits default to a single emit at position 0. ## [0.0.5] - Unreleased diff --git a/crates/forth/src/types.rs b/crates/forth/src/types.rs index 9ae89b5..131beb7 100644 --- a/crates/forth/src/types.rs +++ b/crates/forth/src/types.rs @@ -186,5 +186,6 @@ impl CmdRegister { pub(super) fn clear(&mut self) { self.sound = None; self.params.clear(); + self.deltas.clear(); } } diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 57ae1c8..2e92ba3 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -1113,7 +1113,7 @@ fn emit_output( if !out.ends_with('/') { out.push('/'); } - let _ = write!(&mut out, "dur/{step_duration}"); + let _ = write!(&mut out, "dur/{}", step_duration * 4.0); } if sound.is_some() && delaytime_idx.is_none() { diff --git a/crates/forth/src/words.rs b/crates/forth/src/words.rs deleted file mode 100644 index 3739220..0000000 --- a/crates/forth/src/words.rs +++ /dev/null @@ -1,3078 +0,0 @@ -use std::collections::HashMap; -use std::sync::{Arc, LazyLock}; - -use super::ops::Op; -use super::theory; -use super::types::{Dictionary, SourceSpan}; - -pub enum WordCompile { - Simple, - Context(&'static str), - Param, - Probability(f64), -} - -pub struct Word { - pub name: &'static str, - pub aliases: &'static [&'static str], - pub category: &'static str, - pub stack: &'static str, - pub desc: &'static str, - pub example: &'static str, - pub compile: WordCompile, - pub varargs: bool, -} - -use WordCompile::*; - -pub const WORDS: &[Word] = &[ - // Stack manipulation - Word { - name: "dup", - aliases: &[], - category: "Stack", - stack: "(a -- a a)", - desc: "Duplicate top of stack", - example: "3 dup => 3 3", - compile: Simple, - varargs: false, - }, - Word { - name: "dupn", - aliases: &["!"], - category: "Stack", - stack: "(a n -- a a ... a)", - desc: "Duplicate a onto stack n times", - example: "2 4 dupn => 2 2 2 2", - compile: Simple, - varargs: true, - }, - Word { - name: "drop", - aliases: &[], - category: "Stack", - stack: "(a --)", - desc: "Remove top of stack", - example: "1 2 drop => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "swap", - aliases: &[], - category: "Stack", - stack: "(a b -- b a)", - desc: "Exchange top two items", - example: "1 2 swap => 2 1", - compile: Simple, - varargs: false, - }, - Word { - name: "over", - aliases: &[], - category: "Stack", - stack: "(a b -- a b a)", - desc: "Copy second to top", - example: "1 2 over => 1 2 1", - compile: Simple, - varargs: false, - }, - Word { - name: "rot", - aliases: &[], - category: "Stack", - stack: "(a b c -- b c a)", - desc: "Rotate top three", - example: "1 2 3 rot => 2 3 1", - compile: Simple, - varargs: false, - }, - Word { - name: "nip", - aliases: &[], - category: "Stack", - stack: "(a b -- b)", - desc: "Remove second item", - example: "1 2 nip => 2", - compile: Simple, - varargs: false, - }, - Word { - name: "tuck", - aliases: &[], - category: "Stack", - stack: "(a b -- b a b)", - desc: "Copy top under second", - example: "1 2 tuck => 2 1 2", - compile: Simple, - varargs: false, - }, - Word { - name: "2dup", - aliases: &[], - category: "Stack", - stack: "(a b -- a b a b)", - desc: "Duplicate top two values", - example: "1 2 2dup => 1 2 1 2", - compile: Simple, - varargs: false, - }, - Word { - name: "2drop", - aliases: &[], - category: "Stack", - stack: "(a b --)", - desc: "Drop top two values", - example: "1 2 3 2drop => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "2swap", - aliases: &[], - category: "Stack", - stack: "(a b c d -- c d a b)", - desc: "Swap top two pairs", - example: "1 2 3 4 2swap => 3 4 1 2", - compile: Simple, - varargs: false, - }, - Word { - name: "2over", - aliases: &[], - category: "Stack", - stack: "(a b c d -- a b c d a b)", - desc: "Copy second pair to top", - example: "1 2 3 4 2over => 1 2 3 4 1 2", - compile: Simple, - varargs: false, - }, - // Arithmetic - Word { - name: "+", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- a+b)", - desc: "Add", - example: "2 3 + => 5", - compile: Simple, - varargs: false, - }, - Word { - name: "-", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- a-b)", - desc: "Subtract", - example: "5 3 - => 2", - compile: Simple, - varargs: false, - }, - Word { - name: "*", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- a*b)", - desc: "Multiply", - example: "3 4 * => 12", - compile: Simple, - varargs: false, - }, - Word { - name: "/", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- a/b)", - desc: "Divide", - example: "10 2 / => 5", - compile: Simple, - varargs: false, - }, - Word { - name: "mod", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- a%b)", - desc: "Modulo", - example: "7 3 mod => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "neg", - aliases: &[], - category: "Arithmetic", - stack: "(a -- -a)", - desc: "Negate", - example: "5 neg => -5", - compile: Simple, - varargs: false, - }, - Word { - name: "abs", - aliases: &[], - category: "Arithmetic", - stack: "(a -- |a|)", - desc: "Absolute value", - example: "-5 abs => 5", - compile: Simple, - varargs: false, - }, - Word { - name: "floor", - aliases: &[], - category: "Arithmetic", - stack: "(f -- n)", - desc: "Round down to integer", - example: "3.7 floor => 3", - compile: Simple, - varargs: false, - }, - Word { - name: "ceil", - aliases: &[], - category: "Arithmetic", - stack: "(f -- n)", - desc: "Round up to integer", - example: "3.2 ceil => 4", - compile: Simple, - varargs: false, - }, - Word { - name: "round", - aliases: &[], - category: "Arithmetic", - stack: "(f -- n)", - desc: "Round to nearest integer", - example: "3.5 round => 4", - compile: Simple, - varargs: false, - }, - Word { - name: "min", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- min)", - desc: "Minimum of two values", - example: "3 5 min => 3", - compile: Simple, - varargs: false, - }, - Word { - name: "max", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- max)", - desc: "Maximum of two values", - example: "3 5 max => 5", - compile: Simple, - varargs: false, - }, - Word { - name: "pow", - aliases: &[], - category: "Arithmetic", - stack: "(a b -- a^b)", - desc: "Exponentiation", - example: "2 3 pow => 8", - compile: Simple, - varargs: false, - }, - Word { - name: "sqrt", - aliases: &[], - category: "Arithmetic", - stack: "(a -- √a)", - desc: "Square root", - example: "16 sqrt => 4", - compile: Simple, - varargs: false, - }, - Word { - name: "sin", - aliases: &[], - category: "Arithmetic", - stack: "(a -- sin(a))", - desc: "Sine (radians)", - example: "3.14159 2 / sin => 1.0", - compile: Simple, - varargs: false, - }, - Word { - name: "cos", - aliases: &[], - category: "Arithmetic", - stack: "(a -- cos(a))", - desc: "Cosine (radians)", - example: "0 cos => 1.0", - compile: Simple, - varargs: false, - }, - Word { - name: "log", - aliases: &[], - category: "Arithmetic", - stack: "(a -- ln(a))", - desc: "Natural logarithm", - example: "2.718 log => 1.0", - compile: Simple, - varargs: false, - }, - // Comparison - Word { - name: "=", - aliases: &[], - category: "Comparison", - stack: "(a b -- bool)", - desc: "Equal", - example: "3 3 = => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "!=", - aliases: &["<>"], - category: "Comparison", - stack: "(a b -- bool)", - desc: "Not equal", - example: "3 4 != => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "lt", - aliases: &[], - category: "Comparison", - stack: "(a b -- bool)", - desc: "Less than", - example: "2 3 lt => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "gt", - aliases: &[], - category: "Comparison", - stack: "(a b -- bool)", - desc: "Greater than", - example: "3 2 gt => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "<=", - aliases: &[], - category: "Comparison", - stack: "(a b -- bool)", - desc: "Less or equal", - example: "3 3 <= => 1", - compile: Simple, - varargs: false, - }, - Word { - name: ">=", - aliases: &[], - category: "Comparison", - stack: "(a b -- bool)", - desc: "Greater or equal", - example: "3 3 >= => 1", - compile: Simple, - varargs: false, - }, - // Logic - Word { - name: "and", - aliases: &[], - category: "Logic", - stack: "(a b -- bool)", - desc: "Logical and", - example: "1 1 and => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "or", - aliases: &[], - category: "Logic", - stack: "(a b -- bool)", - desc: "Logical or", - example: "0 1 or => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "not", - aliases: &[], - category: "Logic", - stack: "(a -- bool)", - desc: "Logical not", - example: "0 not => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "xor", - aliases: &[], - category: "Logic", - stack: "(a b -- bool)", - desc: "Exclusive or", - example: "1 0 xor => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "nand", - aliases: &[], - category: "Logic", - stack: "(a b -- bool)", - desc: "Not and", - example: "1 1 nand => 0", - compile: Simple, - varargs: false, - }, - Word { - name: "nor", - aliases: &[], - category: "Logic", - stack: "(a b -- bool)", - desc: "Not or", - example: "0 0 nor => 1", - compile: Simple, - varargs: false, - }, - Word { - name: "ifelse", - aliases: &[], - category: "Logic", - stack: "(true-quot false-quot bool --)", - desc: "Execute true-quot if true, else false-quot", - example: "{ 1 } { 2 } coin ifelse", - compile: Simple, - varargs: false, - }, - Word { - name: "pick", - aliases: &[], - category: "Logic", - stack: "(..quots n --)", - desc: "Execute nth quotation (0-indexed)", - example: "{ 1 } { 2 } { 3 } 2 pick => 3", - compile: Simple, - varargs: true, - }, - // Sound - Word { - name: "sound", - aliases: &["s"], - category: "Sound", - stack: "(name --)", - desc: "Begin sound command", - example: "\"kick\" sound", - compile: Simple, - varargs: false, - }, - Word { - name: ".", - aliases: &[], - category: "Sound", - stack: "(--)", - desc: "Emit current sound", - example: "\"kick\" s . . . .", - compile: Simple, - varargs: false, - }, - // Variables (prefix syntax: @name to fetch, !name to store) - Word { - name: "@", - aliases: &[], - category: "Variables", - stack: "( -- val)", - desc: "Fetch variable value", - example: "@freq => 440", - compile: Simple, - varargs: false, - }, - Word { - name: "!", - aliases: &[], - category: "Variables", - stack: "(val --)", - desc: "Store value in variable", - example: "440 !freq", - compile: Simple, - varargs: false, - }, - // Randomness - Word { - name: "rand", - aliases: &[], - category: "Probability", - stack: "(min max -- n|f)", - desc: "Random in range. Int if both args are int, float otherwise", - example: "1 6 rand => 4 | 0.0 1.0 rand => 0.42", - compile: Simple, - varargs: false, - }, - Word { - name: "exprand", - aliases: &[], - category: "Probability", - stack: "(lo hi -- f)", - desc: "Exponential random biased toward lo. Both args must be positive", - example: "1.0 100.0 exprand => 3.7", - compile: Simple, - varargs: false, - }, - Word { - name: "logrand", - aliases: &[], - category: "Probability", - stack: "(lo hi -- f)", - desc: "Exponential random biased toward hi. Both args must be positive", - example: "1.0 100.0 logrand => 87.2", - compile: Simple, - varargs: false, - }, - Word { - name: "seed", - aliases: &[], - category: "Probability", - stack: "(n --)", - desc: "Set random seed", - example: "12345 seed", - compile: Simple, - varargs: false, - }, - Word { - name: "coin", - aliases: &[], - category: "Probability", - stack: "(-- bool)", - desc: "50/50 random boolean", - example: "coin => 0 or 1", - compile: Simple, - varargs: false, - }, - Word { - name: "chance", - aliases: &[], - category: "Probability", - stack: "(quot prob --)", - desc: "Execute quotation with probability (0.0-1.0)", - example: "{ 2 distort } 0.75 chance", - compile: Simple, - varargs: false, - }, - Word { - name: "prob", - aliases: &[], - category: "Probability", - stack: "(quot pct --)", - desc: "Execute quotation with probability (0-100)", - example: "{ 2 distort } 75 prob", - compile: Simple, - varargs: false, - }, - Word { - name: "choose", - aliases: &[], - category: "Probability", - stack: "(..n n -- val)", - desc: "Random pick from n items", - example: "1 2 3 3 choose", - compile: Simple, - varargs: true, - }, - Word { - name: "cycle", - aliases: &[], - category: "Probability", - stack: "(v1..vn n -- selected)", - desc: "Cycle through n items by step runs", - example: "60 64 67 3 cycle", - compile: Simple, - varargs: true, - }, - Word { - name: "pcycle", - aliases: &[], - category: "Probability", - stack: "(v1..vn n -- selected)", - desc: "Cycle through n items by pattern iteration", - example: "60 64 67 3 pcycle", - compile: Simple, - varargs: true, - }, - Word { - name: "every", - aliases: &[], - category: "Time", - stack: "(n -- bool)", - desc: "True every nth iteration", - example: "4 every", - compile: Simple, - varargs: false, - }, - // Probability shortcuts - Word { - name: "always", - aliases: &[], - category: "Probability", - stack: "(quot --)", - desc: "Always execute quotation", - example: "{ 2 distort } always", - compile: Probability(1.0), - varargs: false, - }, - Word { - name: "never", - aliases: &[], - category: "Probability", - stack: "(quot --)", - desc: "Never execute quotation", - example: "{ 2 distort } never", - compile: Probability(0.0), - varargs: false, - }, - Word { - name: "often", - aliases: &[], - category: "Probability", - stack: "(quot --)", - desc: "Execute quotation 75% of the time", - example: "{ 2 distort } often", - compile: Probability(0.75), - varargs: false, - }, - Word { - name: "sometimes", - aliases: &[], - category: "Probability", - stack: "(quot --)", - desc: "Execute quotation 50% of the time", - example: "{ 2 distort } sometimes", - compile: Probability(0.5), - varargs: false, - }, - Word { - name: "rarely", - aliases: &[], - category: "Probability", - stack: "(quot --)", - desc: "Execute quotation 25% of the time", - example: "{ 2 distort } rarely", - compile: Probability(0.25), - varargs: false, - }, - Word { - name: "almostNever", - aliases: &[], - category: "Probability", - stack: "(quot --)", - desc: "Execute quotation 10% of the time", - example: "{ 2 distort } almostNever", - compile: Probability(0.1), - varargs: false, - }, - Word { - name: "almostAlways", - aliases: &[], - category: "Probability", - stack: "(quot --)", - desc: "Execute quotation 90% of the time", - example: "{ 2 distort } almostAlways", - compile: Probability(0.9), - varargs: false, - }, - // Context - Word { - name: "step", - aliases: &[], - category: "Context", - stack: "(-- n)", - desc: "Current step index", - example: "step => 0", - compile: Context("step"), - varargs: false, - }, - Word { - name: "beat", - aliases: &[], - category: "Context", - stack: "(-- f)", - desc: "Current beat position", - example: "beat => 4.5", - compile: Context("beat"), - varargs: false, - }, - Word { - name: "bank", - aliases: &[], - category: "Sample", - stack: "(str --)", - desc: "Set sample bank suffix", - example: "\"a\" bank", - compile: Param, - varargs: false, - }, - Word { - name: "pattern", - aliases: &[], - category: "Context", - stack: "(-- n)", - desc: "Current pattern index", - example: "pattern => 0", - compile: Context("pattern"), - varargs: false, - }, - Word { - name: "pbank", - aliases: &[], - category: "Context", - stack: "(-- n)", - desc: "Current pattern's bank index", - example: "pbank => 0", - compile: Context("bank"), - varargs: false, - }, - Word { - name: "tempo", - aliases: &[], - category: "Context", - stack: "(-- f)", - desc: "Current BPM", - example: "tempo => 120.0", - compile: Context("tempo"), - varargs: false, - }, - Word { - name: "phase", - aliases: &[], - category: "Context", - stack: "(-- f)", - desc: "Phase in bar (0-1)", - example: "phase => 0.25", - compile: Context("phase"), - varargs: false, - }, - Word { - name: "slot", - aliases: &[], - category: "Context", - stack: "(-- n)", - desc: "Current slot number", - example: "slot => 0", - compile: Context("slot"), - varargs: false, - }, - Word { - name: "runs", - aliases: &[], - category: "Context", - stack: "(-- n)", - desc: "Times this step ran", - example: "runs => 3", - compile: Context("runs"), - varargs: false, - }, - Word { - name: "iter", - aliases: &[], - category: "Context", - stack: "(-- n)", - desc: "Pattern iteration count", - example: "iter => 2", - compile: Context("iter"), - varargs: false, - }, - Word { - name: "stepdur", - aliases: &[], - category: "Context", - stack: "(-- f)", - desc: "Step duration in seconds", - example: "stepdur => 0.125", - compile: Context("stepdur"), - varargs: false, - }, - // Live keys - Word { - name: "fill", - aliases: &[], - category: "Context", - stack: "(-- bool)", - desc: "True when fill is on (f key)", - example: "\"snare\" s . fill ?", - compile: Context("fill"), - varargs: false, - }, - #[cfg(feature = "desktop")] - Word { - name: "mx", - aliases: &[], - category: "Desktop", - stack: "(-- x)", - desc: "Normalized mouse X position (0-1)", - example: "mx 440 880 range freq", - compile: Context("mx"), - varargs: false, - }, - #[cfg(feature = "desktop")] - Word { - name: "my", - aliases: &[], - category: "Desktop", - stack: "(-- y)", - desc: "Normalized mouse Y position (0-1)", - example: "my 0.1 0.9 range gain", - compile: Context("my"), - varargs: false, - }, - #[cfg(feature = "desktop")] - Word { - name: "mdown", - aliases: &[], - category: "Desktop", - stack: "(-- bool)", - desc: "1 when mouse button held, 0 otherwise", - example: "mdown { \"crash\" s . } ?", - compile: Context("mdown"), - varargs: false, - }, - // Music - Word { - name: "mtof", - aliases: &[], - category: "Music", - stack: "(midi -- hz)", - desc: "MIDI note to frequency", - example: "69 mtof => 440.0", - compile: Simple, - varargs: false, - }, - Word { - name: "ftom", - aliases: &[], - category: "Music", - stack: "(hz -- midi)", - desc: "Frequency to MIDI note", - example: "440 ftom => 69.0", - compile: Simple, - varargs: false, - }, - // Chords - Triads - Word { - name: "maj", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth)", - desc: "Major triad", - example: "c4 maj => 60 64 67", - compile: Simple, - varargs: true, - }, - Word { - name: "m", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth)", - desc: "Minor triad", - example: "c4 m => 60 63 67", - compile: Simple, - varargs: true, - }, - Word { - name: "dim", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth)", - desc: "Diminished triad", - example: "c4 dim => 60 63 66", - compile: Simple, - varargs: true, - }, - Word { - name: "aug", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth)", - desc: "Augmented triad", - example: "c4 aug => 60 64 68", - compile: Simple, - varargs: true, - }, - Word { - name: "sus2", - aliases: &[], - category: "Chord", - stack: "(root -- root second fifth)", - desc: "Suspended 2nd", - example: "c4 sus2 => 60 62 67", - compile: Simple, - varargs: true, - }, - Word { - name: "sus4", - aliases: &[], - category: "Chord", - stack: "(root -- root fourth fifth)", - desc: "Suspended 4th", - example: "c4 sus4 => 60 65 67", - compile: Simple, - varargs: true, - }, - // Chords - Seventh - Word { - name: "maj7", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh)", - desc: "Major 7th", - example: "c4 maj7 => 60 64 67 71", - compile: Simple, - varargs: true, - }, - Word { - name: "min7", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh)", - desc: "Minor 7th", - example: "c4 min7 => 60 63 67 70", - compile: Simple, - varargs: true, - }, - Word { - name: "dom7", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh)", - desc: "Dominant 7th", - example: "c4 dom7 => 60 64 67 70", - compile: Simple, - varargs: true, - }, - Word { - name: "dim7", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh)", - desc: "Diminished 7th", - example: "c4 dim7 => 60 63 66 69", - compile: Simple, - varargs: true, - }, - Word { - name: "m7b5", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh)", - desc: "Half-diminished (min7b5)", - example: "c4 m7b5 => 60 63 66 70", - compile: Simple, - varargs: true, - }, - Word { - name: "minmaj7", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh)", - desc: "Minor-major 7th", - example: "c4 minmaj7 => 60 63 67 71", - compile: Simple, - varargs: true, - }, - Word { - name: "aug7", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh)", - desc: "Augmented 7th", - example: "c4 aug7 => 60 64 68 70", - compile: Simple, - varargs: true, - }, - // Chords - Sixth - Word { - name: "maj6", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth sixth)", - desc: "Major 6th", - example: "c4 maj6 => 60 64 67 69", - compile: Simple, - varargs: true, - }, - Word { - name: "min6", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth sixth)", - desc: "Minor 6th", - example: "c4 min6 => 60 63 67 69", - compile: Simple, - varargs: true, - }, - // Chords - Extended - Word { - name: "dom9", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh ninth)", - desc: "Dominant 9th", - example: "c4 dom9 => 60 64 67 70 74", - compile: Simple, - varargs: true, - }, - Word { - name: "maj9", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh ninth)", - desc: "Major 9th", - example: "c4 maj9 => 60 64 67 71 74", - compile: Simple, - varargs: true, - }, - Word { - name: "min9", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh ninth)", - desc: "Minor 9th", - example: "c4 min9 => 60 63 67 70 74", - compile: Simple, - varargs: true, - }, - Word { - name: "dom11", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh ninth eleventh)", - desc: "Dominant 11th", - example: "c4 dom11 => 60 64 67 70 74 77", - compile: Simple, - varargs: true, - }, - Word { - name: "min11", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh ninth eleventh)", - desc: "Minor 11th", - example: "c4 min11 => 60 63 67 70 74 77", - compile: Simple, - varargs: true, - }, - Word { - name: "dom13", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh ninth thirteenth)", - desc: "Dominant 13th", - example: "c4 dom13 => 60 64 67 70 74 81", - compile: Simple, - varargs: true, - }, - // Chords - Add - Word { - name: "add9", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth ninth)", - desc: "Major add 9", - example: "c4 add9 => 60 64 67 74", - compile: Simple, - varargs: true, - }, - Word { - name: "add11", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth eleventh)", - desc: "Major add 11", - example: "c4 add11 => 60 64 67 77", - compile: Simple, - varargs: true, - }, - Word { - name: "madd9", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth ninth)", - desc: "Minor add 9", - example: "c4 madd9 => 60 63 67 74", - compile: Simple, - varargs: true, - }, - // Chords - Altered dominants - Word { - name: "dom7b9", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh flatninth)", - desc: "7th flat 9", - example: "c4 dom7b9 => 60 64 67 70 73", - compile: Simple, - varargs: true, - }, - Word { - name: "dom7s9", - aliases: &[], - category: "Chord", - stack: "(root -- root third fifth seventh sharpninth)", - desc: "7th sharp 9 (Hendrix chord)", - example: "c4 dom7s9 => 60 64 67 70 75", - compile: Simple, - varargs: true, - }, - Word { - name: "dom7b5", - aliases: &[], - category: "Chord", - stack: "(root -- root third flatfifth seventh)", - desc: "7th flat 5", - example: "c4 dom7b5 => 60 64 66 70", - compile: Simple, - varargs: true, - }, - Word { - name: "dom7s5", - aliases: &[], - category: "Chord", - stack: "(root -- root third sharpfifth seventh)", - desc: "7th sharp 5", - example: "c4 dom7s5 => 60 64 68 70", - compile: Simple, - varargs: true, - }, - // LFO - Word { - name: "ramp", - aliases: &[], - category: "LFO", - stack: "(freq curve -- val)", - desc: "Ramp [0,1]: fract(freq*beat)^curve", - example: "0.25 2.0 ramp", - compile: Simple, - varargs: false, - }, - Word { - name: "range", - aliases: &[], - category: "LFO", - stack: "(val min max -- scaled)", - desc: "Scale [0,1] to [min,max]", - example: "0.5 200 800 range => 500", - compile: Simple, - varargs: false, - }, - Word { - name: "linramp", - aliases: &[], - category: "LFO", - stack: "(freq -- val)", - desc: "Linear ramp (curve=1)", - example: "1.0 linramp", - compile: Simple, - varargs: false, - }, - Word { - name: "expramp", - aliases: &[], - category: "LFO", - stack: "(freq -- val)", - desc: "Exponential ramp (curve=3)", - example: "0.25 expramp", - compile: Simple, - varargs: false, - }, - Word { - name: "logramp", - aliases: &[], - category: "LFO", - stack: "(freq -- val)", - desc: "Logarithmic ramp (curve=0.3)", - example: "2.0 logramp", - compile: Simple, - varargs: false, - }, - Word { - name: "triangle", - aliases: &[], - category: "LFO", - stack: "(freq -- val)", - desc: "Triangle wave [0,1]: 0→1→0", - example: "0.5 triangle", - compile: Simple, - varargs: false, - }, - Word { - name: "perlin", - aliases: &[], - category: "LFO", - stack: "(freq -- val)", - desc: "Perlin noise [0,1] sampled at freq*beat", - example: "0.25 perlin", - compile: Simple, - varargs: false, - }, - Word { - name: "loop", - aliases: &[], - category: "Time", - stack: "(n --)", - desc: "Fit sample to n beats", - example: "\"break\" s 4 loop @", - compile: Simple, - varargs: false, - }, - Word { - name: "tempo!", - aliases: &[], - category: "Time", - stack: "(bpm --)", - desc: "Set global tempo", - example: "140 tempo!", - compile: Simple, - varargs: false, - }, - Word { - name: "speed!", - aliases: &[], - category: "Time", - stack: "(multiplier --)", - desc: "Set pattern speed multiplier", - example: "2.0 speed!", - compile: Simple, - varargs: false, - }, - Word { - name: "chain", - aliases: &[], - category: "Time", - stack: "(bank pattern --)", - desc: "Chain to bank/pattern (1-indexed) when current pattern ends", - example: "1 4 chain", - compile: Simple, - varargs: false, - }, - Word { - name: "at", - aliases: &[], - category: "Time", - stack: "(v1..vn --)", - desc: "Set delta context for emit timing", - example: "0 0.5 at kick s . => emits at 0 and 0.5 of step", - compile: Simple, - varargs: true, - }, - // Quotations - Word { - name: "?", - aliases: &[], - category: "Logic", - stack: "(quot bool --)", - desc: "Execute quotation if true", - example: "{ 2 distort } 0.5 chance ?", - compile: Simple, - varargs: false, - }, - Word { - name: "!?", - aliases: &[], - category: "Logic", - stack: "(quot bool --)", - desc: "Execute quotation if false", - example: "{ 1 distort } 0.5 chance !?", - compile: Simple, - varargs: false, - }, - // Sample playback - Word { - name: "time", - aliases: &[], - category: "Sample", - stack: "(f --)", - desc: "Set time offset", - example: "0.1 time", - compile: Param, - varargs: false, - }, - Word { - name: "repeat", - aliases: &[], - category: "Sample", - stack: "(n --)", - desc: "Set repeat count", - example: "4 repeat", - compile: Param, - varargs: false, - }, - Word { - name: "dur", - aliases: &[], - category: "Sample", - stack: "(f --)", - desc: "Set duration", - example: "0.5 dur", - compile: Param, - varargs: false, - }, - Word { - name: "gate", - aliases: &[], - category: "Sample", - stack: "(f --)", - desc: "Set gate time", - example: "0.8 gate", - compile: Param, - varargs: false, - }, - Word { - name: "freq", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set frequency (Hz)", - example: "440 freq", - compile: Param, - varargs: false, - }, - Word { - name: "detune", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set detune amount", - example: "0.01 detune", - compile: Param, - varargs: false, - }, - Word { - name: "speed", - aliases: &[], - category: "Sample", - stack: "(f --)", - desc: "Set playback speed", - example: "1.5 speed", - compile: Param, - varargs: false, - }, - Word { - name: "glide", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set glide/portamento", - example: "0.1 glide", - compile: Param, - varargs: false, - }, - Word { - name: "pw", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set pulse width", - example: "0.5 pw", - compile: Param, - varargs: false, - }, - Word { - name: "spread", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set stereo spread", - example: "0.5 spread", - compile: Param, - varargs: false, - }, - Word { - name: "mult", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set multiplier", - example: "2 mult", - compile: Param, - varargs: false, - }, - Word { - name: "warp", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set warp amount", - example: "0.5 warp", - compile: Param, - varargs: false, - }, - Word { - name: "mirror", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set mirror", - example: "1 mirror", - compile: Param, - varargs: false, - }, - Word { - name: "harmonics", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set harmonics (mutable only)", - example: "4 harmonics", - compile: Param, - varargs: false, - }, - Word { - name: "timbre", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set timbre (mutable only)", - example: "0.5 timbre", - compile: Param, - varargs: false, - }, - Word { - name: "morph", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set morph (mutable only)", - example: "0.5 morph", - compile: Param, - varargs: false, - }, - Word { - name: "scan", - aliases: &[], - category: "Wavetable", - stack: "(f --)", - desc: "Set wavetable scan position (0-1)", - example: "0.5 scan", - compile: Param, - varargs: false, - }, - Word { - name: "wtlen", - aliases: &[], - category: "Wavetable", - stack: "(n --)", - desc: "Set wavetable cycle length in samples", - example: "2048 wtlen", - compile: Param, - varargs: false, - }, - Word { - name: "scanlfo", - aliases: &[], - category: "Wavetable", - stack: "(f --)", - desc: "Set scan LFO rate (Hz)", - example: "0.2 scanlfo", - compile: Param, - varargs: false, - }, - Word { - name: "scandepth", - aliases: &[], - category: "Wavetable", - stack: "(f --)", - desc: "Set scan LFO depth (0-1)", - example: "0.4 scandepth", - compile: Param, - varargs: false, - }, - Word { - name: "scanshape", - aliases: &[], - category: "Wavetable", - stack: "(s --)", - desc: "Set scan LFO shape (sine/tri/saw/square/sh)", - example: "\"tri\" scanshape", - compile: Param, - varargs: false, - }, - Word { - name: "begin", - aliases: &[], - category: "Sample", - stack: "(f --)", - desc: "Set sample start (0-1)", - example: "0.25 begin", - compile: Param, - varargs: false, - }, - Word { - name: "end", - aliases: &[], - category: "Sample", - stack: "(f --)", - desc: "Set sample end (0-1)", - example: "0.75 end", - compile: Param, - varargs: false, - }, - Word { - name: "gain", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set volume (0-1)", - example: "0.8 gain", - compile: Param, - varargs: false, - }, - Word { - name: "postgain", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set post gain", - example: "1.2 postgain", - compile: Param, - varargs: false, - }, - Word { - name: "velocity", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set velocity", - example: "100 velocity", - compile: Param, - varargs: false, - }, - Word { - name: "pan", - aliases: &[], - category: "Stereo", - stack: "(f --)", - desc: "Set pan (-1 to 1)", - example: "0.5 pan", - compile: Param, - varargs: false, - }, - Word { - name: "attack", - aliases: &["att"], - category: "Envelope", - stack: "(f --)", - desc: "Set attack time", - example: "0.01 attack", - compile: Param, - varargs: false, - }, - Word { - name: "decay", - aliases: &["dec"], - category: "Envelope", - stack: "(f --)", - desc: "Set decay time", - example: "0.1 decay", - compile: Param, - varargs: false, - }, - Word { - name: "sustain", - aliases: &["sus"], - category: "Envelope", - stack: "(f --)", - desc: "Set sustain level", - example: "0.5 sustain", - compile: Param, - varargs: false, - }, - Word { - name: "release", - aliases: &["rel"], - category: "Envelope", - stack: "(f --)", - desc: "Set release time", - example: "0.3 release", - compile: Param, - varargs: false, - }, - Word { - name: "adsr", - aliases: &[], - category: "Envelope", - stack: "(a d s r --)", - desc: "Set attack, decay, sustain, release", - example: "0.01 0.1 0.5 0.3 adsr", - compile: Simple, - varargs: false, - }, - Word { - name: "ad", - aliases: &[], - category: "Envelope", - stack: "(a d --)", - desc: "Set attack, decay (sustain=0)", - example: "0.01 0.1 ad", - compile: Simple, - varargs: false, - }, - Word { - name: "lpf", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set lowpass frequency", - example: "2000 lpf", - compile: Param, - varargs: false, - }, - Word { - name: "lpq", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set lowpass resonance", - example: "0.5 lpq", - compile: Param, - varargs: false, - }, - Word { - name: "lpe", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set lowpass envelope", - example: "0.5 lpe", - compile: Param, - varargs: false, - }, - Word { - name: "lpa", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set lowpass attack", - example: "0.01 lpa", - compile: Param, - varargs: false, - }, - Word { - name: "lpd", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set lowpass decay", - example: "0.1 lpd", - compile: Param, - varargs: false, - }, - Word { - name: "lps", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set lowpass sustain", - example: "0.5 lps", - compile: Param, - varargs: false, - }, - Word { - name: "lpr", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set lowpass release", - example: "0.3 lpr", - compile: Param, - varargs: false, - }, - Word { - name: "hpf", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set highpass frequency", - example: "100 hpf", - compile: Param, - varargs: false, - }, - Word { - name: "hpq", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set highpass resonance", - example: "0.5 hpq", - compile: Param, - varargs: false, - }, - Word { - name: "hpe", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set highpass envelope", - example: "0.5 hpe", - compile: Param, - varargs: false, - }, - Word { - name: "hpa", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set highpass attack", - example: "0.01 hpa", - compile: Param, - varargs: false, - }, - Word { - name: "hpd", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set highpass decay", - example: "0.1 hpd", - compile: Param, - varargs: false, - }, - Word { - name: "hps", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set highpass sustain", - example: "0.5 hps", - compile: Param, - varargs: false, - }, - Word { - name: "hpr", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set highpass release", - example: "0.3 hpr", - compile: Param, - varargs: false, - }, - Word { - name: "bpf", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set bandpass frequency", - example: "1000 bpf", - compile: Param, - varargs: false, - }, - Word { - name: "bpq", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set bandpass resonance", - example: "0.5 bpq", - compile: Param, - varargs: false, - }, - Word { - name: "bpe", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set bandpass envelope", - example: "0.5 bpe", - compile: Param, - varargs: false, - }, - Word { - name: "bpa", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set bandpass attack", - example: "0.01 bpa", - compile: Param, - varargs: false, - }, - Word { - name: "bpd", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set bandpass decay", - example: "0.1 bpd", - compile: Param, - varargs: false, - }, - Word { - name: "bps", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set bandpass sustain", - example: "0.5 bps", - compile: Param, - varargs: false, - }, - Word { - name: "bpr", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set bandpass release", - example: "0.3 bpr", - compile: Param, - varargs: false, - }, - Word { - name: "llpf", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set ladder lowpass frequency", - example: "2000 llpf", - compile: Param, - varargs: false, - }, - Word { - name: "llpq", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set ladder lowpass resonance", - example: "0.5 llpq", - compile: Param, - varargs: false, - }, - Word { - name: "lhpf", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set ladder highpass frequency", - example: "100 lhpf", - compile: Param, - varargs: false, - }, - Word { - name: "lhpq", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set ladder highpass resonance", - example: "0.5 lhpq", - compile: Param, - varargs: false, - }, - Word { - name: "lbpf", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set ladder bandpass frequency", - example: "1000 lbpf", - compile: Param, - varargs: false, - }, - Word { - name: "lbpq", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set ladder bandpass resonance", - example: "0.5 lbpq", - compile: Param, - varargs: false, - }, - Word { - name: "ftype", - aliases: &[], - category: "Filter", - stack: "(n --)", - desc: "Set filter type", - example: "1 ftype", - compile: Param, - varargs: false, - }, - Word { - name: "penv", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set pitch envelope", - example: "0.5 penv", - compile: Param, - varargs: false, - }, - Word { - name: "patt", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set pitch attack", - example: "0.01 patt", - compile: Param, - varargs: false, - }, - Word { - name: "pdec", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set pitch decay", - example: "0.1 pdec", - compile: Param, - varargs: false, - }, - Word { - name: "psus", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set pitch sustain", - example: "0 psus", - compile: Param, - varargs: false, - }, - Word { - name: "prel", - aliases: &[], - category: "Envelope", - stack: "(f --)", - desc: "Set pitch release", - example: "0.1 prel", - compile: Param, - varargs: false, - }, - Word { - name: "vib", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set vibrato rate", - example: "5 vib", - compile: Param, - varargs: false, - }, - Word { - name: "vibmod", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set vibrato depth", - example: "0.5 vibmod", - compile: Param, - varargs: false, - }, - Word { - name: "vibshape", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set vibrato shape", - example: "0 vibshape", - compile: Param, - varargs: false, - }, - Word { - name: "fm", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM frequency", - example: "200 fm", - compile: Param, - varargs: false, - }, - Word { - name: "fmh", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM harmonic ratio", - example: "2 fmh", - compile: Param, - varargs: false, - }, - Word { - name: "fmshape", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM shape", - example: "0 fmshape", - compile: Param, - varargs: false, - }, - Word { - name: "fme", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM envelope", - example: "0.5 fme", - compile: Param, - varargs: false, - }, - Word { - name: "fma", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM attack", - example: "0.01 fma", - compile: Param, - varargs: false, - }, - Word { - name: "fmd", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM decay", - example: "0.1 fmd", - compile: Param, - varargs: false, - }, - Word { - name: "fms", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM sustain", - example: "0.5 fms", - compile: Param, - varargs: false, - }, - Word { - name: "fmr", - aliases: &[], - category: "FM", - stack: "(f --)", - desc: "Set FM release", - example: "0.1 fmr", - compile: Param, - varargs: false, - }, - Word { - name: "am", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set AM frequency", - example: "10 am", - compile: Param, - varargs: false, - }, - Word { - name: "amdepth", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set AM depth", - example: "0.5 amdepth", - compile: Param, - varargs: false, - }, - Word { - name: "amshape", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set AM shape", - example: "0 amshape", - compile: Param, - varargs: false, - }, - Word { - name: "rm", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set RM frequency", - example: "100 rm", - compile: Param, - varargs: false, - }, - Word { - name: "rmdepth", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set RM depth", - example: "0.5 rmdepth", - compile: Param, - varargs: false, - }, - Word { - name: "rmshape", - aliases: &[], - category: "Modulation", - stack: "(f --)", - desc: "Set RM shape", - example: "0 rmshape", - compile: Param, - varargs: false, - }, - Word { - name: "phaser", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set phaser rate", - example: "1 phaser", - compile: Param, - varargs: false, - }, - Word { - name: "phaserdepth", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set phaser depth", - example: "0.5 phaserdepth", - compile: Param, - varargs: false, - }, - Word { - name: "phasersweep", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set phaser sweep", - example: "0.5 phasersweep", - compile: Param, - varargs: false, - }, - Word { - name: "phasercenter", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set phaser center", - example: "1000 phasercenter", - compile: Param, - varargs: false, - }, - Word { - name: "flanger", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set flanger rate", - example: "0.5 flanger", - compile: Param, - varargs: false, - }, - Word { - name: "flangerdepth", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set flanger depth", - example: "0.5 flangerdepth", - compile: Param, - varargs: false, - }, - Word { - name: "flangerfeedback", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set flanger feedback", - example: "0.5 flangerfeedback", - compile: Param, - varargs: false, - }, - Word { - name: "chorus", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set chorus rate", - example: "1 chorus", - compile: Param, - varargs: false, - }, - Word { - name: "chorusdepth", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set chorus depth", - example: "0.5 chorusdepth", - compile: Param, - varargs: false, - }, - Word { - name: "chorusdelay", - aliases: &[], - category: "Mod FX", - stack: "(f --)", - desc: "Set chorus delay", - example: "0.02 chorusdelay", - compile: Param, - varargs: false, - }, - Word { - name: "eqlo", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set low shelf gain (dB)", - example: "3 eqlo", - compile: Param, - varargs: false, - }, - Word { - name: "eqmid", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set mid peak gain (dB)", - example: "-2 eqmid", - compile: Param, - varargs: false, - }, - Word { - name: "eqhi", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set high shelf gain (dB)", - example: "1 eqhi", - compile: Param, - varargs: false, - }, - Word { - name: "tilt", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set tilt EQ (-1 dark, 1 bright)", - example: "-0.5 tilt", - compile: Param, - varargs: false, - }, - Word { - name: "width", - aliases: &[], - category: "Stereo", - stack: "(f --)", - desc: "Set stereo width (0 mono, 1 normal, 2 wide)", - example: "0 width", - compile: Param, - varargs: false, - }, - Word { - name: "haas", - aliases: &[], - category: "Stereo", - stack: "(f --)", - desc: "Set Haas delay in ms (spatial placement)", - example: "8 haas", - compile: Param, - varargs: false, - }, - Word { - name: "comb", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set comb filter mix", - example: "0.5 comb", - compile: Param, - varargs: false, - }, - Word { - name: "combfreq", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set comb frequency", - example: "200 combfreq", - compile: Param, - varargs: false, - }, - Word { - name: "combfeedback", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set comb feedback", - example: "0.5 combfeedback", - compile: Param, - varargs: false, - }, - Word { - name: "combdamp", - aliases: &[], - category: "Filter", - stack: "(f --)", - desc: "Set comb damping", - example: "0.5 combdamp", - compile: Param, - varargs: false, - }, - Word { - name: "coarse", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set coarse tune", - example: "12 coarse", - compile: Param, - varargs: false, - }, - Word { - name: "crush", - aliases: &[], - category: "Lo-fi", - stack: "(f --)", - desc: "Set bit crush", - example: "8 crush", - compile: Param, - varargs: false, - }, - Word { - name: "sub", - aliases: &[], - category: "Oscillator", - stack: "(f --)", - desc: "Set sub oscillator level", - example: "0.5 sub", - compile: Param, - varargs: false, - }, - Word { - name: "suboct", - aliases: &[], - category: "Oscillator", - stack: "(n --)", - desc: "Set sub oscillator octave", - example: "2 suboct", - compile: Param, - varargs: false, - }, - Word { - name: "subwave", - aliases: &[], - category: "Oscillator", - stack: "(n --)", - desc: "Set sub oscillator waveform", - example: "1 subwave", - compile: Param, - varargs: false, - }, - Word { - name: "fold", - aliases: &[], - category: "Lo-fi", - stack: "(f --)", - desc: "Set wave fold", - example: "2 fold", - compile: Param, - varargs: false, - }, - Word { - name: "wrap", - aliases: &[], - category: "Lo-fi", - stack: "(f --)", - desc: "Set wave wrap", - example: "0.5 wrap", - compile: Param, - varargs: false, - }, - Word { - name: "distort", - aliases: &[], - category: "Lo-fi", - stack: "(f --)", - desc: "Set distortion", - example: "0.5 distort", - compile: Param, - varargs: false, - }, - Word { - name: "distortvol", - aliases: &[], - category: "Lo-fi", - stack: "(f --)", - desc: "Set distortion volume", - example: "0.8 distortvol", - compile: Param, - varargs: false, - }, - Word { - name: "delay", - aliases: &[], - category: "Delay", - stack: "(f --)", - desc: "Set delay mix", - example: "0.3 delay", - compile: Param, - varargs: false, - }, - Word { - name: "delaytime", - aliases: &[], - category: "Delay", - stack: "(f --)", - desc: "Set delay time", - example: "0.25 delaytime", - compile: Param, - varargs: false, - }, - Word { - name: "delayfeedback", - aliases: &[], - category: "Delay", - stack: "(f --)", - desc: "Set delay feedback", - example: "0.5 delayfeedback", - compile: Param, - varargs: false, - }, - Word { - name: "delaytype", - aliases: &[], - category: "Delay", - stack: "(n --)", - desc: "Set delay type", - example: "1 delaytype", - compile: Param, - varargs: false, - }, - Word { - name: "verb", - aliases: &[], - category: "Reverb", - stack: "(f --)", - desc: "Set reverb mix", - example: "0.3 verb", - compile: Param, - varargs: false, - }, - Word { - name: "verbdecay", - aliases: &[], - category: "Reverb", - stack: "(f --)", - desc: "Set reverb decay", - example: "2 verbdecay", - compile: Param, - varargs: false, - }, - Word { - name: "verbdamp", - aliases: &[], - category: "Reverb", - stack: "(f --)", - desc: "Set reverb damping", - example: "0.5 verbdamp", - compile: Param, - varargs: false, - }, - Word { - name: "verbpredelay", - aliases: &[], - category: "Reverb", - stack: "(f --)", - desc: "Set reverb predelay", - example: "0.02 verbpredelay", - compile: Param, - varargs: false, - }, - Word { - name: "verbdiff", - aliases: &[], - category: "Reverb", - stack: "(f --)", - desc: "Set reverb diffusion", - example: "0.7 verbdiff", - compile: Param, - varargs: false, - }, - Word { - name: "voice", - aliases: &[], - category: "Sample", - stack: "(n --)", - desc: "Set voice number", - example: "1 voice", - compile: Param, - varargs: false, - }, - Word { - name: "orbit", - aliases: &[], - category: "Sample", - stack: "(n --)", - desc: "Set orbit/bus", - example: "0 orbit", - compile: Param, - varargs: false, - }, - Word { - name: "note", - aliases: &[], - category: "Oscillator", - stack: "(n --)", - desc: "Set MIDI note", - example: "60 note", - compile: Param, - varargs: false, - }, - Word { - name: "size", - aliases: &[], - category: "Reverb", - stack: "(f --)", - desc: "Set size", - example: "1 size", - compile: Param, - varargs: false, - }, - Word { - name: "n", - aliases: &[], - category: "Sample", - stack: "(n --)", - desc: "Set sample number", - example: "0 n", - compile: Param, - varargs: false, - }, - Word { - name: "cut", - aliases: &[], - category: "Sample", - stack: "(n --)", - desc: "Set cut group", - example: "1 cut", - compile: Param, - varargs: false, - }, - Word { - name: "reset", - aliases: &[], - category: "Sample", - stack: "(n --)", - desc: "Reset parameter", - example: "1 reset", - compile: Param, - varargs: false, - }, - Word { - name: "clear", - aliases: &[], - category: "Sound", - stack: "(--)", - desc: "Clear sound register (sound and all params)", - example: "\"kick\" s 0.5 gain . clear \"hat\" s .", - compile: Simple, - varargs: false, - }, - // Quotation execution - Word { - name: "apply", - aliases: &[], - category: "Logic", - stack: "(quot --)", - desc: "Execute quotation unconditionally", - example: "{ 2 * } apply", - compile: Simple, - varargs: false, - }, - // Word definitions - Word { - name: ":", - aliases: &[], - category: "Definitions", - stack: "( -- )", - desc: "Begin word definition", - example: ": kick \"kick\" s emit ;", - compile: Simple, - varargs: false, - }, - Word { - name: ";", - aliases: &[], - category: "Definitions", - stack: "( -- )", - desc: "End word definition", - example: ": kick \"kick\" s emit ;", - compile: Simple, - varargs: false, - }, - Word { - name: "forget", - aliases: &[], - category: "Definitions", - stack: "(name --)", - desc: "Remove user-defined word from dictionary", - example: "\"double\" forget", - compile: Simple, - varargs: false, - }, - // Generator - Word { - name: "..", - aliases: &[], - category: "Generator", - stack: "(start end -- start start+1 ... end)", - desc: "Push arithmetic sequence from start to end", - example: "1 4 .. => 1 2 3 4", - compile: Simple, - varargs: false, - }, - Word { - name: "gen", - aliases: &[], - category: "Generator", - stack: "(quot n -- results...)", - desc: "Execute quotation n times, push all results", - example: "{ 1 6 rand } 4 gen => 4 random values", - compile: Simple, - varargs: true, - }, - Word { - name: "geom..", - aliases: &[], - category: "Generator", - stack: "(start ratio count -- start start*r start*r^2 ...)", - desc: "Push geometric sequence", - example: "1 2 4 geom.. => 1 2 4 8", - 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, - }, - // MIDI - Word { - name: "chan", - aliases: &[], - category: "MIDI", - stack: "(n --)", - desc: "Set MIDI channel 1-16", - example: "1 chan", - compile: Param, - varargs: false, - }, - Word { - name: "ccnum", - aliases: &[], - category: "MIDI", - stack: "(n --)", - desc: "Set MIDI CC number 0-127", - example: "1 ccnum", - compile: Param, - varargs: false, - }, - Word { - name: "ccout", - aliases: &[], - category: "MIDI", - stack: "(n --)", - desc: "Set MIDI CC output value 0-127", - example: "64 ccout", - compile: Param, - varargs: false, - }, - Word { - name: "bend", - aliases: &[], - category: "MIDI", - stack: "(f --)", - desc: "Set pitch bend -1.0 to 1.0 (0 = center)", - example: "0.5 bend", - compile: Param, - varargs: false, - }, - Word { - name: "pressure", - aliases: &[], - category: "MIDI", - stack: "(n --)", - desc: "Set channel pressure (aftertouch) 0-127", - example: "64 pressure", - compile: Param, - varargs: false, - }, - Word { - name: "program", - aliases: &[], - category: "MIDI", - stack: "(n --)", - desc: "Set program change number 0-127", - example: "0 program", - compile: Param, - varargs: false, - }, - Word { - name: "m.", - aliases: &[], - category: "MIDI", - stack: "(--)", - desc: "Emit MIDI message from params (note/cc/bend/pressure/program)", - example: "60 note 100 velocity 1 chan m.", - compile: Simple, - varargs: false, - }, - Word { - name: "mclock", - aliases: &[], - category: "MIDI", - stack: "(--)", - desc: "Send MIDI clock pulse (24 per quarter note)", - example: "mclock", - compile: Simple, - varargs: false, - }, - Word { - name: "mstart", - aliases: &[], - category: "MIDI", - stack: "(--)", - desc: "Send MIDI start message", - example: "mstart", - compile: Simple, - varargs: false, - }, - Word { - name: "mstop", - aliases: &[], - category: "MIDI", - stack: "(--)", - desc: "Send MIDI stop message", - example: "mstop", - compile: Simple, - varargs: false, - }, - Word { - name: "mcont", - aliases: &[], - category: "MIDI", - stack: "(--)", - desc: "Send MIDI continue message", - example: "mcont", - compile: Simple, - varargs: false, - }, - Word { - name: "ccval", - aliases: &[], - category: "MIDI", - stack: "(cc chan -- val)", - desc: "Read CC value 0-127 from MIDI input (uses dev param for device)", - example: "1 1 ccval", - compile: Simple, - varargs: false, - }, - Word { - name: "dev", - aliases: &[], - category: "MIDI", - stack: "(n --)", - desc: "Set MIDI device slot 0-3 for output/input", - example: "1 dev 60 note m.", - compile: Param, - varargs: false, - }, -]; - -static WORD_MAP: LazyLock> = LazyLock::new(|| { - let mut map = HashMap::with_capacity(WORDS.len() * 2); - for word in WORDS { - map.insert(word.name, word); - for alias in word.aliases { - map.insert(alias, word); - } - } - map -}); - -pub fn lookup_word(name: &str) -> Option<&'static Word> { - WORD_MAP.get(name).copied() -} - -pub(super) fn simple_op(name: &str) -> Option { - Some(match name { - "dup" => Op::Dup, - "dupn" => Op::Dupn, - "drop" => Op::Drop, - "swap" => Op::Swap, - "over" => Op::Over, - "rot" => Op::Rot, - "nip" => Op::Nip, - "tuck" => Op::Tuck, - "2dup" => Op::Dup2, - "2drop" => Op::Drop2, - "2swap" => Op::Swap2, - "2over" => Op::Over2, - "+" => Op::Add, - "-" => Op::Sub, - "*" => Op::Mul, - "/" => Op::Div, - "mod" => Op::Mod, - "neg" => Op::Neg, - "abs" => Op::Abs, - "floor" => Op::Floor, - "ceil" => Op::Ceil, - "round" => Op::Round, - "min" => Op::Min, - "max" => Op::Max, - "pow" => Op::Pow, - "sqrt" => Op::Sqrt, - "sin" => Op::Sin, - "cos" => Op::Cos, - "log" => Op::Log, - "=" => Op::Eq, - "!=" => Op::Ne, - "lt" => Op::Lt, - "gt" => Op::Gt, - "<=" => Op::Le, - ">=" => Op::Ge, - "and" => Op::And, - "or" => Op::Or, - "not" => Op::Not, - "xor" => Op::Xor, - "nand" => Op::Nand, - "nor" => Op::Nor, - "ifelse" => Op::IfElse, - "pick" => Op::Pick, - "sound" => Op::NewCmd, - "." => Op::Emit, - "rand" => Op::Rand, - "exprand" => Op::ExpRand, - "logrand" => Op::LogRand, - "seed" => Op::Seed, - "cycle" => Op::Cycle, - "pcycle" => Op::PCycle, - "choose" => Op::Choose, - "every" => Op::Every, - "chance" => Op::ChanceExec, - "prob" => Op::ProbExec, - "coin" => Op::Coin, - "mtof" => Op::Mtof, - "ftom" => Op::Ftom, - "?" => Op::When, - "!?" => Op::Unless, - "tempo!" => Op::SetTempo, - "speed!" => Op::SetSpeed, - "at" => Op::At, - "adsr" => Op::Adsr, - "ad" => Op::Ad, - "apply" => Op::Apply, - "ramp" => Op::Ramp, - "triangle" => Op::Triangle, - "range" => Op::Range, - "perlin" => Op::Perlin, - "chain" => Op::Chain, - "loop" => Op::Loop, - "oct" => Op::Oct, - "clear" => Op::ClearCmd, - ".." => Op::IntRange, - "gen" => Op::Generate, - "geom.." => Op::GeomRange, - "times" => Op::Times, - "m." => Op::MidiEmit, - "ccval" => Op::GetMidiCC, - "mclock" => Op::MidiClock, - "mstart" => Op::MidiStart, - "mstop" => Op::MidiStop, - "mcont" => Op::MidiContinue, - "forget" => Op::Forget, - _ => return None, - }) -} - -/// Parse note names like c4, c#4, cs4, eb4 into MIDI numbers. -/// C4 = 60 (middle C), A4 = 69 (440 Hz reference). -fn parse_note_name(name: &str) -> Option { - let name = name.to_lowercase(); - let bytes = name.as_bytes(); - - if bytes.len() < 2 { - return None; - } - - let base = match bytes[0] { - b'c' => 0, - b'd' => 2, - b'e' => 4, - b'f' => 5, - b'g' => 7, - b'a' => 9, - b'b' => 11, - _ => return None, - }; - - let (modifier, octave_start) = match bytes[1] { - b'#' | b's' => (1, 2), - b'b' if bytes.len() > 2 && bytes[2].is_ascii_digit() => (-1, 2), // flat: eb4, bb4 - b'0'..=b'9' => (0, 1), - _ => return None, - }; - - let octave_str = &name[octave_start..]; - let octave: i64 = octave_str.parse().ok()?; - - if !(-1..=9).contains(&octave) { - return None; - } - - // MIDI: C4 = 60, so C-1 = 0 - Some((octave + 1) * 12 + base + modifier) -} - -/// Parse interval names like m3, M3, P5 into semitone counts. -/// Supports simple intervals (1-8) and compound intervals (9-15). -fn parse_interval(name: &str) -> Option { - // Simple intervals: unison through octave - let simple = match name { - "P1" | "unison" => 0, - "m2" => 1, - "M2" => 2, - "m3" => 3, - "M3" => 4, - "P4" => 5, - "aug4" | "dim5" | "tritone" => 6, - "P5" => 7, - "m6" => 8, - "M6" => 9, - "m7" => 10, - "M7" => 11, - "P8" => 12, - // Compound intervals (octave + simple) - "m9" => 13, - "M9" => 14, - "m10" => 15, - "M10" => 16, - "P11" => 17, - "aug11" => 18, - "P12" => 19, - "m13" => 20, - "M13" => 21, - "m14" => 22, - "M14" => 23, - "P15" => 24, - _ => return None, - }; - Some(simple) -} - -pub(super) fn compile_word( - name: &str, - span: Option, - ops: &mut Vec, - dict: &Dictionary, -) -> bool { - match name { - "linramp" => { - ops.push(Op::PushFloat(1.0, span)); - 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; - } - _ => {} - } - - if let Some(pattern) = theory::lookup(name) { - ops.push(Op::Degree(pattern)); - return true; - } - - if let Some(intervals) = theory::chords::lookup(name) { - ops.push(Op::Chord(intervals)); - return true; - } - - if let Some(word) = lookup_word(name) { - match &word.compile { - Simple => { - if let Some(op) = simple_op(word.name) { - ops.push(op); - } - } - Context(ctx) => ops.push(Op::GetContext((*ctx).into())), - Param => ops.push(Op::SetParam(word.name.into())), - Probability(p) => { - ops.push(Op::PushFloat(*p, None)); - ops.push(Op::ChanceExec); - } - } - return true; - } - - // @varname - fetch variable - if let Some(var_name) = name.strip_prefix('@') { - if !var_name.is_empty() { - ops.push(Op::PushStr(Arc::from(var_name), span)); - ops.push(Op::Get); - return true; - } - } - - // !varname - store into variable - if let Some(var_name) = name.strip_prefix('!') { - if !var_name.is_empty() { - ops.push(Op::PushStr(Arc::from(var_name), span)); - ops.push(Op::Set); - return true; - } - } - - // Note names: c4, c#4, cs4, eb4, etc. -> MIDI number - if let Some(midi) = parse_note_name(name) { - ops.push(Op::PushInt(midi, span)); - return true; - } - - // Intervals: m3, M3, P5, etc. -> dup top, add semitones (for chord building) - if let Some(semitones) = parse_interval(name) { - ops.push(Op::Dup); - ops.push(Op::PushInt(semitones, span)); - ops.push(Op::Add); - return true; - } - - // Internal ops not exposed in WORDS - if let Some(op) = simple_op(name) { - ops.push(op); - return true; - } - - // User-defined words from dictionary - if let Some(body) = dict.lock().get(name) { - ops.extend(body.iter().cloned()); - return true; - } - - // Unrecognized token becomes a string - ops.push(Op::PushStr(Arc::from(name), span)); - true -} diff --git a/crates/forth/src/words/compile.rs b/crates/forth/src/words/compile.rs new file mode 100644 index 0000000..a87479c --- /dev/null +++ b/crates/forth/src/words/compile.rs @@ -0,0 +1,260 @@ +use std::sync::Arc; + +use crate::ops::Op; +use crate::theory; +use crate::types::{Dictionary, SourceSpan}; + +use super::{lookup_word, WordCompile::*}; + +pub(super) fn simple_op(name: &str) -> Option { + Some(match name { + "dup" => Op::Dup, + "dupn" => Op::Dupn, + "drop" => Op::Drop, + "swap" => Op::Swap, + "over" => Op::Over, + "rot" => Op::Rot, + "nip" => Op::Nip, + "tuck" => Op::Tuck, + "2dup" => Op::Dup2, + "2drop" => Op::Drop2, + "2swap" => Op::Swap2, + "2over" => Op::Over2, + "+" => Op::Add, + "-" => Op::Sub, + "*" => Op::Mul, + "/" => Op::Div, + "mod" => Op::Mod, + "neg" => Op::Neg, + "abs" => Op::Abs, + "floor" => Op::Floor, + "ceil" => Op::Ceil, + "round" => Op::Round, + "min" => Op::Min, + "max" => Op::Max, + "pow" => Op::Pow, + "sqrt" => Op::Sqrt, + "sin" => Op::Sin, + "cos" => Op::Cos, + "log" => Op::Log, + "=" => Op::Eq, + "!=" => Op::Ne, + "lt" => Op::Lt, + "gt" => Op::Gt, + "<=" => Op::Le, + ">=" => Op::Ge, + "and" => Op::And, + "or" => Op::Or, + "not" => Op::Not, + "xor" => Op::Xor, + "nand" => Op::Nand, + "nor" => Op::Nor, + "ifelse" => Op::IfElse, + "pick" => Op::Pick, + "sound" => Op::NewCmd, + "." => Op::Emit, + "rand" => Op::Rand, + "exprand" => Op::ExpRand, + "logrand" => Op::LogRand, + "seed" => Op::Seed, + "cycle" => Op::Cycle, + "pcycle" => Op::PCycle, + "choose" => Op::Choose, + "every" => Op::Every, + "chance" => Op::ChanceExec, + "prob" => Op::ProbExec, + "coin" => Op::Coin, + "mtof" => Op::Mtof, + "ftom" => Op::Ftom, + "?" => Op::When, + "!?" => Op::Unless, + "tempo!" => Op::SetTempo, + "speed!" => Op::SetSpeed, + "at" => Op::At, + "adsr" => Op::Adsr, + "ad" => Op::Ad, + "apply" => Op::Apply, + "ramp" => Op::Ramp, + "triangle" => Op::Triangle, + "range" => Op::Range, + "perlin" => Op::Perlin, + "chain" => Op::Chain, + "loop" => Op::Loop, + "oct" => Op::Oct, + "clear" => Op::ClearCmd, + ".." => Op::IntRange, + "gen" => Op::Generate, + "geom.." => Op::GeomRange, + "times" => Op::Times, + "m." => Op::MidiEmit, + "ccval" => Op::GetMidiCC, + "mclock" => Op::MidiClock, + "mstart" => Op::MidiStart, + "mstop" => Op::MidiStop, + "mcont" => Op::MidiContinue, + "forget" => Op::Forget, + _ => return None, + }) +} + +fn parse_note_name(name: &str) -> Option { + let name = name.to_lowercase(); + let bytes = name.as_bytes(); + + if bytes.len() < 2 { + return None; + } + + let base = match bytes[0] { + b'c' => 0, + b'd' => 2, + b'e' => 4, + b'f' => 5, + b'g' => 7, + b'a' => 9, + b'b' => 11, + _ => return None, + }; + + let (modifier, octave_start) = match bytes[1] { + b'#' | b's' => (1, 2), + b'b' if bytes.len() > 2 && bytes[2].is_ascii_digit() => (-1, 2), + b'0'..=b'9' => (0, 1), + _ => return None, + }; + + let octave_str = &name[octave_start..]; + let octave: i64 = octave_str.parse().ok()?; + + if !(-1..=9).contains(&octave) { + return None; + } + + Some((octave + 1) * 12 + base + modifier) +} + +fn parse_interval(name: &str) -> Option { + let simple = match name { + "P1" | "unison" => 0, + "m2" => 1, + "M2" => 2, + "m3" => 3, + "M3" => 4, + "P4" => 5, + "aug4" | "dim5" | "tritone" => 6, + "P5" => 7, + "m6" => 8, + "M6" => 9, + "m7" => 10, + "M7" => 11, + "P8" => 12, + "m9" => 13, + "M9" => 14, + "m10" => 15, + "M10" => 16, + "P11" => 17, + "aug11" => 18, + "P12" => 19, + "m13" => 20, + "M13" => 21, + "m14" => 22, + "M14" => 23, + "P15" => 24, + _ => return None, + }; + Some(simple) +} + +pub(crate) fn compile_word( + name: &str, + span: Option, + ops: &mut Vec, + dict: &Dictionary, +) -> bool { + match name { + "linramp" => { + ops.push(Op::PushFloat(1.0, span)); + 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; + } + _ => {} + } + + if let Some(pattern) = theory::lookup(name) { + ops.push(Op::Degree(pattern)); + return true; + } + + if let Some(intervals) = theory::chords::lookup(name) { + ops.push(Op::Chord(intervals)); + return true; + } + + if let Some(word) = lookup_word(name) { + match &word.compile { + Simple => { + if let Some(op) = simple_op(word.name) { + ops.push(op); + } + } + Context(ctx) => ops.push(Op::GetContext((*ctx).into())), + Param => ops.push(Op::SetParam(word.name.into())), + Probability(p) => { + ops.push(Op::PushFloat(*p, None)); + ops.push(Op::ChanceExec); + } + } + return true; + } + + if let Some(var_name) = name.strip_prefix('@') { + if !var_name.is_empty() { + ops.push(Op::PushStr(Arc::from(var_name), span)); + ops.push(Op::Get); + return true; + } + } + + if let Some(var_name) = name.strip_prefix('!') { + if !var_name.is_empty() { + ops.push(Op::PushStr(Arc::from(var_name), span)); + ops.push(Op::Set); + return true; + } + } + + if let Some(midi) = parse_note_name(name) { + ops.push(Op::PushInt(midi, span)); + return true; + } + + if let Some(semitones) = parse_interval(name) { + ops.push(Op::Dup); + ops.push(Op::PushInt(semitones, span)); + ops.push(Op::Add); + return true; + } + + if let Some(op) = simple_op(name) { + ops.push(op); + return true; + } + + if let Some(body) = dict.lock().get(name) { + ops.extend(body.iter().cloned()); + return true; + } + + ops.push(Op::PushStr(Arc::from(name), span)); + true +} diff --git a/crates/forth/src/words/core.rs b/crates/forth/src/words/core.rs new file mode 100644 index 0000000..db14e18 --- /dev/null +++ b/crates/forth/src/words/core.rs @@ -0,0 +1,532 @@ +use super::{Word, WordCompile::*}; + +// Stack, Arithmetic, Comparison, Logic, Control, Variables, Definitions +pub(super) const WORDS: &[Word] = &[ + // Stack manipulation + Word { + name: "dup", + aliases: &[], + category: "Stack", + stack: "(a -- a a)", + desc: "Duplicate top of stack", + example: "3 dup => 3 3", + compile: Simple, + varargs: false, + }, + Word { + name: "dupn", + aliases: &["!"], + category: "Stack", + stack: "(a n -- a a ... a)", + desc: "Duplicate a onto stack n times", + example: "2 4 dupn => 2 2 2 2", + compile: Simple, + varargs: true, + }, + Word { + name: "drop", + aliases: &[], + category: "Stack", + stack: "(a --)", + desc: "Remove top of stack", + example: "1 2 drop => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "swap", + aliases: &[], + category: "Stack", + stack: "(a b -- b a)", + desc: "Exchange top two items", + example: "1 2 swap => 2 1", + compile: Simple, + varargs: false, + }, + Word { + name: "over", + aliases: &[], + category: "Stack", + stack: "(a b -- a b a)", + desc: "Copy second to top", + example: "1 2 over => 1 2 1", + compile: Simple, + varargs: false, + }, + Word { + name: "rot", + aliases: &[], + category: "Stack", + stack: "(a b c -- b c a)", + desc: "Rotate top three", + example: "1 2 3 rot => 2 3 1", + compile: Simple, + varargs: false, + }, + Word { + name: "nip", + aliases: &[], + category: "Stack", + stack: "(a b -- b)", + desc: "Remove second item", + example: "1 2 nip => 2", + compile: Simple, + varargs: false, + }, + Word { + name: "tuck", + aliases: &[], + category: "Stack", + stack: "(a b -- b a b)", + desc: "Copy top under second", + example: "1 2 tuck => 2 1 2", + compile: Simple, + varargs: false, + }, + Word { + name: "2dup", + aliases: &[], + category: "Stack", + stack: "(a b -- a b a b)", + desc: "Duplicate top two values", + example: "1 2 2dup => 1 2 1 2", + compile: Simple, + varargs: false, + }, + Word { + name: "2drop", + aliases: &[], + category: "Stack", + stack: "(a b --)", + desc: "Drop top two values", + example: "1 2 3 2drop => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "2swap", + aliases: &[], + category: "Stack", + stack: "(a b c d -- c d a b)", + desc: "Swap top two pairs", + example: "1 2 3 4 2swap => 3 4 1 2", + compile: Simple, + varargs: false, + }, + Word { + name: "2over", + aliases: &[], + category: "Stack", + stack: "(a b c d -- a b c d a b)", + desc: "Copy second pair to top", + example: "1 2 3 4 2over => 1 2 3 4 1 2", + compile: Simple, + varargs: false, + }, + // Arithmetic + Word { + name: "+", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- a+b)", + desc: "Add", + example: "2 3 + => 5", + compile: Simple, + varargs: false, + }, + Word { + name: "-", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- a-b)", + desc: "Subtract", + example: "5 3 - => 2", + compile: Simple, + varargs: false, + }, + Word { + name: "*", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- a*b)", + desc: "Multiply", + example: "3 4 * => 12", + compile: Simple, + varargs: false, + }, + Word { + name: "/", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- a/b)", + desc: "Divide", + example: "10 2 / => 5", + compile: Simple, + varargs: false, + }, + Word { + name: "mod", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- a%b)", + desc: "Modulo", + example: "7 3 mod => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "neg", + aliases: &[], + category: "Arithmetic", + stack: "(a -- -a)", + desc: "Negate", + example: "5 neg => -5", + compile: Simple, + varargs: false, + }, + Word { + name: "abs", + aliases: &[], + category: "Arithmetic", + stack: "(a -- |a|)", + desc: "Absolute value", + example: "-5 abs => 5", + compile: Simple, + varargs: false, + }, + Word { + name: "floor", + aliases: &[], + category: "Arithmetic", + stack: "(f -- n)", + desc: "Round down to integer", + example: "3.7 floor => 3", + compile: Simple, + varargs: false, + }, + Word { + name: "ceil", + aliases: &[], + category: "Arithmetic", + stack: "(f -- n)", + desc: "Round up to integer", + example: "3.2 ceil => 4", + compile: Simple, + varargs: false, + }, + Word { + name: "round", + aliases: &[], + category: "Arithmetic", + stack: "(f -- n)", + desc: "Round to nearest integer", + example: "3.5 round => 4", + compile: Simple, + varargs: false, + }, + Word { + name: "min", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- min)", + desc: "Minimum of two values", + example: "3 5 min => 3", + compile: Simple, + varargs: false, + }, + Word { + name: "max", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- max)", + desc: "Maximum of two values", + example: "3 5 max => 5", + compile: Simple, + varargs: false, + }, + Word { + name: "pow", + aliases: &[], + category: "Arithmetic", + stack: "(a b -- a^b)", + desc: "Exponentiation", + example: "2 3 pow => 8", + compile: Simple, + varargs: false, + }, + Word { + name: "sqrt", + aliases: &[], + category: "Arithmetic", + stack: "(a -- √a)", + desc: "Square root", + example: "16 sqrt => 4", + compile: Simple, + varargs: false, + }, + Word { + name: "sin", + aliases: &[], + category: "Arithmetic", + stack: "(a -- sin(a))", + desc: "Sine (radians)", + example: "3.14159 2 / sin => 1.0", + compile: Simple, + varargs: false, + }, + Word { + name: "cos", + aliases: &[], + category: "Arithmetic", + stack: "(a -- cos(a))", + desc: "Cosine (radians)", + example: "0 cos => 1.0", + compile: Simple, + varargs: false, + }, + Word { + name: "log", + aliases: &[], + category: "Arithmetic", + stack: "(a -- ln(a))", + desc: "Natural logarithm", + example: "2.718 log => 1.0", + compile: Simple, + varargs: false, + }, + // Comparison + Word { + name: "=", + aliases: &[], + category: "Comparison", + stack: "(a b -- bool)", + desc: "Equal", + example: "3 3 = => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "!=", + aliases: &["<>"], + category: "Comparison", + stack: "(a b -- bool)", + desc: "Not equal", + example: "3 4 != => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "lt", + aliases: &[], + category: "Comparison", + stack: "(a b -- bool)", + desc: "Less than", + example: "2 3 lt => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "gt", + aliases: &[], + category: "Comparison", + stack: "(a b -- bool)", + desc: "Greater than", + example: "3 2 gt => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "<=", + aliases: &[], + category: "Comparison", + stack: "(a b -- bool)", + desc: "Less or equal", + example: "3 3 <= => 1", + compile: Simple, + varargs: false, + }, + Word { + name: ">=", + aliases: &[], + category: "Comparison", + stack: "(a b -- bool)", + desc: "Greater or equal", + example: "3 3 >= => 1", + compile: Simple, + varargs: false, + }, + // Logic + Word { + name: "and", + aliases: &[], + category: "Logic", + stack: "(a b -- bool)", + desc: "Logical and", + example: "1 1 and => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "or", + aliases: &[], + category: "Logic", + stack: "(a b -- bool)", + desc: "Logical or", + example: "0 1 or => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "not", + aliases: &[], + category: "Logic", + stack: "(a -- bool)", + desc: "Logical not", + example: "0 not => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "xor", + aliases: &[], + category: "Logic", + stack: "(a b -- bool)", + desc: "Exclusive or", + example: "1 0 xor => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "nand", + aliases: &[], + category: "Logic", + stack: "(a b -- bool)", + desc: "Not and", + example: "1 1 nand => 0", + compile: Simple, + varargs: false, + }, + Word { + name: "nor", + aliases: &[], + category: "Logic", + stack: "(a b -- bool)", + desc: "Not or", + example: "0 0 nor => 1", + compile: Simple, + varargs: false, + }, + Word { + name: "ifelse", + aliases: &[], + category: "Logic", + stack: "(true-quot false-quot bool --)", + desc: "Execute true-quot if true, else false-quot", + example: "{ 1 } { 2 } coin ifelse", + compile: Simple, + varargs: false, + }, + Word { + name: "pick", + aliases: &[], + category: "Logic", + stack: "(..quots n --)", + desc: "Execute nth quotation (0-indexed)", + example: "{ 1 } { 2 } { 3 } 2 pick => 3", + compile: Simple, + varargs: true, + }, + Word { + name: "?", + aliases: &[], + category: "Logic", + stack: "(quot bool --)", + desc: "Execute quotation if true", + example: "{ 2 distort } 0.5 chance ?", + compile: Simple, + varargs: false, + }, + Word { + name: "!?", + aliases: &[], + category: "Logic", + stack: "(quot bool --)", + desc: "Execute quotation if false", + example: "{ 1 distort } 0.5 chance !?", + compile: Simple, + varargs: false, + }, + Word { + name: "apply", + aliases: &[], + category: "Logic", + stack: "(quot --)", + desc: "Execute quotation unconditionally", + example: "{ 2 * } apply", + compile: Simple, + varargs: false, + }, + // Control + 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, + }, + // Variables + Word { + name: "@", + aliases: &[], + category: "Variables", + stack: "( -- val)", + desc: "Fetch variable value", + example: "@freq => 440", + compile: Simple, + varargs: false, + }, + Word { + name: "!", + aliases: &[], + category: "Variables", + stack: "(val --)", + desc: "Store value in variable", + example: "440 !freq", + compile: Simple, + varargs: false, + }, + // Definitions + Word { + name: ":", + aliases: &[], + category: "Definitions", + stack: "( -- )", + desc: "Begin word definition", + example: ": kick \"kick\" s emit ;", + compile: Simple, + varargs: false, + }, + Word { + name: ";", + aliases: &[], + category: "Definitions", + stack: "( -- )", + desc: "End word definition", + example: ": kick \"kick\" s emit ;", + compile: Simple, + varargs: false, + }, + Word { + name: "forget", + aliases: &[], + category: "Definitions", + stack: "(name --)", + desc: "Remove user-defined word from dictionary", + example: "\"double\" forget", + compile: Simple, + varargs: false, + }, +]; diff --git a/crates/forth/src/words/effects.rs b/crates/forth/src/words/effects.rs new file mode 100644 index 0000000..df7a2b3 --- /dev/null +++ b/crates/forth/src/words/effects.rs @@ -0,0 +1,792 @@ +use super::{Word, WordCompile::*}; + +// Filter, Envelope, Reverb, Delay, Lo-fi, Stereo, Mod FX +pub(super) const WORDS: &[Word] = &[ + // Envelope + Word { + name: "gain", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set volume (0-1)", + example: "0.8 gain", + compile: Param, + varargs: false, + }, + Word { + name: "postgain", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set post gain", + example: "1.2 postgain", + compile: Param, + varargs: false, + }, + Word { + name: "velocity", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set velocity", + example: "100 velocity", + compile: Param, + varargs: false, + }, + Word { + name: "attack", + aliases: &["att"], + category: "Envelope", + stack: "(f --)", + desc: "Set attack time", + example: "0.01 attack", + compile: Param, + varargs: false, + }, + Word { + name: "decay", + aliases: &["dec"], + category: "Envelope", + stack: "(f --)", + desc: "Set decay time", + example: "0.1 decay", + compile: Param, + varargs: false, + }, + Word { + name: "sustain", + aliases: &["sus"], + category: "Envelope", + stack: "(f --)", + desc: "Set sustain level", + example: "0.5 sustain", + compile: Param, + varargs: false, + }, + Word { + name: "release", + aliases: &["rel"], + category: "Envelope", + stack: "(f --)", + desc: "Set release time", + example: "0.3 release", + compile: Param, + varargs: false, + }, + Word { + name: "adsr", + aliases: &[], + category: "Envelope", + stack: "(a d s r --)", + desc: "Set attack, decay, sustain, release", + example: "0.01 0.1 0.5 0.3 adsr", + compile: Simple, + varargs: false, + }, + Word { + name: "ad", + aliases: &[], + category: "Envelope", + stack: "(a d --)", + desc: "Set attack, decay (sustain=0)", + example: "0.01 0.1 ad", + compile: Simple, + varargs: false, + }, + Word { + name: "penv", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set pitch envelope", + example: "0.5 penv", + compile: Param, + varargs: false, + }, + Word { + name: "patt", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set pitch attack", + example: "0.01 patt", + compile: Param, + varargs: false, + }, + Word { + name: "pdec", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set pitch decay", + example: "0.1 pdec", + compile: Param, + varargs: false, + }, + Word { + name: "psus", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set pitch sustain", + example: "0 psus", + compile: Param, + varargs: false, + }, + Word { + name: "prel", + aliases: &[], + category: "Envelope", + stack: "(f --)", + desc: "Set pitch release", + example: "0.1 prel", + compile: Param, + varargs: false, + }, + // Filter + Word { + name: "lpf", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set lowpass frequency", + example: "2000 lpf", + compile: Param, + varargs: false, + }, + Word { + name: "lpq", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set lowpass resonance", + example: "0.5 lpq", + compile: Param, + varargs: false, + }, + Word { + name: "lpe", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set lowpass envelope", + example: "0.5 lpe", + compile: Param, + varargs: false, + }, + Word { + name: "lpa", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set lowpass attack", + example: "0.01 lpa", + compile: Param, + varargs: false, + }, + Word { + name: "lpd", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set lowpass decay", + example: "0.1 lpd", + compile: Param, + varargs: false, + }, + Word { + name: "lps", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set lowpass sustain", + example: "0.5 lps", + compile: Param, + varargs: false, + }, + Word { + name: "lpr", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set lowpass release", + example: "0.3 lpr", + compile: Param, + varargs: false, + }, + Word { + name: "hpf", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set highpass frequency", + example: "100 hpf", + compile: Param, + varargs: false, + }, + Word { + name: "hpq", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set highpass resonance", + example: "0.5 hpq", + compile: Param, + varargs: false, + }, + Word { + name: "hpe", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set highpass envelope", + example: "0.5 hpe", + compile: Param, + varargs: false, + }, + Word { + name: "hpa", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set highpass attack", + example: "0.01 hpa", + compile: Param, + varargs: false, + }, + Word { + name: "hpd", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set highpass decay", + example: "0.1 hpd", + compile: Param, + varargs: false, + }, + Word { + name: "hps", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set highpass sustain", + example: "0.5 hps", + compile: Param, + varargs: false, + }, + Word { + name: "hpr", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set highpass release", + example: "0.3 hpr", + compile: Param, + varargs: false, + }, + Word { + name: "bpf", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set bandpass frequency", + example: "1000 bpf", + compile: Param, + varargs: false, + }, + Word { + name: "bpq", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set bandpass resonance", + example: "0.5 bpq", + compile: Param, + varargs: false, + }, + Word { + name: "bpe", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set bandpass envelope", + example: "0.5 bpe", + compile: Param, + varargs: false, + }, + Word { + name: "bpa", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set bandpass attack", + example: "0.01 bpa", + compile: Param, + varargs: false, + }, + Word { + name: "bpd", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set bandpass decay", + example: "0.1 bpd", + compile: Param, + varargs: false, + }, + Word { + name: "bps", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set bandpass sustain", + example: "0.5 bps", + compile: Param, + varargs: false, + }, + Word { + name: "bpr", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set bandpass release", + example: "0.3 bpr", + compile: Param, + varargs: false, + }, + Word { + name: "llpf", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set ladder lowpass frequency", + example: "2000 llpf", + compile: Param, + varargs: false, + }, + Word { + name: "llpq", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set ladder lowpass resonance", + example: "0.5 llpq", + compile: Param, + varargs: false, + }, + Word { + name: "lhpf", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set ladder highpass frequency", + example: "100 lhpf", + compile: Param, + varargs: false, + }, + Word { + name: "lhpq", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set ladder highpass resonance", + example: "0.5 lhpq", + compile: Param, + varargs: false, + }, + Word { + name: "lbpf", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set ladder bandpass frequency", + example: "1000 lbpf", + compile: Param, + varargs: false, + }, + Word { + name: "lbpq", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set ladder bandpass resonance", + example: "0.5 lbpq", + compile: Param, + varargs: false, + }, + Word { + name: "ftype", + aliases: &[], + category: "Filter", + stack: "(n --)", + desc: "Set filter type", + example: "1 ftype", + compile: Param, + varargs: false, + }, + Word { + name: "eqlo", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set low shelf gain (dB)", + example: "3 eqlo", + compile: Param, + varargs: false, + }, + Word { + name: "eqmid", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set mid peak gain (dB)", + example: "-2 eqmid", + compile: Param, + varargs: false, + }, + Word { + name: "eqhi", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set high shelf gain (dB)", + example: "1 eqhi", + compile: Param, + varargs: false, + }, + Word { + name: "tilt", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set tilt EQ (-1 dark, 1 bright)", + example: "-0.5 tilt", + compile: Param, + varargs: false, + }, + Word { + name: "comb", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set comb filter mix", + example: "0.5 comb", + compile: Param, + varargs: false, + }, + Word { + name: "combfreq", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set comb frequency", + example: "200 combfreq", + compile: Param, + varargs: false, + }, + Word { + name: "combfeedback", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set comb feedback", + example: "0.5 combfeedback", + compile: Param, + varargs: false, + }, + Word { + name: "combdamp", + aliases: &[], + category: "Filter", + stack: "(f --)", + desc: "Set comb damping", + example: "0.5 combdamp", + compile: Param, + varargs: false, + }, + // Reverb + Word { + name: "verb", + aliases: &[], + category: "Reverb", + stack: "(f --)", + desc: "Set reverb mix", + example: "0.3 verb", + compile: Param, + varargs: false, + }, + Word { + name: "verbdecay", + aliases: &[], + category: "Reverb", + stack: "(f --)", + desc: "Set reverb decay", + example: "2 verbdecay", + compile: Param, + varargs: false, + }, + Word { + name: "verbdamp", + aliases: &[], + category: "Reverb", + stack: "(f --)", + desc: "Set reverb damping", + example: "0.5 verbdamp", + compile: Param, + varargs: false, + }, + Word { + name: "verbpredelay", + aliases: &[], + category: "Reverb", + stack: "(f --)", + desc: "Set reverb predelay", + example: "0.02 verbpredelay", + compile: Param, + varargs: false, + }, + Word { + name: "verbdiff", + aliases: &[], + category: "Reverb", + stack: "(f --)", + desc: "Set reverb diffusion", + example: "0.7 verbdiff", + compile: Param, + varargs: false, + }, + Word { + name: "size", + aliases: &[], + category: "Reverb", + stack: "(f --)", + desc: "Set size", + example: "1 size", + compile: Param, + varargs: false, + }, + // Delay + Word { + name: "delay", + aliases: &[], + category: "Delay", + stack: "(f --)", + desc: "Set delay mix", + example: "0.3 delay", + compile: Param, + varargs: false, + }, + Word { + name: "delaytime", + aliases: &[], + category: "Delay", + stack: "(f --)", + desc: "Set delay time", + example: "0.25 delaytime", + compile: Param, + varargs: false, + }, + Word { + name: "delayfeedback", + aliases: &[], + category: "Delay", + stack: "(f --)", + desc: "Set delay feedback", + example: "0.5 delayfeedback", + compile: Param, + varargs: false, + }, + Word { + name: "delaytype", + aliases: &[], + category: "Delay", + stack: "(n --)", + desc: "Set delay type", + example: "1 delaytype", + compile: Param, + varargs: false, + }, + // Lo-fi + Word { + name: "crush", + aliases: &[], + category: "Lo-fi", + stack: "(f --)", + desc: "Set bit crush", + example: "8 crush", + compile: Param, + varargs: false, + }, + Word { + name: "fold", + aliases: &[], + category: "Lo-fi", + stack: "(f --)", + desc: "Set wave fold", + example: "2 fold", + compile: Param, + varargs: false, + }, + Word { + name: "wrap", + aliases: &[], + category: "Lo-fi", + stack: "(f --)", + desc: "Set wave wrap", + example: "0.5 wrap", + compile: Param, + varargs: false, + }, + Word { + name: "distort", + aliases: &[], + category: "Lo-fi", + stack: "(f --)", + desc: "Set distortion", + example: "0.5 distort", + compile: Param, + varargs: false, + }, + Word { + name: "distortvol", + aliases: &[], + category: "Lo-fi", + stack: "(f --)", + desc: "Set distortion volume", + example: "0.8 distortvol", + compile: Param, + varargs: false, + }, + // Stereo + Word { + name: "pan", + aliases: &[], + category: "Stereo", + stack: "(f --)", + desc: "Set pan (-1 to 1)", + example: "0.5 pan", + compile: Param, + varargs: false, + }, + Word { + name: "width", + aliases: &[], + category: "Stereo", + stack: "(f --)", + desc: "Set stereo width (0 mono, 1 normal, 2 wide)", + example: "0 width", + compile: Param, + varargs: false, + }, + Word { + name: "haas", + aliases: &[], + category: "Stereo", + stack: "(f --)", + desc: "Set Haas delay in ms (spatial placement)", + example: "8 haas", + compile: Param, + varargs: false, + }, + // Mod FX + Word { + name: "phaser", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set phaser rate", + example: "1 phaser", + compile: Param, + varargs: false, + }, + Word { + name: "phaserdepth", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set phaser depth", + example: "0.5 phaserdepth", + compile: Param, + varargs: false, + }, + Word { + name: "phasersweep", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set phaser sweep", + example: "0.5 phasersweep", + compile: Param, + varargs: false, + }, + Word { + name: "phasercenter", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set phaser center", + example: "1000 phasercenter", + compile: Param, + varargs: false, + }, + Word { + name: "flanger", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set flanger rate", + example: "0.5 flanger", + compile: Param, + varargs: false, + }, + Word { + name: "flangerdepth", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set flanger depth", + example: "0.5 flangerdepth", + compile: Param, + varargs: false, + }, + Word { + name: "flangerfeedback", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set flanger feedback", + example: "0.5 flangerfeedback", + compile: Param, + varargs: false, + }, + Word { + name: "chorus", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set chorus rate", + example: "1 chorus", + compile: Param, + varargs: false, + }, + Word { + name: "chorusdepth", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set chorus depth", + example: "0.5 chorusdepth", + compile: Param, + varargs: false, + }, + Word { + name: "chorusdelay", + aliases: &[], + category: "Mod FX", + stack: "(f --)", + desc: "Set chorus delay", + example: "0.02 chorusdelay", + compile: Param, + varargs: false, + }, +]; diff --git a/crates/forth/src/words/midi.rs b/crates/forth/src/words/midi.rs new file mode 100644 index 0000000..88fa338 --- /dev/null +++ b/crates/forth/src/words/midi.rs @@ -0,0 +1,135 @@ +use super::{Word, WordCompile::*}; + +// MIDI +pub(super) const WORDS: &[Word] = &[ + Word { + name: "chan", + aliases: &[], + category: "MIDI", + stack: "(n --)", + desc: "Set MIDI channel 1-16", + example: "1 chan", + compile: Param, + varargs: false, + }, + Word { + name: "ccnum", + aliases: &[], + category: "MIDI", + stack: "(n --)", + desc: "Set MIDI CC number 0-127", + example: "1 ccnum", + compile: Param, + varargs: false, + }, + Word { + name: "ccout", + aliases: &[], + category: "MIDI", + stack: "(n --)", + desc: "Set MIDI CC output value 0-127", + example: "64 ccout", + compile: Param, + varargs: false, + }, + Word { + name: "bend", + aliases: &[], + category: "MIDI", + stack: "(f --)", + desc: "Set pitch bend -1.0 to 1.0 (0 = center)", + example: "0.5 bend", + compile: Param, + varargs: false, + }, + Word { + name: "pressure", + aliases: &[], + category: "MIDI", + stack: "(n --)", + desc: "Set channel pressure (aftertouch) 0-127", + example: "64 pressure", + compile: Param, + varargs: false, + }, + Word { + name: "program", + aliases: &[], + category: "MIDI", + stack: "(n --)", + desc: "Set program change number 0-127", + example: "0 program", + compile: Param, + varargs: false, + }, + Word { + name: "m.", + aliases: &[], + category: "MIDI", + stack: "(--)", + desc: "Emit MIDI message from params (note/cc/bend/pressure/program)", + example: "60 note 100 velocity 1 chan m.", + compile: Simple, + varargs: false, + }, + Word { + name: "mclock", + aliases: &[], + category: "MIDI", + stack: "(--)", + desc: "Send MIDI clock pulse (24 per quarter note)", + example: "mclock", + compile: Simple, + varargs: false, + }, + Word { + name: "mstart", + aliases: &[], + category: "MIDI", + stack: "(--)", + desc: "Send MIDI start message", + example: "mstart", + compile: Simple, + varargs: false, + }, + Word { + name: "mstop", + aliases: &[], + category: "MIDI", + stack: "(--)", + desc: "Send MIDI stop message", + example: "mstop", + compile: Simple, + varargs: false, + }, + Word { + name: "mcont", + aliases: &[], + category: "MIDI", + stack: "(--)", + desc: "Send MIDI continue message", + example: "mcont", + compile: Simple, + varargs: false, + }, + Word { + name: "ccval", + aliases: &[], + category: "MIDI", + stack: "(cc chan -- val)", + desc: "Read CC value 0-127 from MIDI input (uses dev param for device)", + example: "1 1 ccval", + compile: Simple, + varargs: false, + }, + Word { + name: "dev", + aliases: &[], + category: "MIDI", + stack: "(n --)", + desc: "Set MIDI device slot 0-3 for output/input", + example: "1 dev 60 note m.", + compile: Param, + varargs: false, + }, +]; diff --git a/crates/forth/src/words/mod.rs b/crates/forth/src/words/mod.rs new file mode 100644 index 0000000..bb16323 --- /dev/null +++ b/crates/forth/src/words/mod.rs @@ -0,0 +1,58 @@ +mod compile; +mod core; +mod effects; +mod midi; +mod music; +mod sequencing; +mod sound; + +use std::collections::HashMap; +use std::sync::LazyLock; + +pub(crate) use compile::compile_word; + +#[derive(Clone, Copy)] +pub enum WordCompile { + Simple, + Context(&'static str), + Param, + Probability(f64), +} + +#[derive(Clone, Copy)] +pub struct Word { + pub name: &'static str, + pub aliases: &'static [&'static str], + pub category: &'static str, + pub stack: &'static str, + pub desc: &'static str, + pub example: &'static str, + pub compile: WordCompile, + pub varargs: bool, +} + +pub static WORDS: LazyLock> = LazyLock::new(|| { + let mut words = Vec::new(); + words.extend_from_slice(self::core::WORDS); + words.extend_from_slice(sound::WORDS); + words.extend_from_slice(effects::WORDS); + words.extend_from_slice(sequencing::WORDS); + words.extend_from_slice(music::WORDS); + words.extend_from_slice(midi::WORDS); + words +}); + +static WORD_MAP: LazyLock> = LazyLock::new(|| { + let mut map = HashMap::with_capacity(WORDS.len() * 2); + for word in WORDS.iter() { + map.insert(word.name, word); + for alias in word.aliases { + map.insert(alias, word); + } + } + map +}); + +pub fn lookup_word(name: &str) -> Option<&'static Word> { + WORD_MAP.get(name).copied() +} diff --git a/crates/forth/src/words/music.rs b/crates/forth/src/words/music.rs new file mode 100644 index 0000000..b1e64d1 --- /dev/null +++ b/crates/forth/src/words/music.rs @@ -0,0 +1,312 @@ +use super::{Word, WordCompile::*}; + +// Music, Chord +pub(super) const WORDS: &[Word] = &[ + // Music + Word { + name: "mtof", + aliases: &[], + category: "Music", + stack: "(midi -- hz)", + desc: "MIDI note to frequency", + example: "69 mtof => 440.0", + compile: Simple, + varargs: false, + }, + Word { + name: "ftom", + aliases: &[], + category: "Music", + stack: "(hz -- midi)", + desc: "Frequency to MIDI note", + example: "440 ftom => 69.0", + compile: Simple, + varargs: false, + }, + // Chords - Triads + Word { + name: "maj", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth)", + desc: "Major triad", + example: "c4 maj => 60 64 67", + compile: Simple, + varargs: true, + }, + Word { + name: "m", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth)", + desc: "Minor triad", + example: "c4 m => 60 63 67", + compile: Simple, + varargs: true, + }, + Word { + name: "dim", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth)", + desc: "Diminished triad", + example: "c4 dim => 60 63 66", + compile: Simple, + varargs: true, + }, + Word { + name: "aug", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth)", + desc: "Augmented triad", + example: "c4 aug => 60 64 68", + compile: Simple, + varargs: true, + }, + Word { + name: "sus2", + aliases: &[], + category: "Chord", + stack: "(root -- root second fifth)", + desc: "Suspended 2nd", + example: "c4 sus2 => 60 62 67", + compile: Simple, + varargs: true, + }, + Word { + name: "sus4", + aliases: &[], + category: "Chord", + stack: "(root -- root fourth fifth)", + desc: "Suspended 4th", + example: "c4 sus4 => 60 65 67", + compile: Simple, + varargs: true, + }, + // Chords - Seventh + Word { + name: "maj7", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh)", + desc: "Major 7th", + example: "c4 maj7 => 60 64 67 71", + compile: Simple, + varargs: true, + }, + Word { + name: "min7", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh)", + desc: "Minor 7th", + example: "c4 min7 => 60 63 67 70", + compile: Simple, + varargs: true, + }, + Word { + name: "dom7", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh)", + desc: "Dominant 7th", + example: "c4 dom7 => 60 64 67 70", + compile: Simple, + varargs: true, + }, + Word { + name: "dim7", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh)", + desc: "Diminished 7th", + example: "c4 dim7 => 60 63 66 69", + compile: Simple, + varargs: true, + }, + Word { + name: "m7b5", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh)", + desc: "Half-diminished (min7b5)", + example: "c4 m7b5 => 60 63 66 70", + compile: Simple, + varargs: true, + }, + Word { + name: "minmaj7", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh)", + desc: "Minor-major 7th", + example: "c4 minmaj7 => 60 63 67 71", + compile: Simple, + varargs: true, + }, + Word { + name: "aug7", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh)", + desc: "Augmented 7th", + example: "c4 aug7 => 60 64 68 70", + compile: Simple, + varargs: true, + }, + // Chords - Sixth + Word { + name: "maj6", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth sixth)", + desc: "Major 6th", + example: "c4 maj6 => 60 64 67 69", + compile: Simple, + varargs: true, + }, + Word { + name: "min6", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth sixth)", + desc: "Minor 6th", + example: "c4 min6 => 60 63 67 69", + compile: Simple, + varargs: true, + }, + // Chords - Extended + Word { + name: "dom9", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh ninth)", + desc: "Dominant 9th", + example: "c4 dom9 => 60 64 67 70 74", + compile: Simple, + varargs: true, + }, + Word { + name: "maj9", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh ninth)", + desc: "Major 9th", + example: "c4 maj9 => 60 64 67 71 74", + compile: Simple, + varargs: true, + }, + Word { + name: "min9", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh ninth)", + desc: "Minor 9th", + example: "c4 min9 => 60 63 67 70 74", + compile: Simple, + varargs: true, + }, + Word { + name: "dom11", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh ninth eleventh)", + desc: "Dominant 11th", + example: "c4 dom11 => 60 64 67 70 74 77", + compile: Simple, + varargs: true, + }, + Word { + name: "min11", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh ninth eleventh)", + desc: "Minor 11th", + example: "c4 min11 => 60 63 67 70 74 77", + compile: Simple, + varargs: true, + }, + Word { + name: "dom13", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh ninth thirteenth)", + desc: "Dominant 13th", + example: "c4 dom13 => 60 64 67 70 74 81", + compile: Simple, + varargs: true, + }, + // Chords - Add + Word { + name: "add9", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth ninth)", + desc: "Major add 9", + example: "c4 add9 => 60 64 67 74", + compile: Simple, + varargs: true, + }, + Word { + name: "add11", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth eleventh)", + desc: "Major add 11", + example: "c4 add11 => 60 64 67 77", + compile: Simple, + varargs: true, + }, + Word { + name: "madd9", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth ninth)", + desc: "Minor add 9", + example: "c4 madd9 => 60 63 67 74", + compile: Simple, + varargs: true, + }, + // Chords - Altered dominants + Word { + name: "dom7b9", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh flatninth)", + desc: "7th flat 9", + example: "c4 dom7b9 => 60 64 67 70 73", + compile: Simple, + varargs: true, + }, + Word { + name: "dom7s9", + aliases: &[], + category: "Chord", + stack: "(root -- root third fifth seventh sharpninth)", + desc: "7th sharp 9 (Hendrix chord)", + example: "c4 dom7s9 => 60 64 67 70 75", + compile: Simple, + varargs: true, + }, + Word { + name: "dom7b5", + aliases: &[], + category: "Chord", + stack: "(root -- root third flatfifth seventh)", + desc: "7th flat 5", + example: "c4 dom7b5 => 60 64 66 70", + compile: Simple, + varargs: true, + }, + Word { + name: "dom7s5", + aliases: &[], + category: "Chord", + stack: "(root -- root third sharpfifth seventh)", + desc: "7th sharp 5", + example: "c4 dom7s5 => 60 64 68 70", + compile: Simple, + varargs: true, + }, +]; diff --git a/crates/forth/src/words/sequencing.rs b/crates/forth/src/words/sequencing.rs new file mode 100644 index 0000000..ec009d6 --- /dev/null +++ b/crates/forth/src/words/sequencing.rs @@ -0,0 +1,413 @@ +use super::{Word, WordCompile::*}; + +// Time, Context, Probability, Generator, Desktop +pub(super) const WORDS: &[Word] = &[ + // Probability + Word { + name: "rand", + aliases: &[], + category: "Probability", + stack: "(min max -- n|f)", + desc: "Random in range. Int if both args are int, float otherwise", + example: "1 6 rand => 4 | 0.0 1.0 rand => 0.42", + compile: Simple, + varargs: false, + }, + Word { + name: "exprand", + aliases: &[], + category: "Probability", + stack: "(lo hi -- f)", + desc: "Exponential random biased toward lo. Both args must be positive", + example: "1.0 100.0 exprand => 3.7", + compile: Simple, + varargs: false, + }, + Word { + name: "logrand", + aliases: &[], + category: "Probability", + stack: "(lo hi -- f)", + desc: "Exponential random biased toward hi. Both args must be positive", + example: "1.0 100.0 logrand => 87.2", + compile: Simple, + varargs: false, + }, + Word { + name: "seed", + aliases: &[], + category: "Probability", + stack: "(n --)", + desc: "Set random seed", + example: "12345 seed", + compile: Simple, + varargs: false, + }, + Word { + name: "coin", + aliases: &[], + category: "Probability", + stack: "(-- bool)", + desc: "50/50 random boolean", + example: "coin => 0 or 1", + compile: Simple, + varargs: false, + }, + Word { + name: "chance", + aliases: &[], + category: "Probability", + stack: "(quot prob --)", + desc: "Execute quotation with probability (0.0-1.0)", + example: "{ 2 distort } 0.75 chance", + compile: Simple, + varargs: false, + }, + Word { + name: "prob", + aliases: &[], + category: "Probability", + stack: "(quot pct --)", + desc: "Execute quotation with probability (0-100)", + example: "{ 2 distort } 75 prob", + compile: Simple, + varargs: false, + }, + Word { + name: "choose", + aliases: &[], + category: "Probability", + stack: "(..n n -- val)", + desc: "Random pick from n items", + example: "1 2 3 3 choose", + compile: Simple, + varargs: true, + }, + Word { + name: "cycle", + aliases: &[], + category: "Probability", + stack: "(v1..vn n -- selected)", + desc: "Cycle through n items by step runs", + example: "60 64 67 3 cycle", + compile: Simple, + varargs: true, + }, + Word { + name: "pcycle", + aliases: &[], + category: "Probability", + stack: "(v1..vn n -- selected)", + desc: "Cycle through n items by pattern iteration", + example: "60 64 67 3 pcycle", + compile: Simple, + varargs: true, + }, + Word { + name: "always", + aliases: &[], + category: "Probability", + stack: "(quot --)", + desc: "Always execute quotation", + example: "{ 2 distort } always", + compile: Probability(1.0), + varargs: false, + }, + Word { + name: "never", + aliases: &[], + category: "Probability", + stack: "(quot --)", + desc: "Never execute quotation", + example: "{ 2 distort } never", + compile: Probability(0.0), + varargs: false, + }, + Word { + name: "often", + aliases: &[], + category: "Probability", + stack: "(quot --)", + desc: "Execute quotation 75% of the time", + example: "{ 2 distort } often", + compile: Probability(0.75), + varargs: false, + }, + Word { + name: "sometimes", + aliases: &[], + category: "Probability", + stack: "(quot --)", + desc: "Execute quotation 50% of the time", + example: "{ 2 distort } sometimes", + compile: Probability(0.5), + varargs: false, + }, + Word { + name: "rarely", + aliases: &[], + category: "Probability", + stack: "(quot --)", + desc: "Execute quotation 25% of the time", + example: "{ 2 distort } rarely", + compile: Probability(0.25), + varargs: false, + }, + Word { + name: "almostNever", + aliases: &[], + category: "Probability", + stack: "(quot --)", + desc: "Execute quotation 10% of the time", + example: "{ 2 distort } almostNever", + compile: Probability(0.1), + varargs: false, + }, + Word { + name: "almostAlways", + aliases: &[], + category: "Probability", + stack: "(quot --)", + desc: "Execute quotation 90% of the time", + example: "{ 2 distort } almostAlways", + compile: Probability(0.9), + varargs: false, + }, + // Time + Word { + name: "every", + aliases: &[], + category: "Time", + stack: "(n -- bool)", + desc: "True every nth iteration", + example: "4 every", + compile: Simple, + varargs: false, + }, + Word { + name: "loop", + aliases: &[], + category: "Time", + stack: "(n --)", + desc: "Fit sample to n beats", + example: "\"break\" s 4 loop @", + compile: Simple, + varargs: false, + }, + Word { + name: "tempo!", + aliases: &[], + category: "Time", + stack: "(bpm --)", + desc: "Set global tempo", + example: "140 tempo!", + compile: Simple, + varargs: false, + }, + Word { + name: "speed!", + aliases: &[], + category: "Time", + stack: "(multiplier --)", + desc: "Set pattern speed multiplier", + example: "2.0 speed!", + compile: Simple, + varargs: false, + }, + Word { + name: "chain", + aliases: &[], + category: "Time", + stack: "(bank pattern --)", + desc: "Chain to bank/pattern (1-indexed) when current pattern ends", + example: "1 4 chain", + compile: Simple, + varargs: false, + }, + Word { + name: "at", + aliases: &[], + category: "Time", + stack: "(v1..vn --)", + desc: "Set delta context for emit timing", + example: "0 0.5 at kick s . => emits at 0 and 0.5 of step", + compile: Simple, + varargs: true, + }, + // Context + Word { + name: "step", + aliases: &[], + category: "Context", + stack: "(-- n)", + desc: "Current step index", + example: "step => 0", + compile: Context("step"), + varargs: false, + }, + Word { + name: "beat", + aliases: &[], + category: "Context", + stack: "(-- f)", + desc: "Current beat position", + example: "beat => 4.5", + compile: Context("beat"), + varargs: false, + }, + Word { + name: "pattern", + aliases: &[], + category: "Context", + stack: "(-- n)", + desc: "Current pattern index", + example: "pattern => 0", + compile: Context("pattern"), + varargs: false, + }, + Word { + name: "pbank", + aliases: &[], + category: "Context", + stack: "(-- n)", + desc: "Current pattern's bank index", + example: "pbank => 0", + compile: Context("bank"), + varargs: false, + }, + Word { + name: "tempo", + aliases: &[], + category: "Context", + stack: "(-- f)", + desc: "Current BPM", + example: "tempo => 120.0", + compile: Context("tempo"), + varargs: false, + }, + Word { + name: "phase", + aliases: &[], + category: "Context", + stack: "(-- f)", + desc: "Phase in bar (0-1)", + example: "phase => 0.25", + compile: Context("phase"), + varargs: false, + }, + Word { + name: "slot", + aliases: &[], + category: "Context", + stack: "(-- n)", + desc: "Current slot number", + example: "slot => 0", + compile: Context("slot"), + varargs: false, + }, + Word { + name: "runs", + aliases: &[], + category: "Context", + stack: "(-- n)", + desc: "Times this step ran", + example: "runs => 3", + compile: Context("runs"), + varargs: false, + }, + Word { + name: "iter", + aliases: &[], + category: "Context", + stack: "(-- n)", + desc: "Pattern iteration count", + example: "iter => 2", + compile: Context("iter"), + varargs: false, + }, + Word { + name: "stepdur", + aliases: &[], + category: "Context", + stack: "(-- f)", + desc: "Step duration in seconds", + example: "stepdur => 0.125", + compile: Context("stepdur"), + varargs: false, + }, + Word { + name: "fill", + aliases: &[], + category: "Context", + stack: "(-- bool)", + desc: "True when fill is on (f key)", + example: "\"snare\" s . fill ?", + compile: Context("fill"), + varargs: false, + }, + // Desktop + #[cfg(feature = "desktop")] + Word { + name: "mx", + aliases: &[], + category: "Desktop", + stack: "(-- x)", + desc: "Normalized mouse X position (0-1)", + example: "mx 440 880 range freq", + compile: Context("mx"), + varargs: false, + }, + #[cfg(feature = "desktop")] + Word { + name: "my", + aliases: &[], + category: "Desktop", + stack: "(-- y)", + desc: "Normalized mouse Y position (0-1)", + example: "my 0.1 0.9 range gain", + compile: Context("my"), + varargs: false, + }, + #[cfg(feature = "desktop")] + Word { + name: "mdown", + aliases: &[], + category: "Desktop", + stack: "(-- bool)", + desc: "1 when mouse button held, 0 otherwise", + example: "mdown { \"crash\" s . } ?", + compile: Context("mdown"), + varargs: false, + }, + // Generator + Word { + name: "..", + aliases: &[], + category: "Generator", + stack: "(start end -- start start+1 ... end)", + desc: "Push arithmetic sequence from start to end", + example: "1 4 .. => 1 2 3 4", + compile: Simple, + varargs: false, + }, + Word { + name: "gen", + aliases: &[], + category: "Generator", + stack: "(quot n -- results...)", + desc: "Execute quotation n times, push all results", + example: "{ 1 6 rand } 4 gen => 4 random values", + compile: Simple, + varargs: true, + }, + Word { + name: "geom..", + aliases: &[], + category: "Generator", + stack: "(start ratio count -- start start*r start*r^2 ...)", + desc: "Push geometric sequence", + example: "1 2 4 geom.. => 1 2 4 8", + compile: Simple, + varargs: false, + }, +]; diff --git a/crates/forth/src/words/sound.rs b/crates/forth/src/words/sound.rs new file mode 100644 index 0000000..b5e7661 --- /dev/null +++ b/crates/forth/src/words/sound.rs @@ -0,0 +1,622 @@ +use super::{Word, WordCompile::*}; + +// Sound, Oscillator, Sample, Wavetable, FM, Modulation, LFO +pub(super) const WORDS: &[Word] = &[ + // Sound + Word { + name: "sound", + aliases: &["s"], + category: "Sound", + stack: "(name --)", + desc: "Begin sound command", + example: "\"kick\" sound", + compile: Simple, + varargs: false, + }, + Word { + name: ".", + aliases: &[], + category: "Sound", + stack: "(--)", + desc: "Emit current sound", + example: "\"kick\" s . . . .", + compile: Simple, + varargs: false, + }, + Word { + name: "clear", + aliases: &[], + category: "Sound", + stack: "(--)", + desc: "Clear sound register (sound and all params)", + example: "\"kick\" s 0.5 gain . clear \"hat\" s .", + compile: Simple, + varargs: false, + }, + // Sample + Word { + name: "bank", + aliases: &[], + category: "Sample", + stack: "(str --)", + desc: "Set sample bank suffix", + example: "\"a\" bank", + compile: Param, + varargs: false, + }, + Word { + name: "time", + aliases: &[], + category: "Sample", + stack: "(f --)", + desc: "Set time offset", + example: "0.1 time", + compile: Param, + varargs: false, + }, + Word { + name: "repeat", + aliases: &[], + category: "Sample", + stack: "(n --)", + desc: "Set repeat count", + example: "4 repeat", + compile: Param, + varargs: false, + }, + Word { + name: "dur", + aliases: &[], + category: "Sample", + stack: "(f --)", + desc: "Set duration", + example: "0.5 dur", + compile: Param, + varargs: false, + }, + Word { + name: "gate", + aliases: &[], + category: "Sample", + stack: "(f --)", + desc: "Set gate time", + example: "0.8 gate", + compile: Param, + varargs: false, + }, + Word { + name: "speed", + aliases: &[], + category: "Sample", + stack: "(f --)", + desc: "Set playback speed", + example: "1.5 speed", + compile: Param, + varargs: false, + }, + Word { + name: "begin", + aliases: &[], + category: "Sample", + stack: "(f --)", + desc: "Set sample start (0-1)", + example: "0.25 begin", + compile: Param, + varargs: false, + }, + Word { + name: "end", + aliases: &[], + category: "Sample", + stack: "(f --)", + desc: "Set sample end (0-1)", + example: "0.75 end", + compile: Param, + varargs: false, + }, + Word { + name: "voice", + aliases: &[], + category: "Sample", + stack: "(n --)", + desc: "Set voice number", + example: "1 voice", + compile: Param, + varargs: false, + }, + Word { + name: "orbit", + aliases: &[], + category: "Sample", + stack: "(n --)", + desc: "Set orbit/bus", + example: "0 orbit", + compile: Param, + varargs: false, + }, + Word { + name: "n", + aliases: &[], + category: "Sample", + stack: "(n --)", + desc: "Set sample number", + example: "0 n", + compile: Param, + varargs: false, + }, + Word { + name: "cut", + aliases: &[], + category: "Sample", + stack: "(n --)", + desc: "Set cut group", + example: "1 cut", + compile: Param, + varargs: false, + }, + Word { + name: "reset", + aliases: &[], + category: "Sample", + stack: "(n --)", + desc: "Reset parameter", + example: "1 reset", + compile: Param, + varargs: false, + }, + // Oscillator + Word { + name: "freq", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set frequency (Hz)", + example: "440 freq", + compile: Param, + varargs: false, + }, + Word { + name: "detune", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set detune amount", + example: "0.01 detune", + compile: Param, + varargs: false, + }, + Word { + name: "glide", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set glide/portamento", + example: "0.1 glide", + compile: Param, + varargs: false, + }, + Word { + name: "pw", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set pulse width", + example: "0.5 pw", + compile: Param, + varargs: false, + }, + Word { + name: "spread", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set stereo spread", + example: "0.5 spread", + compile: Param, + varargs: false, + }, + Word { + name: "mult", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set multiplier", + example: "2 mult", + compile: Param, + varargs: false, + }, + Word { + name: "warp", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set warp amount", + example: "0.5 warp", + compile: Param, + varargs: false, + }, + Word { + name: "mirror", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set mirror", + example: "1 mirror", + compile: Param, + varargs: false, + }, + Word { + name: "harmonics", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set harmonics (mutable only)", + example: "4 harmonics", + compile: Param, + varargs: false, + }, + Word { + name: "timbre", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set timbre (mutable only)", + example: "0.5 timbre", + compile: Param, + varargs: false, + }, + Word { + name: "morph", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set morph (mutable only)", + example: "0.5 morph", + compile: Param, + varargs: false, + }, + Word { + name: "coarse", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set coarse tune", + example: "12 coarse", + compile: Param, + varargs: false, + }, + Word { + name: "sub", + aliases: &[], + category: "Oscillator", + stack: "(f --)", + desc: "Set sub oscillator level", + example: "0.5 sub", + compile: Param, + varargs: false, + }, + Word { + name: "suboct", + aliases: &[], + category: "Oscillator", + stack: "(n --)", + desc: "Set sub oscillator octave", + example: "2 suboct", + compile: Param, + varargs: false, + }, + Word { + name: "subwave", + aliases: &[], + category: "Oscillator", + stack: "(n --)", + desc: "Set sub oscillator waveform", + example: "1 subwave", + compile: Param, + varargs: false, + }, + Word { + name: "note", + aliases: &[], + category: "Oscillator", + stack: "(n --)", + desc: "Set MIDI note", + example: "60 note", + compile: Param, + varargs: false, + }, + // Wavetable + Word { + name: "scan", + aliases: &[], + category: "Wavetable", + stack: "(f --)", + desc: "Set wavetable scan position (0-1)", + example: "0.5 scan", + compile: Param, + varargs: false, + }, + Word { + name: "wtlen", + aliases: &[], + category: "Wavetable", + stack: "(n --)", + desc: "Set wavetable cycle length in samples", + example: "2048 wtlen", + compile: Param, + varargs: false, + }, + Word { + name: "scanlfo", + aliases: &[], + category: "Wavetable", + stack: "(f --)", + desc: "Set scan LFO rate (Hz)", + example: "0.2 scanlfo", + compile: Param, + varargs: false, + }, + Word { + name: "scandepth", + aliases: &[], + category: "Wavetable", + stack: "(f --)", + desc: "Set scan LFO depth (0-1)", + example: "0.4 scandepth", + compile: Param, + varargs: false, + }, + Word { + name: "scanshape", + aliases: &[], + category: "Wavetable", + stack: "(s --)", + desc: "Set scan LFO shape (sine/tri/saw/square/sh)", + example: "\"tri\" scanshape", + compile: Param, + varargs: false, + }, + // FM + Word { + name: "fm", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM frequency", + example: "200 fm", + compile: Param, + varargs: false, + }, + Word { + name: "fmh", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM harmonic ratio", + example: "2 fmh", + compile: Param, + varargs: false, + }, + Word { + name: "fmshape", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM shape", + example: "0 fmshape", + compile: Param, + varargs: false, + }, + Word { + name: "fme", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM envelope", + example: "0.5 fme", + compile: Param, + varargs: false, + }, + Word { + name: "fma", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM attack", + example: "0.01 fma", + compile: Param, + varargs: false, + }, + Word { + name: "fmd", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM decay", + example: "0.1 fmd", + compile: Param, + varargs: false, + }, + Word { + name: "fms", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM sustain", + example: "0.5 fms", + compile: Param, + varargs: false, + }, + Word { + name: "fmr", + aliases: &[], + category: "FM", + stack: "(f --)", + desc: "Set FM release", + example: "0.1 fmr", + compile: Param, + varargs: false, + }, + // Modulation + Word { + name: "vib", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set vibrato rate", + example: "5 vib", + compile: Param, + varargs: false, + }, + Word { + name: "vibmod", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set vibrato depth", + example: "0.5 vibmod", + compile: Param, + varargs: false, + }, + Word { + name: "vibshape", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set vibrato shape", + example: "0 vibshape", + compile: Param, + varargs: false, + }, + Word { + name: "am", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set AM frequency", + example: "10 am", + compile: Param, + varargs: false, + }, + Word { + name: "amdepth", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set AM depth", + example: "0.5 amdepth", + compile: Param, + varargs: false, + }, + Word { + name: "amshape", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set AM shape", + example: "0 amshape", + compile: Param, + varargs: false, + }, + Word { + name: "rm", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set RM frequency", + example: "100 rm", + compile: Param, + varargs: false, + }, + Word { + name: "rmdepth", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set RM depth", + example: "0.5 rmdepth", + compile: Param, + varargs: false, + }, + Word { + name: "rmshape", + aliases: &[], + category: "Modulation", + stack: "(f --)", + desc: "Set RM shape", + example: "0 rmshape", + compile: Param, + varargs: false, + }, + // LFO + Word { + name: "ramp", + aliases: &[], + category: "LFO", + stack: "(freq curve -- val)", + desc: "Ramp [0,1]: fract(freq*beat)^curve", + example: "0.25 2.0 ramp", + compile: Simple, + varargs: false, + }, + Word { + name: "range", + aliases: &[], + category: "LFO", + stack: "(val min max -- scaled)", + desc: "Scale [0,1] to [min,max]", + example: "0.5 200 800 range => 500", + compile: Simple, + varargs: false, + }, + Word { + name: "linramp", + aliases: &[], + category: "LFO", + stack: "(freq -- val)", + desc: "Linear ramp (curve=1)", + example: "1.0 linramp", + compile: Simple, + varargs: false, + }, + Word { + name: "expramp", + aliases: &[], + category: "LFO", + stack: "(freq -- val)", + desc: "Exponential ramp (curve=3)", + example: "0.25 expramp", + compile: Simple, + varargs: false, + }, + Word { + name: "logramp", + aliases: &[], + category: "LFO", + stack: "(freq -- val)", + desc: "Logarithmic ramp (curve=0.3)", + example: "2.0 logramp", + compile: Simple, + varargs: false, + }, + Word { + name: "triangle", + aliases: &[], + category: "LFO", + stack: "(freq -- val)", + desc: "Triangle wave [0,1]: 0→1→0", + example: "0.5 triangle", + compile: Simple, + varargs: false, + }, + Word { + name: "perlin", + aliases: &[], + category: "LFO", + stack: "(freq -- val)", + desc: "Perlin noise [0,1] sampled at freq*beat", + example: "0.25 perlin", + compile: Simple, + varargs: false, + }, +]; diff --git a/tests/forth/temporal.rs b/tests/forth/temporal.rs index 29f58a4..276d4c0 100644 --- a/tests/forth/temporal.rs +++ b/tests/forth/temporal.rs @@ -91,7 +91,7 @@ fn alternating_sounds() { fn dur_is_step_duration() { let outputs = expect_outputs(r#""kick" s ."#, 1); let durs = get_durs(&outputs); - assert!(approx_eq(durs[0], 0.125), "dur should be step_duration (0.125), got {}", durs[0]); + assert!(approx_eq(durs[0], 0.5), "dur should be 4 * step_duration (0.5), got {}", durs[0]); } #[test] @@ -181,6 +181,15 @@ fn at_reset_with_zero() { assert_eq!(sounds, vec!["kick", "kick", "hat"]); } +#[test] +fn clear_resets_at_deltas() { + let outputs = expect_outputs(r#"0 0.5 at "kick" s . clear "hat" s ."#, 3); + let sounds = get_sounds(&outputs); + assert_eq!(sounds, vec!["kick", "kick", "hat"]); + let deltas = get_deltas(&outputs); + assert!(approx_eq(deltas[2], 0.0), "after clear, hat should emit at delta 0, got {}", deltas[2]); +} + #[test] fn at_records_selected_spans() { use cagire::forth::ExecutionTrace;