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,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.