Six new letters: T LFO (rate/shape, tremolo with an input), I slew limiter, Q pitch quantizer (scale/octaves, transposes the oscillators below it), J stepped random, P phaser (voice or master), Y comparator. Any control op left of an operator feeds its left param, as K does. Punctuation maths with the cell to the left: + - * (signal or constant), % attenuverter, | max, & min, ~ sample-and-hold on a rising trigger. A column's input is now only an operator above it, so a channel tag or param glyph above T/J no longer flips them into VCA mode on silence. Height resize moves from _ + to [ ] since + is a glyph now. examples/modular.galdr: wobble bass, Q arpeggio, J crackle, Y/K pulse. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
348 lines
14 KiB
Markdown
348 lines
14 KiB
Markdown
# 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 |
|
||
| `T` | LFO, 0–1, retriggered per note; with an input it is a tremolo | rate / shape |
|
||
| `J` | stepped random (sample-and-hold noise); with an input it chops it | rate / amount |
|
||
| `I` | slew limiter (lag) | rise / fall |
|
||
| `Q` | pitch quantizer: transposes the oscillators **below** it | scale / octaves |
|
||
| `Y` | comparator: 1 while the input is above the threshold | smoothing / threshold |
|
||
| `P` | 4-stage phaser | rate / depth |
|
||
| `+` `-` `*` | add / subtract / multiply the cell to its **left** | signal or constant |
|
||
| `%` | attenuverter | gain (0 = −1 … 9 = +1) / offset |
|
||
| `\|` `&` | max / min of input and the cell to its **left** (gate OR / AND) | signal or constant |
|
||
| `~` | sample-and-hold: holds the input when the cell to its **left** rises | trigger |
|
||
| `!` | output to master | — |
|
||
|
||
`K`, `T`, `J`, `I`, `Q`, `Y` and the maths glyphs output a *control value*:
|
||
placed to the **left** of another operator any of them 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.
|
||
`4TL5` is the same filter wobbled by an LFO. See [the modular glyphs](#the-modular-glyphs).
|
||
|
||
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 modular glyphs
|
||
|
||
The six letters and the punctuation are the eurorack side of galdr: control
|
||
sources, processors and utilities that patch together through the same
|
||
left-of-an-operator rule as `K`. Everything is on a 0–1 control scale, so the
|
||
pieces combine.
|
||
|
||
| glyph | module | left | right |
|
||
|---|---|---|---|
|
||
| `T` | LFO (0–1, restarts at note-on) | rate: 0 = 0.05 Hz … 9 = 25.6 Hz | shape: 0 sine, 1 tri, 2 saw, 3 ramp down, 4 square, 5 sample-and-hold |
|
||
| `J` | random voltage: a new value every step | rate (as `T`) | amount |
|
||
| `I` | slew limiter / lag processor | rise: 0 = 1 ms … 9 = 0.5 s | fall |
|
||
| `Q` | quantizer | scale (below) | span in octaves |
|
||
| `Y` | comparator | edge smoothing (0 = hard) | threshold, 0–9 = 0–1 |
|
||
| `P` | phaser (voice or master) | rate | depth |
|
||
|
||
`Q` scales: 0 chromatic, 1 major, 2 minor, 3 pentatonic major, 4 pentatonic
|
||
minor, 5 dorian, 6 phrygian, 7 lydian, 8 mixolydian, 9 blues. It reads a 0–1
|
||
control from the cell above, snaps it to the scale and transposes every
|
||
oscillator *below it in the column* by that many semitones. Its own output is
|
||
the snapped value, so it can also drive a param.
|
||
|
||
`T` and `J` with an audio input become VCAs on that input (tremolo, random
|
||
chopping). `Y` on an audio input is a comparator in the eurorack sense: a saw
|
||
into `Y4` is a pulse wave whose width is the threshold.
|
||
|
||
The punctuation is maths with the cell to the **left**. If that cell is an
|
||
operator its raw signal is used; a digit or register is a constant on the
|
||
0–1 scale (`9` = 1).
|
||
|
||
| glyph | out |
|
||
|---|---|
|
||
| `+` | input + left |
|
||
| `-` | input − left (no input: an inverter) |
|
||
| `*` | input × left: a VCA when a control is left of it, a ring mod when an oscillator is |
|
||
| `%` | input × gain + offset, gain from −1 (`0`) through 0 (`4`/`5`) to +1 (`9`) |
|
||
| `\|` | max(input, left) — OR for gates, half-wave rectifier with nothing to the left |
|
||
| `&` | min(input, left) — AND for gates |
|
||
| `~` | sample-and-hold: holds the input whenever the left cell rises through 0.5 |
|
||
|
||
A few patches, each one column plus its helpers:
|
||
|
||
```
|
||
..A. saw
|
||
7TL6 wobble bass: a 6.4 Hz sine LFO sweeps a resonant lowpass
|
||
..!.
|
||
```
|
||
|
||
```
|
||
..1T2 a 0.1 Hz ramp...
|
||
...Q. ...quantized to the major scale over one octave...
|
||
...A. ...transposes this saw: a slow arpeggio from a single held note
|
||
...!.
|
||
```
|
||
|
||
```
|
||
..N. noise
|
||
.9J* random voltage at 25.6 Hz as a VCA: crackle
|
||
..!.
|
||
```
|
||
|
||
```
|
||
...A. saw
|
||
.6T~. a 3.2 Hz LFO to the left of ~ clocks the hold: a stepped saw
|
||
...!.
|
||
```
|
||
|
||
```
|
||
..A..
|
||
..Y4. comparator turns the saw into a pulse
|
||
3K*.. K to the left of * is the VCA: a plucky pulse
|
||
..!..
|
||
```
|
||
|
||
`examples/modular.galdr` strings four of these into a kit, one per MIDI
|
||
channel, with a phaser on the master:
|
||
|
||
```
|
||
..0.....1......2....3..
|
||
..A....2T2.....N....A..
|
||
7TL6...1Q1...9J*....Y4.
|
||
..E3....A.....5B5.3K*..
|
||
..!....4L2.....E2..1H..
|
||
.3P5....E4.....!....!..
|
||
........!..............
|
||
```
|
||
|
||
- **wobble bass** (ch 1): saw, a 6.4 Hz LFO sweeping a resonant lowpass.
|
||
- **arpeggio** (ch 2): a 0.2 Hz ramp through `Q` (major, one octave) climbs the
|
||
saw a scale step every 0.7 s from a single held note.
|
||
- **crackle** (ch 3): noise chopped by `J` at 25.6 Hz through a bandpass.
|
||
- **plucky pulse** (ch 4): `Y` turns the saw into a pulse, `K` left of `*` is
|
||
the VCA, `H` removes the pulse's DC.
|
||
|
||
Watch the tag rule when you stack control ops: a digit directly above `T`,
|
||
`K` or any other operator at the top of its column is read as a channel tag.
|
||
|
||
### 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 |
|
||
| `P` | phaser (also works in a voice) | 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.
|