Skip to content

Design: how should PatchLang model RF wireless signal paths? #6

@ByteBard97

Description

@ByteBard97

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions