🔧 Template for Adding a New Cue Type
Follow this pattern to introduce any new cue:<type>(...) construct, such as cue:metronome, cue:light, or cue:harmony.
Each cue type requires three small additions: one in the grammar, one in the AST translator, and one in the runtime dispatcher.
🧱 Step 1 — Add Grammar Rule in parser.js
Define the cue-specific rule using the existing cue:page pattern.
Use clear token labels for arguments so they can be extracted easily in cstToAst().
// ── cue:metronome(seq:7/16x2,3/4x4,bpm:92,style:pulse,showNumbers:1)
$.RULE("metronomeCue", () => {
$.CONSUME(Identifier, { LABEL: "metronomeKeyword" }); // "metronome"
$.CONSUME(LParen);
$.MANY_SEP({
SEP: Comma,
DEF: () => {
$.CONSUME(Identifier, { LABEL: "paramKey" }); // seq, bpm, style, etc.
$.CONSUME(Colon);
$.OR([
{ ALT: () => $.CONSUME(NumberLiteral, { LABEL: "paramNumber" }) },
{ ALT: () => $.CONSUME(Identifier, { LABEL: "paramIdent" }) },
{ ALT: () => $.CONSUME(StringLiteral, { LABEL: "paramString" }) },
]);
},
});
$.CONSUME(RParen);
});
Register it in your root cue selector:
$.RULE("cueRoot", () => {
$.CONSUME(Cue);
$.CONSUME(Colon);
$.OR([
{ ALT: () => $.SUBRULE($.pageCue) },
{ ALT: () => $.SUBRULE($.audioCue) },
{ ALT: () => $.SUBRULE($.metronomeCue) }, // ← new cue here
// ...
]);
});
🧠 Step 2 — Extend cstToAst()
Transform the CST node into a simple, uniform AST object:
if (c.metronomeCue) {
const ch = c.metronomeCue[0].children;
const params = [];
const keys = ch.paramKey || [];
keys.forEach((k, i) => {
const key = k.image;
const val =
ch.paramNumber?.[i]?.image ||
ch.paramIdent?.[i]?.image ||
ch.paramString?.[i]?.image ||
null;
params.push({ type: key, value: val });
});
ast = { type: "cueMetronome", args: params };
}
⚙️ Step 3 — Add a Runtime Handler in cues.js
Create a function to execute the new cue’s behavior at runtime:
export async function handleMetronomeCueFromAST(ast) {
const args = Object.fromEntries(ast.args.map(a => [a.type, a.value]));
console.log("[CueDSL] 🕒 Starting metronome with:", args);
// Example: initialize or sync a visual metronome
startNetworkMetronome({
bpm: Number(args.bpm) || 90,
sequence: args.seq || "4/4x4",
style: args.style || "pulse",
showNumbers: args.showNumbers === "1",
});
}
And register it in the unified dispatcher:
export async function handleCueFromAST(ast) {
switch (ast.type) {
case "cuePage": return handlePageCueFromAST(ast);
case "cueAudio": return handleAudioCueFromAST(ast);
case "cueMetronome": return handleMetronomeCueFromAST(ast);
// ...
}
}
✅ Result
After adding these three parts, you can use your new cue directly inside an SVG element ID:
id="cue:metronome(seq:7/16x2,3/4x4,bpm:92,style:pulse,showNumbers:1)"
and it will be parsed, converted to an AST like:
{
type: "cueMetronome",
args: [
{ type: "seq", value: "7/16x2,3/4x4" },
{ type: "bpm", value: "92" },
{ type: "style", value: "pulse" },
{ type: "showNumbers", value: "1" }
]
}
and executed by handleMetronomeCueFromAST() in real time.
Tip: use ← → or ↑ ↓ to navigate the docs