Bendik Aagaard LynghaugandClaude Fable 5.1 e9836c2467 feat: modular glyphs — T I Q J P Y and control maths punctuation
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>
2026-10-19 22:00:13 +02:00
2026-10-08 18:44:45 +02:00
2026-10-08 18:44:45 +02:00
2026-10-08 18:44:45 +02:00

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.

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.

S
Description
Terminal grid synthesizer: signal flows through single-glyph unit generators, MIDI in, low-latency PipeWire out
Readme MIT
162 KiB
v0.4.0
Latest
2026-10-19 20:00:15 +00:00
Languages
Rust 99.3%
Shell 0.7%