A strict, deterministic Python-to-Lua compiler and toolchain designed specifically for the Q-SYS Core audio/DSP platform.
qsyspyc allows AV systems programmers to write modern, type-annotated Python to produce clean, idiomatic, and valid Q-SYS Designer .qplug plugins and .lua control scripts.
"Unsupported Python must produce an actionable diagnostic, never an approximation."
Q-SYS Core processors operate mission-critical audio, video, and control engines. Naive transpilation (substituting operators, flattening control flow, or ignoring semantics) can cause silent runtime crashes or corrupt DSP states.
qsyspyc enforces total soundness:
- Truthiness Protection: In Lua,
0and""are truthy. In Python, they are falsy. QSysPy’s semantic engine infers operand types and lowersif ctl.Value:toif (ctl.Value ~= 0) thenandif name:toif (#name > 0) then. - Zero Silent Drops: Chained assignments (
a = b = 1), unsupported operators, and tuple unpacking in for-loops produce clear diagnostics (E0101–E0205) with line and column numbers. - Stable IDs & Fuzzy Member Checks: References like
self.Master.Valueresolve to internal IDs (ControlId,SymbolId), and typos likectl.Valuemit actionable suggestions (did you mean 'Value'?).
The project is structured as a modular Cargo workspace:
crates/
├── qsys_model/ # Authoritative Q-SYS domain model (Controls, Properties, Pin styles, Layout)
├── qsys_diagnostics/ # Diagnostic catalog (E0001–E0205), span tracking, Monaco/LSP markers
├── qsys_parser/ # AST lowering from ruff_python_parser; strict syntax validation
├── qsys_runtime/ # Runtime plugins (PythonCore, LuaStdlib, QSysRuntime): globals, native types, removed builtins
├── qsys_sema/ # Scope management, symbol resolution, stable IDs, type inference
├── qsys_ir/ # Target-agnostic Intermediate Representation (Typed IR)
├── qsys_codegen_lua/ # Q-SYS Lua 5.3 code generator (PluginInfo, GetControls, runtime guard)
├── qsys_compiler/ # Unified orchestration driver (Compiler::compile, Compiler::check)
├── qsyspyc/ # Native CLI binary (build, check, gen-stubs)
└── qsys_wasm/ # WebAssembly bindings for in-browser compilation
web/ # Monaco Editor-based static playground studio
# Clone the repository
git clone https://github.com/Robby-cell/QSysPyC.git
cd QSysPyC
# Build the compiler CLI
cargo build --release -p qsyspyc
# The binary will be available at:
./target/release/qsyspyc --helpcargo test# Automatically derives output name (e.g. mixer.qplug)
qsyspyc build mixer.py
# Or specify a custom output path:
qsyspyc build mixer.py -o dist/MatrixMixer.qplugqsyspyc check mixer.pyIf an error is found, qsyspyc prints formatted diagnostics with line numbers, code snippets, and suggestions:
error[E0201]: Control 'ctl' has no member 'Valu'; did you mean 'Value'? at mixer.py:8:17
= help: Declare controls using Control.<Type>() or check member casing.
qsyspyc gen-stubsThis creates standard PEP 561 stub files (typings/qsys/__init__.pyi, typings/qsys/py.typed, typings/qsys.pyi).
Add this to .vscode/settings.json for full IDE autocompletion:
{
"python.analysis.extraPaths": ["./typings"]
}from qsys import QSysPlugin, Control, Property, ControlHandle, qsys
class MatrixMixer(QSysPlugin):
version = "1.0.0"
author = "Audio Systems Engineering"
description = "8-Channel Matrix Gain Controller"
# Declare component controls with wiring pin configuration
Gain = Control.Float(
name="MasterGain", min=-100.0, max=20.0, default=0.0, pins="Both"
)
Mute = Control.Button(name="MuteAll", type="Toggle", pins="Both")
# Declare properties shown in the Q-SYS Designer properties panel
Channels = Property.Integer(name="ChannelCount", default=8, min=1, max=64)
@Gain.EventHandler
def on_gain(self, ctl: ControlHandle):
"""Fires whenever MasterGain is modified in the UI or via an external pin."""
if ctl.Value:
# Type-inferred: ctl.Value != 0 guard is automatically emitted in Lua
self.Mute.Boolean = False
@Mute.EventHandler
def on_mute(self, ctl: ControlHandle):
# Escape hatch: Execute native Q-SYS Lua socket/library code directly
qsys.lua("print('Mute toggled: ' .. tostring(Controls['MuteAll'].Boolean))")When compiled with qsyspyc build, this produces:
- Complete
PluginInfometadata table. GetProperties()andGetControls(props)definitions matching Q-SYS plugin architecture.- A default
GetControlLayout(props)layout envelope. - Runtime lifecycle code guarded by
if Controls then ... end. - Event handler bindings mapped to
Controls["MasterGain"].EventHandler.
The project includes an in-browser playground powered by Monaco Editor and WebAssembly.
Compile the WASM crate
wasm-pack build crates/qsys_wasm --target web --out-dir ../../web/pkgServe the web folder
cd web
python3 -m http.server 3000Or to serve with npm:
npx serve ./webOpen http://localhost:3000 in any browser.
| Code Range | Category | Examples |
|---|---|---|
| E0001–E0099 | Syntax & Structure | Python syntax errors, unclosed parentheses, missing module body |
| E0101–E0199 | Language Subset Guardrails | Unsupported async/await, generators (yield), match/case, chained assignment |
| E0201–E0299 | Semantic & Q-SYS Binding | Unknown control members (ctl.Valu), missing self, invalid pin style |
We welcome issues, feedback, and pull requests!
- Fork and clone the repository.
- Verify that existing tests pass:
cargo test - Format and lint your changes:
cargo fmt --check cargo clippy --all-targets -- -D warnings
- Strict Lowering: Never add silent fallbacks in the parser or lowering phases. If a Python language feature cannot be represented in Q-SYS Lua with 100% semantic fidelity, emit an explicit diagnostic code in
crates/qsys_diagnostics/src/kind.rs. - ID Preservation: When extending the semantic analyzer or IR, avoid passing raw identifier strings. Use or extend
ControlId,PropertyId,FunctionId, orSymbolId. - Tests Required: Every bug fix or new feature must include integration tests under
crates/<crate_name>/tests/.
Dual-licensed under either:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.