Problem
The frontend emitter generates routes like this for RF devices (AD4Q, QLXD4, PSM1000):
instance Vox_1_4 is AD4Q {
route __rf_receive__[1] -> XLR_Out[1]
route __rf_receive__[2] -> XLR_Out[2]
route __rf_receive__[3] -> XLR_Out[3]
route __rf_receive__[4] -> XLR_Out[4]
route __rf_receive__[1] -> TRS[1]
route __rf_receive__[2] -> TRS[2]
route __rf_receive__[3] -> TRS[3]
route __rf_receive__[4] -> TRS[4]
route __rf_receive__[1] -> DANTE[1]
route __rf_receive__[2] -> DANTE[2]
route __rf_receive__[3] -> DANTE[3]
route __rf_receive__[4] -> DANTE[4]
}
instance P10T_01 is PSM1000 {
route Input[1] -> __rf_transmit__[1]
route Input[2] -> __rf_transmit__[2]
}
__rf_receive__ and __rf_transmit__ are not declared as ports on any template. The compiler flags every one of these as an S04 error: "Route references port 'rf_receive' does not exist on template 'AD4Q'."
This is 393 errors on the Hillsong MTG project — the single largest error category (64% of all DRC errors).
Why this is a design question, not just a bug
There are several valid ways to model RF signal paths, each with different trade-offs. The choice affects the language spec, not just the compiler implementation.
Option A: Declare virtual ports on RF templates
Add __rf_receive__ / __rf_transmit__ as real ports in each RF template definition:
template AD4Q {
meta { kind: "rf-system", rf_subtype: "radio-mic", rf_min_channels: 4 }
ports {
__rf_receive__[1..4]: in(Wireless) [RF]
XLR_Out[1..4]: out(XLR) [Analogue, AES3]
TRS[1..4]: out(TRS_3) [Analogue]
DANTE_In[1..4]: in(RJ45_etherCON) [Dante]
DANTE_Out[1..4]: out(RJ45_etherCON) [Dante]
}
}
Pros:
- No compiler changes needed — existing S04 check passes
- Explicit and self-documenting
- Works with existing
route semantics
Cons:
__rf_receive__ isn't a physical port — there's no connector to plug into. Giving it a connector type (Wireless) is a fiction. The DRC mechanical layer would need to know not to validate connections to wireless "ports."
- Every RF template (AD4Q, QLXD4, PSM1000, ULXD4, etc.) needs these added — both in the stock library and user-created templates
- The
__ naming convention is an emitter implementation detail leaking into the language spec
Option B: Use template-level bridges instead of instance-level routes
An RF receiver's signal path (antenna → output) is manufacturer-hardwired, not operator-configured. That's a bridge, not a route:
template AD4Q {
meta { kind: "rf-system", rf_subtype: "radio-mic" }
ports {
XLR_Out[1..4]: out(XLR) [Analogue, AES3]
TRS[1..4]: out(TRS_3) [Analogue]
DANTE_Out[1..4]: out(RJ45_etherCON) [Dante]
}
# No __rf_receive__ port. The bridge says "this device produces
# output from a wireless source — the DRC shouldn't expect an
# upstream wired connection."
}
The instance would have NO routes for the RF path — just RF channel config labels. The emitter would stop generating route __rf_receive__ lines.
Pros:
- Semantically correct: the signal path IS hardwired by the manufacturer
- No fake ports needed
- Templates stay clean
Cons:
- Loses the explicit
route showing which RF channel maps to which output
- The emitter currently uses routes to model per-instance RF channel→output mapping. Removing them loses information.
- Doesn't address PSM1000 (IEM transmitter) where
Input → __rf_transmit__ represents audio going INTO the wireless transmitter — there the input side IS a real port
Option C: First-class RF channel concept
Add rf_channels as a language-level concept distinct from ports:
template AD4Q {
meta { kind: "rf-system", rf_subtype: "radio-mic", rf_min_channels: 4 }
ports {
XLR_Out[1..4]: out(XLR) [Analogue, AES3]
TRS[1..4]: out(TRS_3) [Analogue]
DANTE_Out[1..4]: out(RJ45_etherCON) [Dante]
}
rf receive[1..4] # <-- new syntax: declares 4 RF receive channels
bridge receive -> XLR_Out
bridge receive -> TRS
bridge receive -> DANTE_Out
}
template PSM1000 {
ports { Input[1..4]: in(XLR) [Analogue] }
rf transmit[1..2] # 2 RF transmit channels (dual-channel transmitter)
bridge Input -> transmit
}
Instance-level config uses __rf_channels__ labels (already emitted correctly):
config Vox_1_4 {
label __rf_channels__[1]: "Vox 1" { source_type: "AD2-B58", rf_band: "K54" }
label __rf_channels__[2]: "Vox 2" { source_type: "AD2-B58", rf_band: "K54" }
}
Pros:
- Semantically richest — RF channels are a real concept, not a port hack
- The compiler already has
kind: "rf-system", rf_subtype, rf_min_channels in meta — this builds on that
- Config labels for
__rf_channels__ already work
- Clean separation: ports are physical connectors, RF channels are wireless
- DRC can validate RF-specific rules (e.g., channel count matches
rf_min_channels)
Cons:
- Largest spec change — new syntax, new AST node, new DRC rules
- Every RF template in the stock library needs updating
- Parser changes required
- YAGNI risk if the simpler options work fine
Option D: Compiler special-cases __rf_*__ ports on rf-system templates
Add a check in S04: if the template has kind: "rf-system" in meta and the port name starts with __rf_, skip validation.
fn is_virtual_rf_port(port_name: &str, template: &TemplateDecl) -> bool {
(port_name == "__rf_receive__" || port_name == "__rf_transmit__")
&& template.meta.iter().any(|kv|
kv.key == "kind" && kv.value_str() == Some("rf-system"))
}
Pros:
- Smallest change — a few lines in
structural.rs
- No spec change, no syntax change, no template changes
- Unblocks the Hillsong project immediately
Cons:
- Couples the compiler to emitter naming conventions (
__rf_receive__ is magic)
- No validation that the RF channel count is correct
- The
__ prefix convention is undocumented — fragile
- Doesn't solve the deeper modeling question, just silences the error
Recommendation
This needs domain input. The choice depends on how important RF modeling is to the product roadmap:
- If RF is a secondary concern → Option D (quick fix) or Option A (explicit but simple)
- If RF is first-class (Connected RF tab — SignalCanvas #55, RF patch sheet views) → Option C gives the richest foundation
Impact
Fixes 393 DRC errors on the Hillsong MTG project (64% of all errors).
Related
- SignalCanvas #55 — "Add Connected RF tab to Patch Sheet view"
- The emitter already generates
__rf_channels__ config labels — whatever solution is chosen should be consistent with that
Problem
The frontend emitter generates routes like this for RF devices (AD4Q, QLXD4, PSM1000):
instance Vox_1_4 is AD4Q { route __rf_receive__[1] -> XLR_Out[1] route __rf_receive__[2] -> XLR_Out[2] route __rf_receive__[3] -> XLR_Out[3] route __rf_receive__[4] -> XLR_Out[4] route __rf_receive__[1] -> TRS[1] route __rf_receive__[2] -> TRS[2] route __rf_receive__[3] -> TRS[3] route __rf_receive__[4] -> TRS[4] route __rf_receive__[1] -> DANTE[1] route __rf_receive__[2] -> DANTE[2] route __rf_receive__[3] -> DANTE[3] route __rf_receive__[4] -> DANTE[4] } instance P10T_01 is PSM1000 { route Input[1] -> __rf_transmit__[1] route Input[2] -> __rf_transmit__[2] }__rf_receive__and__rf_transmit__are not declared as ports on any template. The compiler flags every one of these as an S04 error: "Route references port 'rf_receive' does not exist on template 'AD4Q'."This is 393 errors on the Hillsong MTG project — the single largest error category (64% of all DRC errors).
Why this is a design question, not just a bug
There are several valid ways to model RF signal paths, each with different trade-offs. The choice affects the language spec, not just the compiler implementation.
Option A: Declare virtual ports on RF templates
Add
__rf_receive__/__rf_transmit__as real ports in each RF template definition:template AD4Q { meta { kind: "rf-system", rf_subtype: "radio-mic", rf_min_channels: 4 } ports { __rf_receive__[1..4]: in(Wireless) [RF] XLR_Out[1..4]: out(XLR) [Analogue, AES3] TRS[1..4]: out(TRS_3) [Analogue] DANTE_In[1..4]: in(RJ45_etherCON) [Dante] DANTE_Out[1..4]: out(RJ45_etherCON) [Dante] } }Pros:
routesemanticsCons:
__rf_receive__isn't a physical port — there's no connector to plug into. Giving it a connector type (Wireless) is a fiction. The DRC mechanical layer would need to know not to validate connections to wireless "ports."__naming convention is an emitter implementation detail leaking into the language specOption B: Use template-level bridges instead of instance-level routes
An RF receiver's signal path (antenna → output) is manufacturer-hardwired, not operator-configured. That's a
bridge, not aroute:template AD4Q { meta { kind: "rf-system", rf_subtype: "radio-mic" } ports { XLR_Out[1..4]: out(XLR) [Analogue, AES3] TRS[1..4]: out(TRS_3) [Analogue] DANTE_Out[1..4]: out(RJ45_etherCON) [Dante] } # No __rf_receive__ port. The bridge says "this device produces # output from a wireless source — the DRC shouldn't expect an # upstream wired connection." }The instance would have NO routes for the RF path — just RF channel config labels. The emitter would stop generating
route __rf_receive__lines.Pros:
Cons:
routeshowing which RF channel maps to which outputInput → __rf_transmit__represents audio going INTO the wireless transmitter — there the input side IS a real portOption C: First-class RF channel concept
Add
rf_channelsas a language-level concept distinct from ports:template AD4Q { meta { kind: "rf-system", rf_subtype: "radio-mic", rf_min_channels: 4 } ports { XLR_Out[1..4]: out(XLR) [Analogue, AES3] TRS[1..4]: out(TRS_3) [Analogue] DANTE_Out[1..4]: out(RJ45_etherCON) [Dante] } rf receive[1..4] # <-- new syntax: declares 4 RF receive channels bridge receive -> XLR_Out bridge receive -> TRS bridge receive -> DANTE_Out } template PSM1000 { ports { Input[1..4]: in(XLR) [Analogue] } rf transmit[1..2] # 2 RF transmit channels (dual-channel transmitter) bridge Input -> transmit }Instance-level config uses
__rf_channels__labels (already emitted correctly):config Vox_1_4 { label __rf_channels__[1]: "Vox 1" { source_type: "AD2-B58", rf_band: "K54" } label __rf_channels__[2]: "Vox 2" { source_type: "AD2-B58", rf_band: "K54" } }Pros:
kind: "rf-system",rf_subtype,rf_min_channelsin meta — this builds on that__rf_channels__already workrf_min_channels)Cons:
Option D: Compiler special-cases
__rf_*__ports on rf-system templatesAdd a check in S04: if the template has
kind: "rf-system"in meta and the port name starts with__rf_, skip validation.Pros:
structural.rsCons:
__rf_receive__is magic)__prefix convention is undocumented — fragileRecommendation
This needs domain input. The choice depends on how important RF modeling is to the product roadmap:
Impact
Fixes 393 DRC errors on the Hillsong MTG project (64% of all errors).
Related
__rf_channels__config labels — whatever solution is chosen should be consistent with that