Files
Cagire/docs/oddities.md
Raphaël Forment 6b95f31afd
Some checks failed
Deploy Website / deploy (push) Failing after 4m48s
Feat: polyphony + iterator reset
2026-02-02 00:33:46 +01:00

5.9 KiB
Raw Blame History

Oddities

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

Comments

Classic Forth uses parentheses for comments:

( this is a comment )

Cagire uses double semicolons:

;; this is a comment

Everything after ;; until the end of the line is ignored.

Quotations

Classic Forth has no quotations. Code is not a value you can pass around.

Cagire has first-class quotations using curly braces:

{ 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:

x 0 > IF 1 ELSE -1 THEN

Cagire supports this syntax but also provides quotation-based conditionals:

{ 1 } { -1 } x 0 > ifelse

The words ? and !? execute a quotation based on a condition:

{ "kick" s . } coin ?     ;; execute if coin is 1
{ "snare" s . } coin !?   ;; execute if coin is 0

Strings

Classic Forth has limited string support. You print strings with .":

." Hello World"

Cagire has first-class strings:

"hello"

This pushes a string value onto the stack. Strings are used for sound names, sample names, and variable keys. You often do not need quotes at all. Any unrecognized word becomes a string automatically:

kick s .       ;; "kick" is not a word, so it becomes the string "kick"
myweirdname    ;; pushes "myweirdname" onto the stack

This makes scripts cleaner. You only need quotes when the string contains spaces or conflicts with a real word.

Variables

Classic Forth declares variables explicitly:

VARIABLE x
10 x !
x @

Cagire uses prefix syntax:

10 !x    ;; store 10 in x
@x       ;; fetch x (returns 0 if undefined)

No declaration needed. Variables spring into existence when you store to them.

Floating Point

Classic Forth (in its original form) has no floating point. Numbers are integers. Floating point was added later as an optional extension with separate words. Cagire has native floating point:

3.14159
0.5 0.3 +    ;; 0.8

Integers and floats mix freely. Division always produces a float.

Loops

Classic Forth has DO ... LOOP:

10 0 DO I . LOOP

Cagire uses a quotation-based loop with times:

4 { @i . } times    ;; prints 0 1 2 3

The loop counter is stored in the variable i, accessed with @i. This fits Cagire's style where control flow uses quotations.

4 { @i 4 / at hat s . } times    ;; hat at 0, 0.25, 0.5, 0.75
4 { c4 @i + note sine s . } times ;; ascending notes

For generating sequences without side effects, use .. or gen:

1 5 ..          ;; pushes 1 2 3 4 5
{ dup * } 4 gen ;; pushes 0 1 4 9 (squares)

The Command Register

This is completely unique to Cagire. Traditional Forth programs print text. Cagire programs build sound commands.

The command register accumulates a sound name and parameters:

"sine" sound    ;; set sound
440 freq        ;; add parameter
0.5 gain        ;; add parameter
.               ;; emit and clear

Nothing is sent to the audio engine until you emit with .. This is unlike any classic Forth.

Context Words

Cagire provides words that read the current sequencer state:

step    ;; current step index (0-127)
beat    ;; current beat position
iter    ;; pattern iteration count
tempo   ;; current BPM
phase   ;; position in bar (0-1)

These have no equivalent in classic Forth. They connect your script to the sequencer's timeline.

Probability

Classic Forth is deterministic. Cagire has built-in randomness:

{ "snare" s . } 50 prob       ;; 50% chance
{ "clap" s . } 0.25 chance    ;; 25% chance
{ "hat" s . } often           ;; 75% chance
{ "rim" s . } sometimes       ;; 50% chance
{ "tom" s . } rarely          ;; 25% chance

These words take a quotation and execute it probabilistically.

Cycling

Cagire has built-in support for cycling through values. Push values onto the stack, then select one based on pattern state:

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:

{ 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:

60 64 67 note sine s .    ;; emits 3 voices with notes 60, 64, 67

This works for any param and for the sound word itself:

440 880 freq sine tri s .    ;; 2 voices: sine at 440, tri at 880

When params have different lengths, shorter lists cycle:

60 64 67 note           ;; 3 notes
0.5 1.0 gain            ;; 2 gains (cycles: 0.5, 1.0, 0.5)
sine s .                ;; emits 3 voices

Polyphony multiplies with at deltas:

0 0.5 at                ;; 2 time points
60 64 note              ;; 2 notes
sine s .                ;; emits 4 voices (2 notes × 2 times)

Summary

Cagire's Forth is a domain-specific language for music. It keeps Forth's elegance (stack, postfix, definitions) but adapts it for live coding.