Feat: continue to improve documentation

This commit is contained in:
2026-02-17 00:51:56 +01:00
parent 524e686b3a
commit 8fcc0f4e54
9 changed files with 175 additions and 179 deletions

View File

@@ -1,46 +1,47 @@
# 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 many years to do scientific work and program embedded systems: it was used to control telescopes and was running on some devices used in space missions among other things. Forth quickly evolved into multiple implementations targetting various computer architectures. None of them really took off and became popular. Nonetheless, the ideas behind Forth continue to garner the interest of many different people in very different (often unrelated) fields. Nowadays, Forth languages are used by hackers and artists for their peculiarity. Forth is simple, direct and beautiful to implement. Forth is an elegant and minimal language to learn. It is easy to understand, to extend and to apply to a specific task. The Forth we use in Cagire is specialized in making live music. We think of it as a DSL: a _Domain Specific Language_.
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_.
## Why Forth?
Most programming languages nowadays use a complex syntax made of `variables`, `expressions` and `statements` like `x = 3 + 4`. Forth works differently. It is way more simple than that, has almost no syntax and performs computations in a quite unique way. You push values onto a `stack` and apply `words` that transform them:
Most programming languages 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 +
```
This program leaves the number `7` on the stack. There are no variables, no parentheses, no syntax to remember. You just end up with words and numbers separated by spaces. For live coding music, this directness is quite exciting. All you do is think in terms of transformations and add things to the stack: take a note, shift it up, add reverb, play it.
The 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.
## The Stack
The stack is where values live. When you type a number, it goes on the stack. When you type a word, it usually takes values off and puts new ones back.
```forth
3 ( stack: 3 )
4 ( stack: 3 4 )
+ ( stack: 7 )
3 ;; stack: 3
4 ;; stack: 3 4
+ ;; stack: 7
```
The stack is `last-in, first-out`. The most recent value is always on top. This means that its often better to read Forth programs from the end to the beginning: from right to left, from the bottom to the top.
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.
## Words
Everything in Forth is either a `number` or a `word`. Words are like functions but conceptually simpler. They have no arguments or return values in the traditional sense. They just manipulate the stack directly.
```forth
dup ( duplicate the top value )
drop ( discard the top value )
swap ( swap the top two values )
dup ;; duplicate the top value
drop ;; discard the top value
swap ;; swap the top two values
```
Words compose naturally on the stack. To double a number:
```forth
3 dup + ( 3 3 +)
;; 3 3 +
3 dup +
```
There are a lot of words in a Forth and thus, Cagire has a `Dictionary` embedded directly into the application. You can also create your own words. They will work just like the already existing words. There are good reasons to create new words on-the-fly:
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:
- To make synth definitions.
- To abstract _some piece of code_ that you use frequently.
@@ -48,32 +49,32 @@ There are a lot of words in a Forth and thus, Cagire has a `Dictionary` embedded
## Values
Four types of values can live on the stack:
Four basic types of values can live on the stack:
- **Integers**: `42`, `-7`, `0`
- **Floats**: `0.5`, `3.14`, `-1.0`
- **Strings**: `"kick"`, `"hello"`
- **Quotations**: `{ dup + }` (code as data)
Quotations are special. They let you pass code around as a value. This is how conditionals and loops work. Think nothing of it for now, you will learn more about how to use it later on.
Floats can omit the leading zero: `.25` is the same as `0.25`, and `-.5` is `-0.5`.
## Stack Notation
Parentheses are ignored by the parser. You can use them freely for visual grouping without affecting execution:
Documentation uses a notation to show what words do:
```
( before -- after )
```forth
(c4 note) (0.5 gain) "sine" s .
```
For example, `+` has the signature `( a b -- sum )`. It takes two values and leaves one.
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.
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.
## The Command Register
Traditional Forth programs print text to a terminal. Cagire's Forth builds sound commands instead. This happens through an invisible accumulator called the command register. The command register has two parts:
Traditional Forth programs print text to a terminal. Cagire's Forth builds sound commands instead. This happens through an internal accumulator called the command register. The command register has two parts:
- a **sound name** (what instrument to play)
- a list of **parameters** (how to play it)
Three types of words interact with it:
Three kinds of words interact with it:
```forth
kick sound ;; sets the sound name
@@ -108,8 +109,3 @@ The order of parameters does not matter. You can even emit multiple times in a s
```
This is useful when conditionals might cancel a sound before it emits.
## More details
- Each step has its own stack and independant runtime.
- Word definitions and variable definitions are shared by all steps.

View File

@@ -1,185 +1,140 @@
# Control Flow
A drum pattern that plays the same sound on every step is not very interesting. You want kicks on the downbeats, snares on the backbeats, hats filling the gaps. Control flow is how you make decisions inside a step.
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.
## if / else / then
The simplest branch. Push a condition, then `if`:
```forth
step 4 mod 0 = if kick s . then
coin if 0.8 gain then
saw s c4 note .
```
Every fourth step gets a kick. The rest do nothing. Add `else` for a two-way split:
The gain is applied if the coin flip is true. The sound will always plays. Add `else` for a two-way split:
```forth
step 2 mod 0 = if
kick s 0.8 gain .
coin if
c4 note
else
hat s 0.3 gain .
c3 note
then
saw s 0.6 gain .
```
These are compiler syntax -- you won't find them in the dictionary. Think nothing of it.
These are compiled directly into branch instructions. For that reason, these words will not appear in the dictionary.
## ? and !?
When you already have a quotation, `?` executes it if the condition is truthy:
```forth
{ snare s . } coin ?
{ 0.4 verb } coin ?
saw s c4 note 0.5 gain . ;; reverb on half the hits
```
`!?` is the opposite -- executes when falsy:
`!?` is the opposite executes when falsy:
```forth
{ hat s 0.2 gain . } coin !?
{ 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
{ rim s . } fill ? ;; rim only during fills
{ 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
{ kick s . } { hat s . } step 2 mod 0 = ifelse
```
Reads naturally: "kick or hat, depending on whether it's an even step."
```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
```
## pick
Choose the nth option from a list of quotations:
```forth
{ kick s . } { snare s . } { hat s . } step 3 mod pick
```
Step 0 plays kick, step 1 plays snare, step 2 plays hat. The index is 0-based.
```forth
{ c4 } { e4 } { g4 } { b4 } step 4 mod pick
{ c4 } { e4 } { g4 } { b4 } iter 4 mod pick
note sine s 0.5 decay .
```
Four notes cycling through a major seventh chord, one per step.
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
For matching a value against several options. Cleaner than a chain of `if`s when you have more than two branches:
```forth
step 8 mod case
0 of kick s . endof
4 of snare s . endof
iter 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 .
```
Steps 0 and 4 get sounds. Everything else falls through to `endcase` and nothing happens.
A different root note each time the pattern loops.
A fuller pattern:
The last line before `endcase` is the default — it runs when no `of` matched:
```forth
step 8 mod case
0 of kick s 0.9 gain . endof
2 of hat s 0.3 gain . endof
4 of snare s 0.7 gain . endof
6 of hat s 0.3 gain . endof
hat s 0.15 gain .
endcase
```
The last line before `endcase` is the default -- it runs when no `of` matched. Here it gives unmatched steps a ghost hat.
The `of` value can be any expression:
```forth
step 16 mod case
0 of kick s . endof
3 1 + of snare s . endof
2 4 * of kick s . snare s . endof
iter 3 mod case
0 of 0.9 gain endof
0.4 gain ;; default: quieter
endcase
saw s c4 note .
```
## times
Repeat a quotation n times. `@i` holds the current iteration (starting from 0):
```forth
4 { @i 4 / at hat s . } times ;; four hats, evenly spaced
```
Build chords:
Repeat a quotation n times. The variable `@i` is automatically set to the current iteration index (starting from 0):
```forth
3 { c4 @i 4 * + note } times
sine s 0.4 gain 0.5 verb . ;; c4, e4, g#4
sine s 0.4 gain 0.5 verb . ;; c4, e4, g#4 a chord
```
Subdivide and accent:
Subdivide with `at`:
```forth
4 { @i 4 / at sine s c4 note 0.3 gain . } times
```
Four evenly spaced notes within the step.
Vary intensity per iteration:
```forth
8 {
@i 8 / at
@i 4 mod 0 = if 0.7 else 0.2 then gain
hat s .
tri s c5 note 0.1 decay .
} times
```
Eight hats per step. Every fourth one louder.
## Putting It Together
A basic drum pattern using `case`:
```forth
step 8 mod case
0 of kick s . endof
2 of { hat s . } often endof
4 of snare s . endof
6 of { rim s . } sometimes endof
{ hat s 0.15 gain . } coin ?
endcase
```
Kicks and snares on the strong beats. Hats and rims show up probabilistically. The default sprinkles ghost hats.
A melodic step that picks a scale degree and adds micro-timing:
```forth
{ c4 } { d4 } { e4 } { g4 } { a4 } step 5 mod pick
note
step 3 mod 0 = if
0 0.33 0.66 at ;; triplet feel on every third step
then
saw s 0.4 gain 0.3 decay 0.2 verb .
```
A `times` loop paired with `case` for a drum machine in one step:
```forth
4 {
@i case
0 of kick s . endof
1 of hat s 0.3 gain . endof
2 of snare s . endof
3 of { rim s . } 0.5 chance endof
endcase
@i 4 / at
} times
```
Four voices, four sub-positions, one step.
Eight notes per step. Every fourth one louder.

View File

@@ -58,6 +58,19 @@ You can even redefine numbers:
Now `2` pushes `4` onto the stack. The number two no longer exists in your session. This is a classic Forth demonstration: nothing is sacred, everything can be redefined.
## Removing Words
`forget` removes a user-defined word from the dictionary:
```forth
: double dup + ;
3 double ;; 6
"double" forget
3 double ;; error: unknown word
```
This only affects words you defined with `:` ... `;`. Built-in words cannot be forgotten.
## Practical Uses
**Synth definitions** save you from repeating sound design:

View File

@@ -14,7 +14,7 @@ The dictionary shows every available word organized by category:
- **Context**: Sequencer state like `step`, `beat`, `tempo`.
- And many more...
This tutorial will not teach you how to use all words. The syntax is very uniform and you can quickly learn a new word when necessary. We encourage you to explore as you play, this is the best way to learn. The tutorial will remain focused on various topics that require you to apply knowledge to a given task or specific context.
This documentation will not teach you how to use all words. It would be counterproductive to do so and it would defeat the purpose of having a dictionary. The syntax is very uniform and you can quickly learn a new word when necessary. We encourage you to explore as you play, this is the best way to learn. The tutorial will remain focused on various topics that require you to apply knowledge to a given task or specific context.
## Navigation
@@ -33,6 +33,4 @@ Each word entry shows:
- **Description**: What the word does
- **Example**: How to use it
Press `/` to search across all words. The search matches word names, aliases, and descriptions. Press `Esc` to clear and return to browsing.
Use the dictionary while writing scripts to check stack effects and study their behavior. Some words also come with shorter aliases (e.g., `sound``s`). You will learn aliases quite naturally, because aliases are usually reserved for very common words.
Press `/` to search across all words. The search matches word names, aliases, and descriptions. Press `Esc` to clear and return to browsing. Use the dictionary while writing scripts to check stack effects and study their behavior. Some words also come with shorter aliases (e.g., `sound``s`). You will learn aliases quite naturally, because aliases are usually reserved for very common words.

View File

@@ -1,4 +1,4 @@
# Oddities
# Cagire's Forth VS Classic Forth
Cagire's Forth is not a classic Forth. It borrows the core ideas (stack-based evaluation, postfix notation, word definitions) but adds modern features and domain-specific extensions. If you know traditional Forth, here are the differences.

View File

@@ -1,51 +1,78 @@
# The Prelude
When you define a word in a step, it becomes available to all steps. But when you close and reopen the project, the dictionary is empty again. Words defined in steps only exist after those steps run.
You can define words in any step and they become available to all other steps. But as a project grows, definitions get scattered across steps and become hard to find and maintain. The **prelude** is a dedicated place for this. It is a project-wide Forth script that runs once before the first step plays. Definitions, variables, settings — everything in one place. Press `d` to open the prelude editor. Press `Esc` to save and close. Press `D` (Shift+d) to re-evaluate it without opening the editor.
The **prelude** solves this. It's a project-wide script that runs automatically when playback starts and when you load a project.
## Naming Your Sounds
## Accessing the Prelude
Press `d` to open the prelude editor. Press `Esc` to save and evaluate. Press `D` (Shift+d) to re-evaluate the prelude without opening the editor.
## What It's For
Define words that should be available everywhere, always:
The most common use of the prelude is to define words for your instruments. Without a prelude, every step that plays a bass has to spell out the full sound design or to create a new word before using it:
```forth
: kick "kick" s 0.9 gain . ;
: hat "hat" s 0.4 gain . ;
: bass "saw" s 0.7 gain 200 lpf . ;
pulse sound 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width .
```
Now every step in your project can use `kick`, `hat`, and `bass` from the first beat.
Repeat this across eight steps without making a new word and you have eight copies of the same thing. Change the filter? Change it eight times.
In the prelude, define it once:
```forth
: bass pulse sound 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width . ;
```
Now every step just writes `c2 note bass`. Change the sound in one place, every step follows.
A step that used to read:
```forth
pulse sound c2 note 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width .
```
Becomes:
```forth
c2 note bass
```
## Building a Vocabulary
The prelude is where you build the vocabulary for your music. Not just instruments but any combination of code / words you want to reuse:
```forth
;; instruments
: bass pulse sound 0.8 gain 400 lpf 1 lpd 8 lpe 0.6 width . ;
: pad sine sound 0.5 gain 2 spread 1.5 attack 0.4 verb . ;
: lead tri sound 0.6 gain 5000 lpf 2 decay . ;
;; musical helpers
: octup 12 + ;
: octdn 12 - ;
: quiet 0.3 gain ;
: loud 0.9 gain ;
```
By using the prelude and predefined words, steps become expressive and short. The prelude carries the design decisions; steps carry the composition.
## Setting Initial State
The prelude also runs plain Forth, not just definitions. You can use it to set variables and seed the random generator:
```forth
c2 !root
0 !mode
42 seed
```
Every step can then read `@root` and `@mode`. And `42 seed` makes randomness reproducible — same seed, same sequence every time you hit play.
## When It Runs
The prelude evaluates:
The prelude evaluates at three moments:
1. When you press Space to start playback (if stopped)
2. When you load a project
3. When you press `D` manually
1. When you press **Space** to start playback
2. When you **load** a project
3. When you press **D** manually
It does not run on every step, only once at these moments. This makes it ideal for setup code: word definitions, initial variable values, seed resets.
It runs once at these moments, not on every step. This makes it the right place for definitions and initial values. If you edit the prelude while playing, press `D` to push changes into the running session. New definitions take effect immediately; the next time a step runs, it sees the updated words.
## Practical Example
## What Not to Put Here
A prelude for a techno project:
```forth
: k "kick" s 1.2 attack . ;
: sn "snare" s 0.6 gain 0.02 attack . ;
: hh "hat" s 0.3 gain 8000 hpf . ;
: sub "sine" s 0.8 gain 150 lpf . ;
0 seed
```
Step scripts become trivial:
```forth
c1 note k sub
```
The sound design lives in the prelude. Steps focus on rhythm and melody.
The prelude has no access to sequencer state. Words like `step`, `beat`, `iter`, and `phase` are meaningless here because no step is playing yet. Use the prelude for definitions and setup, not for logic that depends on timing. The prelude also should not emit sounds. It runs silently — any `.` calls here would fire before the sequencer clock is running and produce nothing useful.

View File

@@ -1,6 +1,6 @@
# The Stack
The stack is the heart of Forth. Every value you type goes onto the stack. Every word you call takes values from the stack and puts results back. There are no variables in the traditional sense, just this pile of values that grows and shrinks as your program runs.
In most languages, you store values in variables and pass them to functions. Forth has a single shared workspace instead: the stack. You put values on it, words take them off and put results back. Everything flows through the stack. There is no assignment, no named arguments — just a sequence of values waiting to be consumed. Learning to think in terms of the stack is the one skill that makes everything else in Forth click.
## Pushing Values
@@ -49,7 +49,7 @@ The key to Forth is learning to visualize the stack as you write. Consider this
| `2` | `7 2` | Push 2 |
| `*` | `14` | Multiply |
This computes `(3 + 4) * 2`. The parentheses are implicit in the order of operations. You can use line breaks and white spaces to keep organized, and the editor will also show the stack at each step for you if you ask it nicely :)
This computes `(3 + 4) * 2`. The parentheses are implicit in the order of operations. You can use line breaks and whitespace to keep organized, and the editor can show the stack state as you type — press `Ctrl+S` to toggle it.
## Rearranging Values
@@ -86,7 +86,7 @@ Two things can go wrong with the stack:
The fix is simple: make sure you push enough values before calling a word. Check the stack effect in the dictionary if you are unsure.
* **Stack overflow** is the opposite: too many values left on the stack. This is less critical but indicates sloppy code. If your script leaves unused values behind, you probably made a mistake somewhere.
* **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

View File

@@ -56,6 +56,10 @@ Reset on some condition:
{ 0 !n } @n 16 > ? ;; reset after 16
```
## When Changes Take Effect
Within a single step, you can read back what you just wrote. But variable changes only become visible to other steps after the current step finishes executing. If step 0 writes `10 !x` and step 1 reads `@x`, step 1 sees the value from the previous iteration of step 0, not from the current one running in parallel.
## Naming Sounds
Store a sound name in a variable, reuse it across steps: