Feat: lots of things, preparing for live gig

This commit is contained in:
2026-02-15 11:23:11 +01:00
parent cfaadd9d33
commit 160546d64d
59 changed files with 1414 additions and 96 deletions

302
docs/tutorial_harmony.md Normal file
View File

@@ -0,0 +1,302 @@
# Notes & Harmony
Cagire speaks music theory. Notes, intervals, chords, and scales are all first-class words that compile to stack operations on MIDI values. This tutorial covers every pitch-related feature.
## MIDI Notes
Write a note name followed by an octave number. It compiles to a MIDI integer:
```forth
c4 ;; 60 (middle C)
a4 ;; 69 (concert A)
e3 ;; 52
```
Sharps use `s` or `#`. Flats use `b`:
```forth
fs4 ;; 66 (F sharp 4)
f#4 ;; 66 (same thing)
bb3 ;; 58 (B flat 3)
eb4 ;; 63
```
Octave range is -1 to 9. The formula is `(octave + 1) * 12 + base + modifier`, where C=0, D=2, E=4, F=5, G=7, A=9, B=11.
Note literals push a single integer onto the stack, just like writing `60` directly. They work everywhere an integer works:
```forth
c4 note sine s . ;; play middle C as a sine
a4 note 0.5 gain modal s . ;; concert A, quieter
```
## Intervals
An interval duplicates the top of the stack and adds semitones. This lets you build chords by stacking:
```forth
c4 M3 P5 ;; stack: 60 64 67 (C major triad)
c4 m3 P5 ;; stack: 60 63 67 (C minor triad)
a3 P5 ;; stack: 57 64 (A plus a fifth)
```
Simple intervals (within one octave):
| Interval | Semitones | Name |
|----------|-----------|------|
| `P1` / `unison` | 0 | Perfect unison |
| `m2` | 1 | Minor 2nd |
| `M2` | 2 | Major 2nd |
| `m3` | 3 | Minor 3rd |
| `M3` | 4 | Major 3rd |
| `P4` | 5 | Perfect 4th |
| `aug4` / `dim5` / `tritone` | 6 | Tritone |
| `P5` | 7 | Perfect 5th |
| `m6` | 8 | Minor 6th |
| `M6` | 9 | Major 6th |
| `m7` | 10 | Minor 7th |
| `M7` | 11 | Major 7th |
| `P8` | 12 | Octave |
Compound intervals (beyond one octave):
| Interval | Semitones |
|----------|-----------|
| `m9` | 13 |
| `M9` | 14 |
| `m10` | 15 |
| `M10` | 16 |
| `P11` | 17 |
| `aug11` | 18 |
| `P12` | 19 |
| `m13` | 20 |
| `M13` | 21 |
| `m14` | 22 |
| `M14` | 23 |
| `P15` | 24 |
## Chords
Chord words take a root note and push all the chord tones. They eat the root and replace it with the full voicing:
```forth
c4 maj ;; stack: 60 64 67
c4 min7 ;; stack: 60 63 67 70
c4 dom9 ;; stack: 60 64 67 70 74
```
**Triads:**
| Word | Intervals | Example (C4) |
|------|-----------|-------------|
| `maj` | 0 4 7 | 60 64 67 |
| `m` | 0 3 7 | 60 63 67 |
| `dim` | 0 3 6 | 60 63 66 |
| `aug` | 0 4 8 | 60 64 68 |
| `sus2` | 0 2 7 | 60 62 67 |
| `sus4` | 0 5 7 | 60 65 67 |
**Seventh chords:**
| Word | Intervals | Example (C4) |
|------|-----------|-------------|
| `maj7` | 0 4 7 11 | 60 64 67 71 |
| `min7` | 0 3 7 10 | 60 63 67 70 |
| `dom7` | 0 4 7 10 | 60 64 67 70 |
| `dim7` | 0 3 6 9 | 60 63 66 69 |
| `m7b5` | 0 3 6 10 | 60 63 66 70 |
| `minmaj7` | 0 3 7 11 | 60 63 67 71 |
| `aug7` | 0 4 8 10 | 60 64 68 70 |
**Sixth chords:**
| Word | Intervals | Example (C4) |
|------|-----------|-------------|
| `maj6` | 0 4 7 9 | 60 64 67 69 |
| `min6` | 0 3 7 9 | 60 63 67 69 |
**Extended chords:**
| Word | Intervals | Example (C4) |
|------|-----------|-------------|
| `dom9` | 0 4 7 10 14 | 60 64 67 70 74 |
| `maj9` | 0 4 7 11 14 | 60 64 67 71 74 |
| `min9` | 0 3 7 10 14 | 60 63 67 70 74 |
| `dom11` | 0 4 7 10 14 17 | 60 64 67 70 74 77 |
| `min11` | 0 3 7 10 14 17 | 60 63 67 70 74 77 |
| `dom13` | 0 4 7 10 14 21 | 60 64 67 70 74 81 |
**Add chords:**
| Word | Intervals | Example (C4) |
|------|-----------|-------------|
| `add9` | 0 4 7 14 | 60 64 67 74 |
| `add11` | 0 4 7 17 | 60 64 67 77 |
| `madd9` | 0 3 7 14 | 60 63 67 74 |
**Altered dominants:**
| Word | Intervals | Example (C4) |
|------|-----------|-------------|
| `dom7b9` | 0 4 7 10 13 | 60 64 67 70 73 |
| `dom7s9` | 0 4 7 10 15 | 60 64 67 70 75 |
| `dom7b5` | 0 4 6 10 | 60 64 66 70 |
| `dom7s5` | 0 4 8 10 | 60 64 68 70 |
Chord tones are varargs -- they eat the entire stack. So a chord word should come right after the root note:
```forth
c4 maj note sine s . ;; plays all 3 notes as one chord
```
## Scales
Scale words convert a degree index into a MIDI note. The base note is C4 (MIDI 60). Degrees wrap around with octave transposition:
```forth
0 major ;; 60 (C4 -- degree 0)
4 major ;; 67 (G4 -- degree 4)
7 major ;; 72 (C5 -- degree 7, wraps to next octave)
-1 major ;; 59 (B3 -- negative degrees go down)
```
Use scales with `cycle` or `rand` to walk through pitches:
```forth
0 1 2 3 4 5 6 7 8 cycle minor note sine s .
```
**Standard modes:**
| Word | Pattern (semitones) |
|------|-------------------|
| `major` | 0 2 4 5 7 9 11 |
| `minor` | 0 2 3 5 7 8 10 |
| `dorian` | 0 2 3 5 7 9 10 |
| `phrygian` | 0 1 3 5 7 8 10 |
| `lydian` | 0 2 4 6 7 9 11 |
| `mixolydian` | 0 2 4 5 7 9 10 |
| `aeolian` | 0 2 3 5 7 8 10 |
| `locrian` | 0 1 3 5 6 8 10 |
**Pentatonic and blues:**
| Word | Pattern |
|------|---------|
| `pentatonic` | 0 2 4 7 9 |
| `minpent` | 0 3 5 7 10 |
| `blues` | 0 3 5 6 7 10 |
**Chromatic and whole tone:**
| Word | Pattern |
|------|---------|
| `chromatic` | 0 1 2 3 4 5 6 7 8 9 10 11 |
| `wholetone` | 0 2 4 6 8 10 |
**Harmonic and melodic minor:**
| Word | Pattern |
|------|---------|
| `harmonicminor` | 0 2 3 5 7 8 11 |
| `melodicminor` | 0 2 3 5 7 9 11 |
**Jazz / Bebop:**
| Word | Pattern |
|------|---------|
| `bebop` | 0 2 4 5 7 9 10 11 |
| `bebopmaj` | 0 2 4 5 7 8 9 11 |
| `bebopmin` | 0 2 3 5 7 8 9 10 |
| `altered` | 0 1 3 4 6 8 10 |
| `lyddom` | 0 2 4 6 7 9 10 |
**Symmetric:**
| Word | Pattern |
|------|---------|
| `halfwhole` | 0 1 3 4 6 7 9 10 |
| `wholehalf` | 0 2 3 5 6 8 9 11 |
| `augmented` | 0 3 4 7 8 11 |
| `tritone` | 0 1 4 6 7 10 |
| `prometheus` | 0 2 4 6 9 10 |
**Modal variants (from melodic minor):**
| Word | Pattern |
|------|---------|
| `dorianb2` | 0 1 3 5 7 9 10 |
| `lydianaug` | 0 2 4 6 8 9 11 |
| `mixb6` | 0 2 4 5 7 8 10 |
| `locrian2` | 0 2 3 5 6 8 10 |
## Octave Shifting
`oct` transposes a note by octaves:
```forth
c4 1 oct ;; 72 (C5)
c4 -1 oct ;; 48 (C3)
c4 2 oct ;; 84 (C6)
```
Stack effect: `(note shift -- transposed)`. The shift is multiplied by 12 and added to the note.
## Frequency Conversion
`mtof` converts a MIDI note to frequency in Hz. `ftom` does the reverse:
```forth
69 mtof ;; 440.0 (A4)
60 mtof ;; 261.63 (C4)
440 ftom ;; 69.0
```
Useful when a synth parameter expects Hz rather than MIDI:
```forth
c4 mtof freq sine s .
```
## Putting It Together
A chord progression cycling every pattern iteration:
```forth
{ c3 maj7 } { f3 maj7 } { g3 dom7 } { c3 maj7 } 4 pcycle
note sine s .
```
Arpeggiate a chord across the step's time divisions:
```forth
c4 min7 arp note 0.5 decay sine s .
```
Random notes from a scale:
```forth
0 7 rand minor note sine s .
```
A bass line walking scale degrees:
```forth
0 2 4 5 7 5 4 2 8 cycle minor note
-2 oct 0.8 gain sine s .
```
Chord voicings with random inversion:
```forth
e3 min9
{ } { 1 oct } 2 choose
note modal s .
```
Stacked intervals for custom voicings:
```forth
c3 P5 P8 M10 ;; C3, G3, C4, E4
note sine s .
```