Feat: improving MIDI

This commit is contained in:
2026-02-15 19:06:49 +01:00
parent 160546d64d
commit b23dd85d0f
6 changed files with 304 additions and 98 deletions

100
docs/tutorial_at.md Normal file
View File

@@ -0,0 +1,100 @@
# Timing with at
Every step has a duration. By default, sounds emit at the very start of that duration. `at` changes *when* within the step sounds fire -- giving you sub-step rhythmic control without adding more steps.
## The Basics
`at` drains the entire stack and stores the values as timing offsets. Each value is a fraction of the step duration: 0 = start, 0.5 = halfway, 1.0 = next step boundary.
```forth
0.5 at kick s . ;; kick at the midpoint
```
Push multiple values before calling `at` to get multiple emits from a single `.`:
```forth
0 0.5 at kick s . ;; two kicks: one at start, one at midpoint
0 0.25 0.5 0.75 at hat s . ;; four hats, evenly spaced
```
The deltas persist across multiple `.` calls until `clear` or a new `at`:
```forth
0 0.5 at
kick s . ;; 2 kicks
hat s . ;; 2 hats (same timing)
clear
snare s . ;; 1 snare (deltas cleared)
```
## Cross-product: at Without arp
Without `arp`, deltas multiply with polyphonic voices. If you have 3 notes and 2 deltas, you get 6 emits -- every note at every delta:
```forth
0 0.5 at
c4 e4 g4 note sine s . ;; 6 emits: 3 notes x 2 deltas
```
This is a chord played twice per step.
## 1:1 Pairing: at With arp
`arp` changes the behavior. Instead of cross-product, deltas and arp values pair up 1:1. Each delta gets one note from the arpeggio:
```forth
0 0.33 0.66 at
c4 e4 g4 arp note sine s . ;; c4 at 0, e4 at 0.33, g4 at 0.66
```
If the lists differ in length, the shorter one wraps around:
```forth
0 0.25 0.5 0.75 at
c4 e4 arp note sine s . ;; c4, e4, c4, e4 at 4 time points
```
This is THE key distinction. Without `arp`: every note at every time. With `arp`: one note per time slot.
## Generating Deltas
You rarely type deltas by hand. Use generators:
Evenly spaced via `.,`:
```forth
0 1 0.25 ., at hat s . ;; 0 0.25 0.5 0.75 1.0
```
Euclidean distribution via `euclid`:
```forth
3 8 euclid at hat s . ;; 3 hats at positions 0, 3, 5
```
Random timing via `gen`:
```forth
{ 0.0 1.0 rand } 4 gen at hat s . ;; 4 hats at random positions
```
Geometric spacing via `geom..`:
```forth
0.0 2.0 4 geom.. at hat s . ;; exponentially spaced
```
## Gating at
Wrap `at` expressions in quotations for conditional timing:
```forth
{ 0 0.25 0.5 0.75 at } 2 every ;; 16th-note hats every other bar
hat s .
{ 0 0.5 at } 0.5 chance ;; 50% chance of double-hit
kick s .
```
When the quotation doesn't execute, no deltas are set -- you get the default single emit at beat start.

View File

@@ -27,13 +27,13 @@ Sequences of values drive music: arpeggios, parameter sweeps, rhythmic patterns.
100 0.5 4 geom.. ;; 100 50 25 12.5
```
Musical use -- build a harmonic series:
Build a harmonic series:
```forth
110 2 5 geom.. 5 rev note
110 2 5 geom.. 5 rev freq
```
That gives you 110, 220, 440, 880, 1760 (reversed), ready to feed into `note` or `freq`.
That gives you 110, 220, 440, 880, 1760 (reversed), ready to feed into `freq`.
## Computed Sequences
@@ -73,14 +73,6 @@ The distinction: `gen` is for building data. `times` is for doing things.
These give you raw indices as data on the stack. This is different from `bjork` and `pbjork` (covered in the Randomness tutorial), which execute a quotation on matching steps. `euclid` gives you numbers to work with; `bjork` triggers actions.
Use euclid indices to pick notes from a scale:
```forth
: pick ( ..vals n i -- val ) rot drop swap ;
c4 d4 e4 g4 a4 ;; pentatonic scale on the stack
3 8 euclid ;; get 3 hit positions
```
## Transforming Sequences
Four words reshape values already on the stack. All take n (the count of items to operate on) from the top:
@@ -143,39 +135,4 @@ Or replicate a value for batch processing:
0.5 4 dupn 4 sum ;; 2.0
```
## Combining Techniques
An arpeggio that shuffles every time the step plays:
```forth
c4 e4 g4 b4 4 shuffle
drop drop drop ;; keep only the first note
note sine s .
```
Parameter spread across voices -- four sines with geometrically spaced frequencies:
```forth
220 1.5 4 geom..
4 { @i 1 + pick note sine s . } times
```
Euclidean rhythm driving note selection from a generated sequence:
```forth
3 8 euclid ;; 3 hit indices
```
A chord built from a range, then sorted high to low:
```forth
60 67 .. 8 rsort
```
Rhythmic density control -- generate hits, keep only the loud ones:
```forth
{ 0.0 1.0 rand } 8 gen
```
The generator words produce raw material. The transform words shape it. Together they let you express complex musical ideas in a few words.