Version: 0.1.0 Status: Draft
PatchLang is a domain-specific language for describing signal flow in broadcast and live production environments. It defines device templates, physical instances, cable connections, logical signal mappings, and channel configuration. PatchLang files (.patch) are the source of truth — they are human-readable, git-diffable, and designed for LLM generation.
Lines starting with # are comments. Comments extend to the end of the line.
comment = "#" { any-char-except-newline } ;# This is a comment
template Amp { } # inline comments work too
Spaces, tabs, carriage returns, and newlines are whitespace. Whitespace separates tokens but is otherwise insignificant. There is no significant indentation.
identifier = ( letter | "_" ) { letter | digit | "_" } ;
letter = "A".."Z" | "a".."z" ;
digit = "0".."9" ;Identifiers name templates, instances, ports, signals, and properties. They must start with a letter or underscore. Hyphens are not allowed — use underscores.
FOH_Console (* valid *)
_80_ch_Splitter (* valid — leading underscore for names starting with digits *)
Stage_Left (* valid *)
my-device (* INVALID — no hyphens *)
The following identifiers are reserved keywords:
template instance is connect bridge bridge_group link_group
signal flag stream config ports meta in out io
for over generate use slot routing route bus label
Keywords can be used as property keys (see §3.10) but not as names for templates, instances, or ports.
@suppress @version
number = "0" | ( digit-nonzero { digit } ) ;
string-literal = '"' { any-char-except-quote } '"' ;
digit-nonzero = "1".."9" ;-> .. . { } ( ) [ ] : , *
A PatchLang file is a sequence of top-level statements.
program = { statement } ;
statement = template-decl | instance-decl | connect-decl | bridge-decl
| bridge-group-decl | link-group-decl | signal-decl | flag-decl
| stream-decl | config-decl | use-decl ;Defines a reusable device type.
template-decl = "template" identifier [ param-list ] [ version-annotation ]
"{" { template-block } "}" ;
template-block = meta-block | ports-block | template-bridge
| template-instance | template-connect | slot-def ;
param-list = "(" param-def { "," param-def } ")" ;
param-def = identifier ":" ( number | string-literal ) ;
version-annotation = "@version" "(" string-literal ")" ;template Rio3224(mic_count: 32) @version("2.0") {
meta {
manufacturer: "Yamaha"
model: "Rio3224"
category: "Stagebox"
}
ports {
Dante_Pri: io(etherCON) [Dante, primary]
Dante_Sec: io(etherCON) [Dante, secondary]
Mic_In[1..32]: in(XLR)
Line_Out[1..16]: out(XLR)
}
bridge Mic_In -> Dante_Pri
slot MY_Slot[1..3]: MY_Card
}
Key-value metadata inside a template.
meta-block = "meta" "{" { key-value-pair } "}" ;Standard keys: manufacturer, model, category. Custom keys are allowed.
Defines the device's physical interfaces.
ports-block = "ports" "{" { port-def } "}" ;
port-def = identifier [ range-spec ] ":" port-direction
[ connector-spec ] [ attribute-list ] ;
range-spec = "[" number ".." number "]" ;
port-direction = "in" | "out" | "io" ;
connector-spec = "(" identifier ")" ;
attribute-list = "[" attribute { "," attribute } "]" ;
attribute = identifier [ ":" identifier ] ;Mic_In[1..32]: in(XLR) # 32 XLR inputs
Dante_Pri: io(etherCON) [Dante, primary] # bidirectional with attributes
Mix_Bus[1..24]: out # outputs, no connector specified
SDI[1..72]: io(SDI_BNC) [SDI] # with protocol attribute
MADI[1..64]: io(SC_Fiber) [MADI, redundant] # with named attribute
Common connector identifiers: XLR, BNC_75, RJ45, etherCON, SFP, LC_Fiber, SC_Fiber, MTRJ_Fiber, SpeakON, TRS_14, TRS_3, DB25, HDMI, SDI_BNC, USB, SMA.
Custom connector names are allowed.
Attributes describe transport protocols and properties: Dante, AES67, MADI, AES3, SDI, NDI, SMPTE2110, Analogue, WordClock, Ethernet, primary, secondary, redundant, Gigabit.
Named attributes use key: value syntax: [protocol: SDI, format: UHD].
Creates a physical device from a template.
instance-decl = "instance" identifier "is" identifier
[ arg-list ] [ version-constraint ] [ instance-body ] ;
arg-list = "(" arg-def { "," arg-def } ")" ;
arg-def = identifier ":" ( number | string-literal ) ;
version-constraint = "@version" "(" string-literal ")" ;
instance-body = "{" { instance-entry } "}" ;
instance-entry = route-entry | bus-entry | slot-assignment | key-value-pair ;instance Stage_Left is Rio3224 {
location: "Stage Left Wing"
ip: "192.168.1.31"
}
instance FOH_Console is CL5(mic_count: 48) @version(">=4.0") {
location: "Front of House"
route Dante_In[1] -> Fader[1]
route Dante_In[2] -> Fader[2]
bus Main_LR {
input: Fader[1..8]
output: Matrix_Out[1..2]
}
slot MY_Slot[1]: "Dante_Card"
}
Defines a physical cable between two ports.
connect-decl = "connect" port-ref "->" port-ref [ connect-body ] ;
connect-body = "{" [ suppress-annotation ] { key-value-pair } "}" ;
suppress-annotation = "@suppress" "(" identifier { "," identifier } ")" ;connect Stage_Left.Dante_Pri -> Dante_Switch.Port[1] {
cable: "Cat6a_SL_Pri"
length: "30m"
}
connect SL.Analog_Out[1..4] -> FOH.Line_In[1..4] {
@suppress(protocol_mismatch)
mapping: "1:1"
}
The mapping property specifies channel-level routing. Three formats:
mapping: "1:1" # sequential one-to-one (default)
mapping: "offset 16" # shifted by N channels
mapping: "1->3, 2->4, 3->1" # explicit per-channel pairs
Logical signal mapping between ports (no physical cable).
bridge-decl = "bridge" port-ref "->" port-ref ;Inside templates, port references can be local (no instance prefix):
template-bridge = "bridge" port-ref-or-local "->" port-ref-or-local ;# Top-level (between instances)
bridge Stage_Left.Mic_In[1..32] -> FOH_Console.Dante_Ch[1..32]
# Inside a template (local port names)
template Stagebox {
ports { Mic_In[1..16]: in(XLR) Network: io(etherCON) [Dante] }
bridge Mic_In -> Network
}
Sequential channel mapping — multiple sources auto-fill a destination range.
bridge-group-decl = "bridge_group" port-ref "{" { port-ref } "}" ;bridge_group FOH.Dante_Ch {
SL.Mic_In[1..4] # maps to Ch[1..4]
SR.Mic_In[1..4] # maps to Ch[5..8]
}
Groups connections as a logical unit.
link-group-decl = "link_group" identifier "{" { connect-decl | key-value-pair } "}" ;link_group Cam1_UHD {
connect Cam1.SDI_Out[1] -> Router.SDI_In[1]
connect Cam1.SDI_Out[2] -> Router.SDI_In[2]
connect Cam1.SDI_Out[3] -> Router.SDI_In[3]
connect Cam1.SDI_Out[4] -> Router.SDI_In[4]
mode: "quad_link_4K"
}
signal-decl = "signal" identifier [ "{" { key-value-pair } "}" ] ;
flag-decl = "flag" identifier [ "{" { key-value-pair } "}" ] ;
stream-decl = "stream" identifier [ "{" { key-value-pair } "}" ] ;The origin property in signals and source property in streams accept port references as values.
signal Lead_Vocal {
origin: Stage_Left.Mic_In[1]
channel: "1"
description: "Worship leader vocal"
}
stream SL_Dante_Primary {
source: Stage_Left.Dante_Pri
channels: "32"
protocol: "Dante"
}
flag Genlock_OK {
description: "All cameras locked to house sync"
severity: "warning"
}
Per-instance channel labels and metadata.
config-decl = "config" identifier "{" { config-label } "}" ;
config-label = "label" port-ref-or-local ":" string-literal
[ "{" { key-value-pair } "}" ] ;config FOH_Console {
label Dante_In[1]: "Pastor Mic" {
phantom: "true"
stand: "tall boom"
}
label Dante_In[2]: "Worship Leader" {
phantom: "true"
}
label Fader[1]: "Lead Vocal"
}
Imports templates from a library namespace.
use-decl = "use" namespace [ "." "*" ] [ "{" identifier { "," identifier } "}" ] ;
namespace = identifier { "." identifier } ;use audio.yamaha { CL5, Rio3224 }
use video.blackmagic.*
use infrastructure.dante
slot-def = "slot" identifier [ range-spec ] ":" identifier ;slot MY_Slot[1..3]: MY_Card
slot Expansion[1..8]: Expansion
route-entry = "route" port-ref-or-local "->" port-ref-or-local ;bus-entry = "bus" identifier "{" { bus-port-entry } "}" ;
bus-port-entry = ( "input" | "output" | "in" | "out" ) ":" port-ref-or-local ;slot-assignment = "slot" identifier [ "[" number "]" ] ":" string-literal ;port-ref = identifier "." identifier [ index-spec ] ;
port-ref-or-local = identifier [ "." identifier ] [ index-spec ] ;A port-ref is always fully qualified: Instance.Port. A port-ref-or-local is used inside templates and instance bodies where the instance prefix is optional.
index-spec = "[" index-element { "," index-element } "]" ;
index-element = number [ ".." number ] | "auto" ;Supports single indices, ranges, mixed, and auto-assignment:
[1] # single channel
[1..32] # range from 1 to 32
[1..4,7,9] # mixed: channels 1,2,3,4,7,9
[auto] # auto-assign contiguous channels at compile time
[auto] keyword: When used on one side of a connection, the compiler allocates the next N contiguous available channels from the port's declared range, where N is inferred from the other side. Channels are allocated in declaration order and skip explicitly claimed indices. [auto] must be the sole element in the index spec (cannot mix with numbers). Both sides of a connection cannot use [auto] simultaneously. [auto] is not valid in route or bus declarations. Referencing a vector port without any index spec emits a warning (S14).
key-value-pair = property-key ":" property-value ;
property-key = identifier | "label" | "stream" | "route" | "bus"
| "routing" | "config" ;
property-value = string-literal | number | port-ref ;Keywords can be used as property keys. Values can be strings, numbers, or port references.
# Worship venue — Dante audio network
template Rio3224 {
meta {
manufacturer: "Yamaha"
model: "Rio3224"
category: "Stagebox"
}
ports {
Dante_Pri: io(etherCON) [Dante, primary]
Dante_Sec: io(etherCON) [Dante, secondary]
Mic_In[1..32]: in(XLR)
Line_Out[1..16]: out(XLR)
}
bridge Mic_In -> Dante_Pri
}
template CL5 {
meta {
manufacturer: "Yamaha"
model: "CL5"
category: "Console"
}
ports {
Dante_Pri: io(etherCON) [Dante, primary]
Dante_Sec: io(etherCON) [Dante, secondary]
Dante_Ch[1..72]: in [Dante]
Mix_Bus[1..24]: out
}
}
instance Stage_Left is Rio3224 {
location: "Stage Left Wing"
ip: "192.168.1.31"
}
instance FOH_Console is CL5 {
location: "Front of House"
ip: "192.168.1.10"
}
connect Stage_Left.Dante_Pri -> FOH_Console.Dante_Pri {
cable: "Cat6a_SL_Pri"
length: "30m"
}
bridge Stage_Left.Mic_In[1..32] -> FOH_Console.Dante_Ch[1..32]
signal Lead_Vocal {
origin: Stage_Left.Mic_In[1]
channel: "1"
description: "Worship leader vocal"
}
config FOH_Console {
label Dante_Ch[1]: "Lead Vocal" { phantom: "true" }
label Dante_Ch[2]: "Kick Drum"
}
- Extension:
.patch - Encoding: UTF-8
- Line endings: LF or CRLF (both accepted)
- Layout sidecar:
.layout.jsonstores canvas positions and UI state (not part of PatchLang) - Config files:
.config.patchcan holdconfigblocks imported viause
- Human-readable first. A broadcast engineer should be able to read a
.patchfile and understand the signal chain without special tooling. - LLM-friendly. The syntax is simple enough that language models can generate valid
.patchfiles from plain English descriptions. - Git-diffable. Text diffs show meaningful changes. Adding a mic input is one line, not a JSON blob.
- No ambiguity. Every statement starts with a unique keyword. The grammar is LL(1).
- Domain-specific. The language models broadcast concepts (ports, connectors, protocols, signal chains) directly — not through generic data structures.