Files
galdr/README.md
T

239 lines
9.4 KiB
Markdown
Raw Permalink Normal View History

# galdr
A terminal **grid synthesizer**. Signal flows downward through a field of
single-glyph unit generators; every incoming MIDI note voices the grid (or the
columns tagged with its channel); the mix is rendered low-latency to PipeWire.
It is orca's voice — the same esoteric, spatial approach to programming, but
for making sound instead of sending events.
```
orca-rs ──MIDI + UDP──▶ galdr ──PipeWire──▶ speakers / stems
compose the pattern voice it (spectrust -p visualizes it)
```
## The grid
Signal flows **down**: each cell takes the sample from the cell above as its
input. Glyphs are **UPPERCASE** operators. A digit `0`–`9` to the left/right is
a constant parameter; a lowercase letter `a`–`z` reads MIDI CC (`a` = CC 0).
| glyph | unit generator | left / right param |
|---|---|---|
| `O` | sine | ratio |
| `A` | saw | ratio |
| `U` | pulse | width |
| `V` | triangle | — |
| `N` | noise | — |
| `L` `H` `B` | low / high / band-pass (SVF) | cutoff / resonance |
| `E` | envelope (gated by the note) | attack / release |
| `S` | soft clip (tanh) | drive |
| `C` | hard clip | threshold |
| `F` | wavefolder | amount |
| `D` | decimate / bitcrush | rate |
| `G` | gain | amount |
| `R` | ring modulator | ratio |
| `X` | FM: sine at the note pitch, phase pushed by the cell to its **left** (and above) | — / index |
| `M` | mix — adds the cell to its **left** | — |
| `K` | control envelope: 1 at note-on, decays to a floor; no audio input | decay / floor |
| `!` | output to master | — |
`K` is the one operator whose output is a *param*: placed to the **left** of
another operator it becomes that operator's left param, scaled 0–9. `2KL5` is
a lowpass whose cutoff starts open and decays (2) to the floor, with
resonance 5. That's the 303 filter envelope.
Each `!` sums into the master, so parallel columns stack timbres or voices.
An untagged column plays on every MIDI channel.
### Params
A param cell is a digit `0`–`9` (constant), a lowercase letter `a`–`z` (a
register: MIDI CC `a` = CC 0, or set over UDP), or one of three per-note
values: `@` velocity, `^` key tracking (note number), `?` a random value rolled
at note-on. All read on the same 0–9 scale, so `@L` is velocity-to-cutoff and
`?O` detunes every hit a little differently.
### Channel tags
A base-36 glyph directly **above** a column's top operator is that column's
MIDI channel: `0`–`9`, `a`–`f` for channels 1–16 (orca's numbering). The
column then only voices notes on that channel. A `>` right of the tag makes
the channel **mono-legato**: a note that overlaps the previous one slides the
running voice to the new pitch over 60 ms without retriggering the envelopes,
the 303 slide. From orca, make the step's note length longer than the gap.
### Acid
`examples/acid.galdr`, a 303 line on channel 1:
```
...0>
...A. saw (U for the square)
.2KL@ K sweeps the cutoff (decay 2, floor open-to-closed); accent = velocity -> resonance
...E1 short amp decay
..3S. overdrive
...!.
```
```
1....2.... column 0 is channel 1 (a filtered saw),
A....O.... column 5 is channel 2 (a plain sine)
L....E....
E....!....
!.........
```
### The master chain
Operators placed **below** a column's `!` no longer run per voice: they
process the summed stereo master, in series, in column order. Any filter or
shaper works there, plus two master-only effects:
| glyph | effect | left / right param |
|---|---|---|
| `Z` | delay | time (0 = 100 ms … 9 = 1 s) / feedback |
| `W` | stereo chorus | rate / depth |
```
A
!
5L lowpass on the whole mix
3Z6 then a 400 ms delay with lots of feedback
.W then a slow chorus for width
```
Editing the grid recompiles and hot-swaps the running program without dropping
audio. There is no embedded scope — run a spectrum analyzer (e.g. `spectrust`)
on the side if you want one.
## A drum kit (one drum per MIDI channel)
A note voices every column tagged with its channel, so give each drum its own
channel and let orca pick the channel per hit. `examples/drums.galdr`:
```
.0..9....a...b.
.A..O9...N...N.
@L5.EX9.6B5.8H.
.E2..E1..E1..E0
2S...!..3S...!.
.!.......!.....
```
- **bass** (tag `0`, MIDI ch 1): saw → lowpass with the cutoff from velocity
(`@`) and resonance 5 → envelope, release 2 → soft clip 2 → out.
- **kick** (tag `9`, ch 10): the left column is a sine into an envelope with
no `!`, it exists only as a modulator. The `X` next to it is a sine at the
note pitch, phase-modulated by that decaying signal with index 9: bright at
the hit, a clean low sine a moment later. Play it a low note (C1–C2).
- **snare** (tag `a`, ch 11): noise → bandpass 6, resonance 5 → short
envelope → soft clip 3. A mid note sets the bandpass body.
- **hats** (tag `b`, ch 12): noise → highpass 8 → envelope with release 0.
Note length from orca is the open/closed control.
From orca, `:` with the channel glyph per drum, e.g. `:93C` for the kick,
`:a3D` snare, `:b5F` hats, `:02C` bass. With `--stems 12` each drum also has
its own `galdr.ch*` node to record.
## An orchestra (the big example)
`examples/orchestra.galdr` is ten sections on ten MIDI channels, each in a
four-column block `[left param | chain | right param | gap]`, so every digit
is a knob. It uses most of the vocabulary: velocity (`@`) into a filter and
into an FM index, key tracking (`^`), per-note random (`?`), two modulator
columns feeding `X`, registers set from orca (`k` and `z`), and a hall on the
master chain (highpass, chorus, delay with feedback from `z`).
```
.0...1...2...3...44..5...6...7...8...9..
.A...A...A2.2U...NO.1U..5U...O7..A...V..
@L2.3L3.1EX@4L1.9HE36B6.6L1..EX3^L?.kL2.
4E5.3E4..1E23E3.1E!.2E2.2E2..0E40E3.7E7.
.!..2S...2S.1S..2G..1F...!....!..!..9G..
1H...!....!..!...!...!...............!..
2W3.....................................
4Zz.....................................
```
| ch | tag | section | how |
|---|---|---|---|
| 1 | `0` | strings | saw, lowpass cutoff from velocity, slow envelope; the hall hangs under its `!` |
| 2 | `1` | low strings | saw, lowpass 3/3, soft clip |
| 3 | `2` | brass | a saw-envelope modulator column drives `X`, FM index from velocity |
| 4 | `3` | horns | narrow pulse, lowpass, soft clip |
| 5 | `4` | flute | breath noise (highpass, short env, gain) beside a sine |
| 6 | `5` | reed | pulse width 1, resonant bandpass, fold |
| 7 | `6` | clarinet | square, lowpass |
| 8 | `7` | timpani | sine-envelope modulator into `X`, long release, play C1 to C2 |
| 9 | `8` | harp | lowpass cutoff key-tracked, resonance random per pluck, pluck envelope |
| 10 | `9` | choir | triangle, lowpass cutoff from register `k`, very slow envelope |
Load it with `galdr examples/orchestra.galdr`, or over UDP with `#orchestra`
once it sits in the patches directory. `;k5` and `;z3` from orca set the choir
brightness and the hall feedback live.
## Stems
`--stems n` adds one mono PipeWire node per channel tag, `galdr.ch0` …
`galdr.ch<n-1>` (named by the tag glyph), each carrying the dry sum of that
channel's voices, sample-aligned with the master. They are not auto-connected:
patch them in a graph tool, or record them with a multitrack recorder such as
`stems`, and master the session later.
## UDP control from orca
galdr listens on UDP port 49161 (orca's `;` operator sends there). One
datagram carries one or more commands, separated by whitespace or `;`:
| command | effect |
|---|---|
| `k7` | set register `k` to 7 of 35 (base-36), read by `k` param cells |
| `*3` | bang column 3: a 5 ms gate on that column alone, no note needed (drums, plucks) |
| `@34O` | write glyph `O` at column 3, row 4 — orca patches the synth itself |
| `@34.` | clear that cell |
| `#verse` | load the patch `verse.galdr` from the patches directory |
Bangs and register sets go straight into the audio thread; grid commands are
applied by the editor and recompiled like a keystroke.
## Patches
A patch is plain text: one line per row, `.` for an empty cell, `#` lines
ignored. `galdr file.galdr` opens one and `Ctrl+S` writes it back. With no
file, galdr works on **today's** patch, `~/hometaping/<YYYY-MM-DD>.galdr`:
opened at start if it exists, written by `Ctrl+S`. That is the same dated
naming as the orca files kept in that directory, so a session's orca pattern
and its galdr voice sort together. `#name` over UDP loads
`<patches dir>/name.galdr` (`--patches` overrides the directory).
Example — a filtered saw with an envelope:
```
A saw at the note pitch
L lowpass, cutoff from the digit to its left...
E ...enveloped by the note gate
! ...to master
```
## Usage
```
galdr [--latency <frames>] [--poly <n>] [--rate <hz>] [--stems <n>]
[--udp <port>] [--patches <dir>] [--midi-name <s>] [patch.galdr]
```
galdr creates a virtual MIDI input port named **galdr**; connect orca or any
sequencer/DAW to it. `--midi-name` connects to an existing port instead.
Keys: cursor keys move; type a glyph to place it; Backspace clears; `Ctrl+S`
saves the patch; `?` toggles help; Esc quits. Resize the grid with `(` / `)` (width) and `_` / `+` (height)
by one ruler division, or Ctrl+arrows by one cell (orca's bindings).
galdr survives a PipeWire restart: the status line shows `PIPEWIRE DOWN —
reconnecting…` and the stream comes back on its own, voices and master-chain
state intact.
Runtime deps: PipeWire and ALSA (MIDI). Built in Rust; reuses the native
PipeWire path from spectrust. `GALDR_DEBUG=<file>` appends every event that
reaches the engine, handy when orca and galdr disagree.