2026-09-13 13:24:18 +02:00
|
|
|
|
# galdr
|
|
|
|
|
|
|
|
|
|
|
|
A terminal **grid synthesizer**. Signal flows downward through a field of
|
2026-10-08 18:44:40 +02:00
|
|
|
|
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.
|
2026-09-13 13:24:18 +02:00
|
|
|
|
|
|
|
|
|
|
```
|
2026-10-08 18:44:40 +02:00
|
|
|
|
orca-rs ──MIDI + UDP──▶ galdr ──PipeWire──▶ speakers / stems
|
|
|
|
|
|
compose the pattern voice it (spectrust -p visualizes it)
|
2026-09-13 13:24:18 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## The grid
|
|
|
|
|
|
|
|
|
|
|
|
Signal flows **down**: each cell takes the sample from the cell above as its
|
2026-09-13 14:06:12 +02:00
|
|
|
|
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).
|
2026-09-13 13:24:18 +02:00
|
|
|
|
|
2026-09-13 14:06:12 +02:00
|
|
|
|
| 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 |
|
2026-10-08 18:44:40 +02:00
|
|
|
|
| `X` | FM: sine at the note pitch, phase pushed by the cell to its **left** (and above) | — / index |
|
2026-09-13 14:06:12 +02:00
|
|
|
|
| `M` | mix — adds the cell to its **left** | — |
|
2026-10-08 18:44:40 +02:00
|
|
|
|
| `K` | control envelope: 1 at note-on, decays to a floor; no audio input | decay / floor |
|
2026-09-13 14:06:12 +02:00
|
|
|
|
| `!` | output to master | — |
|
2026-09-13 13:24:18 +02:00
|
|
|
|
|
2026-10-08 18:44:40 +02:00
|
|
|
|
`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.
|
|
|
|
|
|
|
2026-09-13 13:24:18 +02:00
|
|
|
|
Each `!` sums into the master, so parallel columns stack timbres or voices.
|
2026-10-08 18:44:40 +02:00
|
|
|
|
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).
|
2026-09-13 13:24:18 +02:00
|
|
|
|
|
|
|
|
|
|
Example — a filtered saw with an envelope:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-09-13 14:06:12 +02:00
|
|
|
|
A saw at the note pitch
|
|
|
|
|
|
L lowpass, cutoff from the digit to its left...
|
|
|
|
|
|
E ...enveloped by the note gate
|
2026-09-13 13:24:18 +02:00
|
|
|
|
! ...to master
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-10-08 18:44:40 +02:00
|
|
|
|
galdr [--latency <frames>] [--poly <n>] [--rate <hz>] [--stems <n>]
|
|
|
|
|
|
[--udp <port>] [--patches <dir>] [--midi-name <s>] [patch.galdr]
|
2026-09-13 13:24:18 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
2026-09-13 16:16:32 +02:00
|
|
|
|
|
2026-10-08 18:44:40 +02:00
|
|
|
|
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)
|
2026-09-13 16:16:32 +02:00
|
|
|
|
by one ruler division, or Ctrl+arrows by one cell (orca's bindings).
|
2026-09-13 13:24:18 +02:00
|
|
|
|
|
2026-10-08 18:44:40 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-09-13 13:24:18 +02:00
|
|
|
|
Runtime deps: PipeWire and ALSA (MIDI). Built in Rust; reuses the native
|
2026-10-08 18:44:40 +02:00
|
|
|
|
PipeWire path from spectrust. `GALDR_DEBUG=<file>` appends every event that
|
|
|
|
|
|
reaches the engine, handy when orca and galdr disagree.
|