Feat: documentation, UI/UX

This commit is contained in:
2026-03-01 19:09:52 +01:00
parent ecb559e556
commit db44f9b98e
57 changed files with 1531 additions and 615 deletions

View File

@@ -1,13 +1,15 @@
# About Forth
Forth is a _stack-based_ programming language created by Charles H. Moore in the early 1970s. It was designed with simplicity, directness, and interactive exploration in mind. Forth has been used for scientific work and embedded systems: it controlled telescopes and even ran on hardware aboard space missions. It evolved into many implementations targeting various architectures, but none of them really caught on. Nonetheless, the ideas behind Forth continue to attract people from very different, often unrelated fields. Today, Forth languages are used by hackers and artists for their unconventional nature. Forth is simple, direct, and beautiful to implement. Forth is an elegant, minimal language, easy to understand, extend, and tailor to a specific task. The Forth we use in Cagire is specialized in making live music. It is used as a DSL: a _Domain Specific Language_.
Forth is a _stack-based_ programming language created by Charles H. Moore in the early 1970s. It was designed with simplicity, directness, and interactive exploration in mind. Forth has been used for scientific work and embedded systems: it controlled telescopes and even ran on hardware aboard space missions. It evolved into many implementations targeting various architectures, but none of them really caught on. Nonetheless, the ideas behind Forth continue to attract people from very different, often unrelated fields. Today, Forth languages are used by hackers and artists for their unconventional nature. Forth is simple, direct, and beautiful to implement. Forth is an elegant, minimal language, easy to understand, extend, and tailor to a specific task. The Forth we use in Cagire is specialized in making live music. It is used as a DSL: a _Domain Specific Language_.
**TLDR:** Forth is a really nice language to play music with.
## Why Forth?
Most programming languages rely on a complex syntax of `variables`, `expressions` and `statements` like `x = 3 + 4` or `do_something(()=>bob(4))`. Forth works differently. It has almost no syntax at all. Instead, you push values onto a `stack` and apply `words` that transform them:
```forth
3 4 +
3 4 + print
```
The program above leaves the number `7` on the stack. There are no variables, no parentheses, no syntax to remember. You just end up with words and numbers separated by spaces. For live coding music, this directness is quite exciting. All you do is think in terms of transformations and add things to the stack: take a note, shift it up, add reverb, play it.
@@ -20,6 +22,7 @@ The stack is where values live. When you type a number, it goes on the stack. Wh
3 ;; stack: 3
4 ;; stack: 3 4
+ ;; stack: 7
print
```
The stack is `last-in, first-out`. The most recent value is always on top. This means that it's often better to read Forth programs from right to left, bottom to top.
@@ -38,7 +41,7 @@ Words compose naturally on the stack. To double a number:
```forth
;; 3 3 +
3 dup +
3 dup + print
```
Forth has a large vocabulary, so Cagire includes a `Dictionary` directly in the application. You can also create your own words. They will work just like existing words. The only difference is that these words will not be included in the dictionary. There are good reasons to create new words on-the-fly:
@@ -58,13 +61,22 @@ Four basic types of values can live on the stack:
Floats can omit the leading zero: `.25` is the same as `0.25`, and `-.5` is `-0.5`.
Parentheses are ignored by the parser. You can use them freely for visual grouping without affecting execution:
Parentheses are used to "quote" a section of a program. The code inside does not run immediately — it is pushed onto the stack as a value. A quotation only runs when a consuming word decides to execute it. This is how conditionals and loops work:
```forth
(c4 note) (0.5 gain) "sine" s .
( 60 note 0.3 verb ) 1 ?
```
Quotations are special. They let you pass code around as a value. This is how conditionals and loops work. Don't worry about them for now — you'll learn how to use them later.
Here `?` pops the quotation and the condition. The code inside runs only when the condition is truthy. Words like `?`, `!?`, `times`, `cycle`, `choose`, `ifelse`, `every`, `chance`, and `apply` all consume quotations this way.
Because parentheses defer execution, wrapping code in `( ... )` without a consuming word means it never runs. Quotations are transparent to sound and parameter words — they stay on the stack untouched. This is a useful trick for temporarily disabling part of a step:
```forth
( 0.5 gain ) ;; this quotation is ignored
"kick" sound
0.3 decay
.
```
Any word that is not recognized as a built-in or a user definition becomes a string on the stack. This means `kick s` and `"kick" s` are equivalent. You only need quotes when the string contains spaces or when it conflicts with an existing word name.

108
docs/forth/brackets.md Normal file
View File

@@ -0,0 +1,108 @@
# Brackets
Cagire uses three bracket forms. Each one behaves differently.
## ( ... ) — Quotations
Parentheses create quotations: deferred code. The contents are not executed immediately — they are pushed onto the stack as a single value.
```forth
( dup + )
```
This pushes a block of code. You can store it in a variable, pass it to other words, or execute it later. Quotations are what make Cagire's control flow work.
### Words that consume quotations
Many built-in words expect a quotation on the stack:
| Word | Effect |
|------|--------|
| `?` | Execute if condition is truthy |
| `!?` | Execute if condition is falsy |
| `ifelse` | Choose between two quotations |
| `select` | Pick the nth quotation from a list |
| `apply` | Execute unconditionally |
| `times` | Loop n times |
| `cycle` / `pcycle` | Rotate through quotations |
| `choose` | Pick one at random |
| `every` | Execute on every nth iteration |
| `chance` / `prob` | Execute with probability |
| `bjork` / `pbjork` | Euclidean rhythm gate |
When a word like `cycle` or `choose` selects a quotation, it executes it. When it selects a plain value, it pushes it.
### Nesting
Quotations nest freely:
```forth
( ( c4 note ) ( e4 note ) coin ifelse ) 4 every
```
The outer quotation runs every 4th iteration. Inside, a coin flip picks the note.
### The mute trick
Wrapping code in a quotation without consuming it is a quick way to disable it:
```forth
( kick s . )
```
Nothing will execute this quotation — it just sits on the stack and gets discarded. Useful for temporarily silencing a line while editing.
## [ ... ] — Square Brackets
Square brackets execute their contents immediately, then push a count of how many values were produced. The values themselves stay on the stack.
```forth
[ 60 64 67 ]
```
After this runs, the stack holds `60 64 67 3` — three values plus the count `3`. This is useful with words that need to know how many items precede them:
```forth
[ 60 64 67 ] cycle note sine s .
```
The `cycle` word reads the count to know how many values to rotate through. Without brackets you would write `60 64 67 3 cycle` — the brackets save you from counting manually.
Square brackets work with any word that takes a count:
```forth
[ c4 e4 g4 ] choose note saw s . ;; random note from the list
[ 60 64 67 ] note sine s . ;; 3-note chord (note consumes all)
```
### Nesting
Square brackets can nest. Each pair produces its own count:
```forth
[ [ 60 64 67 ] cycle [ 0.3 0.5 0.8 ] cycle ] choose
```
### Expressions inside brackets
The contents are compiled and executed normally, so you can use any Forth code:
```forth
[ c4 c4 3 + c4 7 + ] note sine s . ;; root, minor third, fifth
```
## { ... } — Curly Braces
Curly braces are ignored by the compiler. They do nothing. Use them as a visual aid to group related code:
```forth
{ kick s } { 0.5 gain } { 0.3 verb } .
```
This compiles to exactly the same thing as:
```forth
kick s 0.5 gain 0.3 verb .
```
They can help readability in dense one-liners but have no semantic meaning.

View File

@@ -1,140 +1,143 @@
# Control Flow
Sometimes a step should behave differently depending on context — a coin flip, a fill, which iteration of the pattern is playing. Control flow words let you branch, choose, and repeat inside a step's script. Control structures are essential for programming and allow you to create complex and dynamic patterns.
Control flow in Cagire's Forth comes in two families. The first is compiled syntax — `if/then` and `case` — which the compiler handles directly as branch instructions. The second is quotation words — `?`, `!?`, `ifelse`, `select`, `apply` — which pop `( ... )` quotations from the stack and decide whether to run them. Probability and periodic execution (`chance`, `every`, `bjork`) are covered in the Randomness tutorial.
## if / else / then
## Branching with if / else / then
The simplest branch. Push a condition, then `if`:
Push a condition, then `if`. Everything between `if` and `then` runs only when the condition is truthy:
```forth
coin if 0.8 gain then
saw s c4 note .
;; degrade sound if true
coin if
7 crush
then
sine sound
c4 note
1 decay
.
```
The gain is applied if the coin flip is true. The sound will always plays. Add `else` for a two-way split:
The crush is applied on half the hits. The sound always plays. Add `else` for a two-way split:
```forth
coin if
c4 note
c5 note
else
c3 note
then
saw s 0.6 gain .
saw sound
0.3 verb
0.5 decay
0.6 gain
.
```
These are compiled directly into branch instructions. For that reason, these words will not appear in the dictionary.
These are compiled directly into branch instructions — they will not appear in the dictionary. This is a "low level" way to use conditionals in Cagire.
## ? and !?
When you already have a quotation, `?` executes it if the condition is truthy:
```forth
( 0.4 verb ) coin ?
saw s c4 note 0.5 gain . ;; reverb on half the hits
```
`!?` is the opposite — executes when falsy:
```forth
( 0.2 gain ) coin !?
saw s c4 note . ;; quiet on half the hits
```
These pair well with `chance`, `prob`, and the other probability words:
```forth
( 0.5 verb ) 0.3 chance ? ;; occasional reverb wash
( 12 + ) fill ? ;; octave up during fills
```
## ifelse
Two quotations, one condition. The true branch comes first:
```forth
( c3 note ) ( c4 note ) coin ifelse
saw s 0.6 gain . ;; bass or lead, coin flip
```
Reads naturally: "c3 or c4, depending on the coin."
```forth
( 0.8 gain ) ( 0.3 gain ) fill ifelse
tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise
```
## select
Choose the nth option from a list of quotations:
```forth
( c4 ) ( e4 ) ( g4 ) ( b4 ) iter 4 mod select
note sine s 0.5 decay .
```
Four notes cycling through a major seventh chord, one per pattern iteration. The index is 0-based.
## apply
When you have a quotation and want to execute it unconditionally, use `apply`:
```forth
( dup + ) apply ;; doubles the top value
```
This is simpler than `?` when there is no condition to check. It pops the quotation and runs it.
## case / of / endof / endcase
## Matching with case
For matching a value against several options. Cleaner than a chain of `if`s when you have more than two branches:
```forth
iter 4 mod case
1 8 rand 4 mod case
0 of c3 note endof
1 of e3 note endof
2 of g3 note endof
3 of a3 note endof
endcase
saw s 0.6 gain 800 lpf .
tri s
2 fm 0.99 fmh
0.6 gain 0.2 chorus
1 decay
800 lpf
.
```
A different root note each time the pattern loops.
The last line before `endcase` is the default — it runs when no `of` matched:
A different root note each time the pattern loops. The last line before `endcase` is the default — it runs when no `of` matched:
```forth
iter 3 mod case
0 of 0.9 gain endof
0.4 gain ;; default: quieter
0.4 gain
endcase
saw s c4 note .
saw s
.5 decay
c4 note
.
```
## times
Like `if/then`, `case` is compiled syntax and does not appear in the dictionary.
Repeat a quotation n times. The variable `@i` is automatically set to the current iteration index (starting from 0):
## Quotation Words
The remaining control flow words operate on quotations — `( ... )` blocks sitting on the stack. Each word pops one or more quotations and decides whether or how to execute them.
### ? and !?
`?` executes a quotation if the condition is truthy:
```forth
3 ( c4 @i 4 * + note ) times
sine s 0.4 gain 0.5 verb . ;; c4, e4, g#4 a chord
( 0.4 verb 6 crush ) coin ?
tri sound 2 fm 0.5 fmh
c3 note 0.5 gain 2 decay
.
```
Subdivide with `at`:
Reverb on half the hits. `!?` is the opposite — executes when falsy:
```forth
4 ( @i 4 / at sine s c4 note 0.3 gain . ) times
( 0.5 delay 0.9 delayfeedback ) coin !?
saw sound
c4 note
500 lpf
0.5 decay
0.5 gain
.
```
Four evenly spaced notes within the step.
Quiet on half the hits. These pair well with `chance` and `fill` from the Randomness tutorial.
Vary intensity per iteration:
### ifelse
Two quotations, one condition. The true branch comes first:
```forth
8 (
@i 8 / at
@i 4 mod 0 = if 0.7 else 0.2 then gain
tri s c5 note 0.1 decay .
) times
( c3 note ) ( c5 note ) coin ifelse
saw sound 0.3 verb
0.5 decay 0.6 gain
.
```
Eight notes per step. Every fourth one louder.
Reads naturally: "c3 or c5, depending on the coin."
```forth
( 0.8 gain ) ( 0.3 gain ) fill ifelse
tri s c4 note 0.2 decay .
```
Loud during fills, quiet otherwise.
### select
Choose the nth quotation from a list. The index is 0-based:
```forth
( c4 ) ( e4 ) ( g4 ) ( b4 ) 0 3 rand select
note sine s 0.5 decay .
```
Four notes of a major seventh chord picked randomly. Note that this is unnecessarily complex :)
### apply
When you have a quotation and want to execute it unconditionally:
```forth
( dup + ) apply
```
Pops the quotation and runs it. Simpler than `?` when there is no condition to check.
## More!
For probability gates, periodic execution, and euclidean rhythms, see the Randomness tutorial. For generators and ranges, see the Generators tutorial.

92
docs/forth/cycling.md Normal file
View File

@@ -0,0 +1,92 @@
# Cycling & Selection
These words all share a pattern: push values onto the stack, then select one. If the selected item is a quotation, it gets executed. If it is a plain value, it gets pushed. All of them support `[ ]` brackets for auto-counting.
## cycle / pcycle
Sequential rotation through values.
`cycle` advances based on `runs` — how many times this particular step has played:
```forth
60 64 67 3 cycle note sine s . ;; 60, 64, 67, 60, 64, 67, ...
```
`pcycle` advances based on `iter` — the pattern iteration count:
```forth
kick snare 2 pcycle s . ;; kick on even iterations, snare on odd
```
The distinction matters when patterns have different lengths or when multiple steps share the same script. `cycle` gives each step its own independent counter. `pcycle` ties all steps to the same global pattern position.
## bounce / pbounce
Ping-pong instead of wrapping. With 4 values the sequence is 0, 1, 2, 3, 2, 1, 0, 1, 2, ...
```forth
60 64 67 72 4 bounce note sine s . ;; ping-pong by step runs
60 64 67 72 4 pbounce note sine s . ;; ping-pong by pattern iteration
```
Same `runs` vs `iter` split as `cycle` / `pcycle`.
## choose
Uniform random selection:
```forth
kick snare hat 3 choose s . ;; random drum hit each time
```
Unlike the cycling words, `choose` is nondeterministic — every evaluation picks independently.
## wchoose
Weighted random. Push value/weight pairs, then the count:
```forth
kick 0.5 snare 0.3 hat 0.2 3 wchoose s .
```
Kick plays 50% of the time, snare 30%, hat 20%. Weights are normalized automatically — they don't need to sum to 1.
## index
Direct lookup by an explicit index. The index wraps with modulo, so it never goes out of bounds. Negative indices count from the end:
```forth
[ c4 e4 g4 ] step index note sine s . ;; step number picks the note
[ c4 e4 g4 ] iter index note sine s . ;; pattern iteration picks the note
```
This is useful when you want full control over which value is selected, driven by any expression you like.
## Using with brackets
All these words take a count argument `n`. Square brackets compute that count for you:
```forth
[ 60 64 67 ] cycle note sine s . ;; no need to write "3"
[ kick snare hat ] choose s .
[ c4 e4 g4 b4 ] bounce note sine s .
```
Without brackets: `60 64 67 3 cycle`. With brackets: `[ 60 64 67 ] cycle`. Same result, less counting.
## Quotations
When any of these words selects a quotation, it executes it instead of pushing it:
```forth
[ ( c4 note ) ( e4 note ) ( g4 note ) ] cycle
sine s .
```
On the first run the quotation `( c4 note )` executes, setting the note to C4. Next run, E4. Then G4. Then back to C4.
This works with all selection words. Mix plain values and quotations freely:
```forth
[ ( hat s 0.3 gain . ) ( snare s . ) ( kick s . ) ] choose
```

View File

@@ -13,8 +13,7 @@ Use `:` to start a definition and `;` to end it:
This creates a word called `double` that duplicates the top value and adds it to itself. Now you can use it:
```forth
3 double ;; leaves 6 on the stack
5 double ;; leaves 10 on the stack
3 double print ;; leaves 6 on the stack
```
The definition is simple: everything between `:` and `;` becomes the body of the word.

View File

@@ -10,7 +10,7 @@ Classic Forth uses parentheses for comments:
( this is a comment )
```
Cagire uses double semicolons:
In Cagire, parentheses create quotations, so comments use double semicolons instead:
```forth
;; this is a comment
@@ -18,18 +18,6 @@ Cagire uses double semicolons:
Everything after `;;` until the end of the line is ignored.
## Quotations
Classic Forth has no quotations. Code is not a value you can pass around.
Cagire has first-class quotations using parentheses:
```forth
( dup + )
```
This pushes a block of code onto the stack. You can store it, pass it to other words, and execute it later. Quotations enable conditionals, probability, and cycling.
## Conditionals
Classic Forth uses `IF ... ELSE ... THEN`:
@@ -190,31 +178,6 @@ Execute a quotation on specific iterations:
`bjork` and `pbjork` use Bjorklund's algorithm to distribute k hits as evenly as possible across n positions. `bjork` counts by step runs, `pbjork` counts by pattern iterations. Classic Euclidean rhythms: tresillo (3,8), cinquillo (5,8), son clave (5,16).
## Cycling
Cagire has built-in support for cycling through values. Push values onto the stack, then select one based on pattern state:
```forth
60 64 67 3 cycle note
```
Each time the step runs, a different note is selected. The `3` tells `cycle` how many values to pick from.
You can also use quotations if you need to execute code:
```forth
( c4 note ) ( e4 note ) ( g4 note ) 3 cycle
```
When the selected value is a quotation, it gets executed. When it is a plain value, it gets pushed onto the stack.
Two cycling words exist:
- `cycle` - selects based on `runs` (how many times this step has played)
- `pcycle` - selects based on `iter` (how many times the pattern has looped)
The difference between `cycle` and `pcycle` matters when patterns have different lengths. `cycle` counts per-step, `pcycle` counts per-pattern.
## Polyphonic Parameters
Parameter words like `note`, `freq`, and `gain` consume the entire stack. If you push multiple values before a param word, you get polyphony:

View File

@@ -89,7 +89,7 @@ The fix is simple: make sure you push enough values before calling a word. Check
* **Leftover values** are the opposite problem: values remain on the stack after your script finishes. This is less critical but indicates sloppy code. If your script leaves unused values behind, you probably made a mistake somewhere.
```forth
3 4 5 + . ;; plays a sound, but 3 is still on the stack
3 4 5 + ;; 3 is still on the stack, unconsumed
```
The `3` was never used. Either it should not be there, or you forgot a word that consumes it.