You are generating Pure Data (.pd) patch files — plain-text files that define audio processing graphs. These files open in both plugdata and vanilla Pure Data.
The .pd File Format
Every patch is a sequence of semicolon-terminated lines. The format is deliberately simple so it can be generated by hand or by code.
Minimal Patch
#N canvas 0 0 450 300 12;
#X obj 100 50 osc~ 440;
#X obj 100 120 *~ 0.5;
#X obj 100 200 dac~;
#X connect 0 0 1 0;
#X connect 1 0 2 0;
#X connect 1 0 2 1;
This creates a 440Hz sine oscillator at half volume, output to both audio channels.
Core Syntax
| Line Type | Format | Purpose |
|---|---|---|
| Canvas | #N canvas X Y W H FONTSIZE; |
Define patch window (first line) |
| Object | #X obj X Y TYPE ARGS; |
Place an object at (X,Y) |
| Message | #X msg X Y CONTENT; |
Place a message box |
| Comment | #X text X Y CONTENT; |
Place a text comment |
| Connect | #X connect SRC SRC_OUTLET DST DST_INLET; |
Wire two objects together |
| Array | #X array NAME SIZE float SAVE; |
Declare a named array |
| Array data | #A 0 V0 V1 V2 ...; |
Array sample values |
| Subpatch start | #N canvas 0 0 W H NAME 0; |
Begin nested subpatch |
| Subpatch end | #X restore X Y pd NAME; |
End subpatch, place on parent |
Object Indexing
Every #X obj, #X msg, #X text, #X floatatom, #X symbolatom and #X restore line gets a sequential 0-based index on its canvas. Comments count. #X connect lines reference these indices. #X connect, #X coords, #X array, #X declare and #A lines do NOT get indices.
#X text 50 10 sine at half volume; // index 0 — comments take an index too
#X obj 50 30 osc~ 440; // index 1
#X obj 50 80 *~ 0.5; // index 2
#X obj 50 130 dac~; // index 3
#X connect 1 0 2 0; // osc~ out0 -> *~ in0
#X connect 2 0 3 0; // *~ out0 -> dac~ in0
If you skip comments when counting, every later connection shifts. In the console that shows up as (text->...) connection failed or audio signal outlet connected to nonsignal inlet (ignored).
Signal vs Control
Objects ending in ~ process audio-rate signals. Objects without ~ process control-rate messages (bang, float, symbol, list). You cannot directly connect signal outlets to control inlets or vice versa. Use sig~ to convert control to signal, or snapshot~ to convert signal to control.
Patch Generation Workflow
When generating a patch, follow this process:
1. Understand the Goal
Determine what kind of audio processing the user wants:
- Synthesizer — oscillators + envelopes + filters + output
- Effect processor — audio input (adc~) -> processing -> output (dac~)
- MIDI processor — notein/ctlin -> logic -> noteout/ctlout
- Utility/analysis — measurement, conversion, routing
- DAW plugin — use param/playhead objects for host integration
- Embedded (Daisy) — restrict to hvcc-compatible objects only
2. Design the Signal Flow
Sketch the signal path from source to destination:
- What generates the sound? (oscillators, noise, samples, audio input)
- What transforms it? (filters, effects, envelopes, math)
- Where does it go? (dac~, send~, array, MIDI out)
- What controls it? (MIDI, GUI, parameters, logic)
3. Choose Objects
Read references/objects.md for the full object catalog. Key categories:
- Oscillators:
osc~,phasor~,noise~,saw~,tri~,pulse~,bl.saw~(bandlimited) - Filters:
lop~,hip~,bp~,vcf~,biquad~,svfilter~ - Effects:
free.rev~,plate.rev~,chorus~,flanger~,phaser~,drive~,compress~ - Envelopes:
line~,vline~,adsr~,envgen~,decay~ - Math:
+~,*~,-~,/~,clip~,wrap~,expr~ - MIDI:
notein,noteout,ctlin,ctlout,mtof,ftom - I/O:
adc~,dac~,notein,noteout - Timing:
metro,delay,pipe,tempo - GUI:
knob,nbx,tgl,bng,vsl,hsl,scope~
For compilation targets (Daisy, DPF plugin), read references/hvcc.md — only a subset of vanilla objects + heavylib are supported.
4. Generate the .pd File
Use this template pattern:
Build an array of objects, each with {type, x, y, args}
Build an array of connections, each with {src, src_outlet, dst, dst_inlet}
Emit #N canvas header
Emit #X obj / msg / text lines in order (each one, comments included, takes the next index)
Emit #X connect lines using tracked indices
Positioning Guidelines
- Vertical flow: Y increments of 50-70 pixels per object
- Parallel paths: X offsets of 100-150 pixels
- Signal path goes top-to-bottom (source -> processing -> output)
- Control/modulation paths branch horizontally
- Start X at ~50-100, start Y at ~30-50
Common Patch Patterns
Monophonic Synthesizer
#N canvas 0 0 600 500 12;
#X obj 100 30 notein;
#X obj 100 80 mtof;
#X obj 100 130 osc~;
#X obj 250 30 notein;
#X obj 250 80 stripnote;
#X obj 250 130 adsr~ 200 50 0.7 300;
#X obj 100 200 *~;
#X obj 100 270 lop~ 2000;
#X obj 100 350 *~ 0.5;
#X obj 100 420 dac~;
#X connect 0 0 1 0;
#X connect 1 0 2 0;
#X connect 2 0 6 0;
#X connect 3 0 4 0;
#X connect 4 0 5 0;
#X connect 5 0 6 1;
#X connect 6 0 7 0;
#X connect 7 0 8 0;
#X connect 8 0 9 0;
#X connect 8 0 9 1;
Audio Effect (DAW plugin)
#N canvas 0 0 600 400 12;
#X obj 100 50 adc~;
#X obj 100 120 lop~ 1000;
#X obj 100 190 *~ 0.8;
#X obj 100 260 dac~;
#X connect 0 0 1 0;
#X connect 1 0 2 0;
#X connect 2 0 3 0;
#X connect 2 0 3 1;
Parameter with DAW Automation
#X obj 200 50 param cutoff 1000;
#X obj 200 100 sig~;
#X obj 100 150 lop~;
The param object exposes a "cutoff" parameter to the DAW's automation system.
Subpatch (Abstraction)
#N canvas 0 0 300 200 mySubpatch 0;
#X obj 50 30 inlet~;
#X obj 50 100 *~ 0.5;
#X obj 50 170 outlet~;
#X connect 0 0 1 0;
#X connect 1 0 2 0;
#X restore 100 200 pd mySubpatch;
Exposing Parameters for hvcc Compilation
#X obj 200 50 r gain @hv_param 0 1 0.5;
Format: [r NAME @hv_param MIN MAX DEFAULT [TYPE]]
Types: float, bool, int, trig, dB, Hz, log, log_hz
Key Constraints
-
Every line ends with
;— missing semicolons break the file -
Object indices are 0-based and sequential per canvas —
#X obj,#X msg,#X text(comments!),#X floatatom,#X symbolatomand#X restoreall count -
Signal/control boundary —
~objects only connect to~objects unless bridged -
No spaces in file paths referenced by objects (soundfiler, readsf~, etc.)
-
[dac~]takes channel args:[dac~ 1]= left,[dac~ 1 2]= stereo,[dac~ 1 2 3 4]= quad -
[adc~]similarly takes channel args for audio input -
hvcc compilation only supports vanilla + heavylib objects (~323 total) — no ELSE, Gem, Cyclone, or pdlua
-
Send/receive (
s/r,send/receive) provides wireless connections — useful for clean layouts and parameter distribution -
Gem in plugdata is built in, but its classes are registered only as
Gem/<name>. Use[Gem/gemwin],[Gem/gemhead],[Gem/pix_film]and so on. The other option is a canvas-level#X declare -lib Gem;line, which makes the plain names global for the whole session and replaces ELSE objects with the same name (e.g.[circle]). A#X obj ... declare -lib Gem;box on its own loads nothing (seereferences/pd-format.md→ Declaration) -
Gem video in plugdata: plugdata runs Gem on the audio thread.
[pix_film]'s default AVF backend rebuilds its AVAssetReader on every frame, which measured about 64 ms per frame for 1080p H.264, so 30 fps playback pins the CPU at 100%. Play a light copy through the ffmpeg backend instead (about 1 ms per frame):ffmpeg -i in.mov -map 0:v:0 -an -vf "scale=960:540,setpts=PTS-STARTPTS" -fps_mode cfr -r 30 \ -c:v libx264 -crf 21 -g 1 -bf 0 -pix_fmt yuv420p -video_track_timescale 15360 out_540p.movThen send
[backend ffmpeg AVF, open out_540p.mov RGBA(to[Gem/pix_film]. The flags matter:-g 1 -bf 0makes every frame a keyframe, so per-frame seeks are exact.- Gem's ffmpeg backend seeks to
frame * timescale / fps, so keep the timescale a multiple of the frame rate. -anandsetptsstop an audio track or a video start offset from skewing Gem's frame numbering. Gem counts frames over the container duration, which the audio track can lengthen.
Check that the frame count in the console's
loaded fileline matches the video's.
Reference Files
Read these when you need deeper detail:
references/pd-format.md— Complete .pd syntax specification with all element types, subpatches, arrays, and GUI objectsreferences/objects.md— Full object catalog organized by category (1,653 objects across 8 libraries)references/else.md— ELSE library reference (595 objects): oscillators, filters, effects, modules, GUI, MIDI, tuning, sequencing, and a Cyclone migration tablereferences/cyclone.md— Cyclone library reference (206 objects): Max/MSP compatibility layer, non-alphanumeric operators,zlmodes, and vanilla Pd differencesreferences/hvcc.md— Heavy compiler reference: supported objects, compilation targets, parameter annotations, and constraintsreferences/pd-lua.md— pd-lua API for writing custom Pd objects in Lua (inlets, outlets, DSP, GUI, clocks, arrays)
Library Selection Guide
For new patches, prefer this order: ELSE > vanilla > Cyclone.
- ELSE — Active development, more features, better plugdata integration. Use for oscillators (
saw~,bl.saw~), effects (free.rev~,chorus~), envelopes (adsr~), MIDI (note.in), GUI (knob,scope~) - Cyclone — Use only when porting Max/MSP patches or when you need a specific Max object not available elsewhere
- Vanilla — Use for core objects (
osc~,*~,dac~,metro,send/receive) and when targeting hvcc compilation
When to Use pd-lua
If the user needs custom logic that doesn't exist as a built-in object, pd-lua lets you write objects in Lua:
local myobj = pd.Class:new():register("myobj")
function myobj:initialize(sel, atoms)
self.inlets = 1
self.outlets = 1
return true
end
function myobj:in_1_float(x)
self:outlet(1, "float", {x * 2})
end
Save as myobj.pd_lua in the patch directory, then use [myobj] in the patch. Read references/pd-lua.md for the full API.
Checklist Before Writing a Patch
Before writing the .pd file, verify:
- Does the signal path flow from source to output?
- Are signal (
~) and control domains properly separated? - Are object indices correct for all connections, counting comments and subpatch
#X restorelines? - Is every line terminated with
;? - Does the first line declare a canvas?
- If targeting compilation, are all objects hvcc-compatible?
- If it's a DAW plugin, does it use
adc~/dac~(for effects) or MIDI objects (for instruments)? - Are positions reasonable for visual readability?