Channel tags, stems, master chain, FM, K envelope, slide, UDP control, reconnect
Grid grammar: - a base-36 glyph above a column's top operator tags it with a MIDI channel; '>' right of the tag makes the channel mono-legato (60 ms slide) - operators below a '!' form the stereo master chain: Z delay, W chorus, plus any filter/shaper - X: phase modulation by the cell to the left (and above), index from the right - K: control envelope; left of an operator it drives that operator's left param (2KL5 = 303 filter sweep) - per-note params: @ velocity, ^ key tracking, ? random; registers now read on the digit scale, so MIDI CC and UDP registers have full range I/O: - MIDI keeps the channel nibble - UDP (orca's ';', port 49161): k7 register, *3 bang a column, @34O write a glyph, #name load a patch - --stems n: one mono PipeWire node per channel tag (galdr.ch<tag>), sample-aligned with the master; separate nodes because a playback stream's AUX ports deliver no data in PipeWire 1.6 - the audio thread reconnects when PipeWire restarts; status line shows it - patches: ~/hometaping/<date>.galdr by default, .galdr extension, Ctrl+S, GALDR_DEBUG=<file> logs events Examples with render tests: drums, orchestra, acid. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b53fdd5a6d
commit
4bf13a2bf0
@@ -1,14 +1,14 @@
|
||||
# galdr
|
||||
|
||||
A terminal **grid synthesizer**. Signal flows downward through a field of
|
||||
single-glyph unit generators; every incoming MIDI note voices the whole grid;
|
||||
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.
|
||||
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/OSC/UDP──▶ galdr ──PipeWire──▶ speakers
|
||||
compose the pattern voice it (spectrust -p visualizes it)
|
||||
orca-rs ──MIDI + UDP──▶ galdr ──PipeWire──▶ speakers / stems
|
||||
compose the pattern voice it (spectrust -p visualizes it)
|
||||
```
|
||||
|
||||
## The grid
|
||||
@@ -32,11 +32,179 @@ a constant parameter; a lowercase letter `a`–`z` reads MIDI CC (`a` = CC 0).
|
||||
| `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.
|
||||
A note plays every column at once.
|
||||
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:
|
||||
|
||||
@@ -47,22 +215,24 @@ E ...enveloped by the note gate
|
||||
! ...to master
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
galdr [--latency <frames>] [--poly <n>] [--rate <hz>] [--midi-name <s>]
|
||||
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; `?` toggles
|
||||
help; Esc quits. Resize the grid with `(` / `)` (width) and `_` / `+` (height)
|
||||
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.
|
||||
PipeWire path from spectrust. `GALDR_DEBUG=<file>` appends every event that
|
||||
reaches the engine, handy when orca and galdr disagree.
|
||||
|
||||
Reference in New Issue
Block a user