BPscript · S10 · Design
Under the Hood: The Complete BPscript Technical Pipeline
The complete pipeline — from .bps file to sound
Published 21 May 2026 · Lire en français
Compiler, Engine, and Dispatcher
The Full Pipeline — from the .bps file to sound
A
.bpsfile goes in. Sound comes out. In between: a JavaScript compiler that produces a scene tree, the BPx engine that derives that tree, and — downstream — a dispatcher that resolves notes by actor and routes them to transports.Scope: the chain goes from the
.bpssource to the scene tree, then to derivation by BPx, whose output is a sequence of timed tokens (a projection of the tree). The runtime (dispatcher, resolver, transports, code sessions) is the downstream consumer of those tokens. It is described here as a specification of the complete system.
Where does this article fit?
After actors (S3) and the six pitch layers (S9), we’re opening the hood. This article is technical — it describes how everything fits together.
The pipeline in a diagram
flowchart TD
subgraph Depot["Language + transpiler + engine"]
SRC["Source .bps"]
TOK["Tokenizer"]
PAR["Parser"]
TREE["Scene tree"]
BPX["BPx — derivation"]
TOKENS["Timed tokens (projection)"]
end
subgraph Data["lib/"]
ALP[("alphabets")]
OCT[("octaves")]
TUN[("tunings")]
TMP[("temperaments")]
CTR[("controls")]
ROU[("routing")]
end
subgraph Parity["Parity (validation)"]
BP3TXT["BP3 text"]
NAT["Native engine (oracle)"]
end
subgraph Aval["Runtime (downstream)"]
DISP["Dispatcher"]
RES["Per-actor resolver"]
WAUDIO["Web Audio"]
MIDIT["MIDI"]
JSEVAL["JS inline"]
end
subgraph Prevu["Planned (downstream)"]
OSCT["OSC"]
SCL["sclang"]
PYT["Python"]
end
SRC --> TOK
ALP -.-> TOK
OCT -.-> TOK
TOK --> PAR
PAR --> TREE
CTR -.-> TREE
TREE --> BPX
BPX --> TOKENS
TREE -.-> BP3TXT
BP3TXT -.-> NAT
TOKENS --> DISP
TUN -.-> RES
TMP -.-> RES
ROU -.-> DISP
DISP --> RES
RES --> WAUDIO
RES --> MIDIT
DISP --> JSEVAL
JSEVAL --> DISP
DISP -.-> OSCT
DISP -.-> SCL
DISP -.-> PYT
style SRC fill:#4a7ab5,stroke:#3a6aa5,color:#fff
style TOK fill:#4a9a8a,stroke:#3a8a7a,color:#fff
style PAR fill:#4a9a8a,stroke:#3a8a7a,color:#fff
style TREE fill:#4a9a8a,stroke:#3a8a7a,color:#fff
style BPX fill:#c8842a,stroke:#b8742a,color:#fff
style DISP fill:#8a5ab5,stroke:#7a4aa5,color:#fff
style RES fill:#7a4a9a,stroke:#6a3a8a,color:#fff
style WAUDIO fill:#2a6a4a,stroke:#1a5a3a,color:#fff
style MIDIT fill:#2a6a4a,stroke:#1a5a3a,color:#fff
style JSEVAL fill:#4a5ab5,stroke:#3a4aa5,color:#fff
style TOKENS fill:#444,stroke:#666,color:#ddd
style BP3TXT fill:#555,stroke:#777,color:#999
style NAT fill:#555,stroke:#777,color:#999
style ALP fill:#333,stroke:#666,color:#ccc
style OCT fill:#333,stroke:#666,color:#ccc
style TUN fill:#333,stroke:#666,color:#ccc
style TMP fill:#333,stroke:#666,color:#ccc
style CTR fill:#333,stroke:#666,color:#ccc
style ROU fill:#333,stroke:#666,color:#ccc
style OSCT fill:#555,stroke:#777,color:#999
style SCL fill:#555,stroke:#777,color:#999
style PYT fill:#555,stroke:#777,color:#999
Figure 1 — BPscript pipeline. The production chain runs from the source to the scene tree, derived by BPx into timed tokens (a projection of the tree). The downstream runtime consumes those tokens. Solid line = implemented (Web Audio, MIDI, JS inline). Dotted line = planned (OSC, sclang, Python) or the parity path (encoding to BP3 text compared against the native engine). The JSON files (
lib/) feed the compiler and the resolver.
The BPscript compiler (3 steps)
The compiler is written in JavaScript. It turns the .bps source into a scene tree in three steps.
1. Tokenizer
Splits the source into tokens. Reads alphabets.json and octaves.json to recognize note names and register prefixes/suffixes.
@actor sitar alphabet.sargam ... → [@, actor, sitar, alphabet, ., sargam, ...]
Sa_^(vel:120) → [Sa_^, (, vel, :, 120, )]
[phase==1] S -> A → [[, phase, ==, 1, ], S, ->, A]
Backticks are tokenized as opaque blocks — their content is not analyzed.
2. Parser
Builds the AST (Abstract Syntax Tree — see S13). Checks syntax, records @actor and declarations. No separate type checker (planned, not implemented). The textual expansion of macros (accent(x) = x(vel:120)) happens in this phase.
3. Scene tree
The parser produces the scene tree: the normalized form that is the engine’s direct input. This is what BPx consumes — not an intermediate text. Each leaf carries its opaque charge (velocity, timbre…) and its actor, which travel with it through derivation. The [] (engine) become structural attributes; the () (runtime) become the leaf’s non-temporal charge; guards, captures, templates and ties become the corresponding nodes.
The parity path. A second path encodes the same tree into BP3 text to replay it on Bernard Bel’s native engine and prove equivalence (the old WebAssembly port of that engine serves as a validation tool, now frozen). This text is not the production path — it is a scaffold of truth, not the path to sound.
Structure on one side, content on the other
BPx handles structure (when, how long, in what order). Content (which frequency, which channel, which sound) stays an opaque charge that the engine transports without interpreting, and that the downstream resolver resolves. This boundary is what makes multi-runtime rendering possible.
The BPx engine
BPx is the TypeScript port of Bernard Bel’s engine — its temporal-scheduling dimension — designed to run in the browser and consume the tree directly. It receives the scene tree and produces the derivation tree, whose flat sequence of timed tokens is a projection:
[
{ name: "Sa", actor: "sitar", start: 0, duration: 1000, controls: { vel: 120 } },
{ name: "Re", actor: "sitar", start: 1000, duration: 1000, controls: {} },
...
]
The tree is the single artifact of truth: the flat sequence is only a view of it, chosen by layers (time, identity, resolved controls, actor, structure…). The engine handles: derivation, polymetry (PolyMake — B13), indeterminate rests, flags, captures, templates, homomorphisms, weights, and the arrangement of non-temporal controls on each terminal at the end of derivation.
The dispatcher (downstream runtime)
The richest component of the runtime — downstream of the engine. For each timed token at time T:
1. Identify the actor
The actor travels in the token (it is part of its charge). The dispatcher therefore routes by actor, with no flat terminal → actor table: the historical path needed one because the detour through BP3 text lost the actor; the tree, by contrast, keeps it.
2. Resolve pitch
If the actor has a tuning, its resolver translates the token into a frequency through the six layers (S9):
"Sa_^" → parse → note="sa", register=2
→ alphabet: sa = degree 0
→ octave: register 2 - default 1 = +1
→ tuning: degree 0 → step 0
→ temperament: step 0 → ratio 1/1
→ freq = baseHz × ratio × period_ratio = 480 Hz
3. Read the controls
Non-temporal controls (velocity, timbre, panning) are already resolved by the engine, arranged on the token (controls). The dispatcher reads them — it does not recompute them.
4. The CV engine
CV objects (Control Voltage — ADSR envelopes, LFOs, ramps) are a layer in their own right. Declared at the top of the scene and placed in the grammar like ordinary symbols, they are resolved by the dispatcher into audio buses (Web Audio), into CC message sequences (MIDI), or into interpolated parameters (OSC) depending on the destination transport.
5. Route
Two streams:
- Terminals → the actor’s transport (
actor.transport.send({frequency, duration, controls})) - Backticks → code evaluation (
actor.evalor a tag for orphans)
The symbolic reading (showing the bols, words or numbers being played) is not a third transport: it is a view of the derivation tree, just like the visualization.
6. Advanced handling
- Simultaneity (
!): same timestamp, several outputs - Loop mode: optional re-derivation at the loop boundary (the grammar produces variations each cycle)
- Hot-swap: hot recompilation, quantized by default (S11)
Transports
Implemented: Web Audio and MIDI. Planned: OSC, DMX.
| Transport | Protocol | Use | Status |
|---|---|---|---|
| Web Audio | browser API | Sound in the browser | Implemented |
| MIDI | Web MIDI API | DAW, hardware, synths | Implemented |
| OSC | UDP | scsynth, Processing, TouchDesigner | Planned |
| DMX | OSC/serial | Lights, motors | Planned |
Transports are universal — no state, no session. The dispatcher converts the event into a native call and sends it, without waiting for a reply.
Code evaluation
Today: JS inline (
new Function()) in the browser. Planned: external code sessions (sclang, Python, GHCi+Tidal) via stdin/TCP/WebSocket.
Code evaluation is not a mere output adapter — it is a bidirectional component:
| Direction | What happens |
|---|---|
| Dispatcher → evaluator | Code sent at time T (playback) |
| Evaluator → dispatcher | Value returned (parameter resolution) |
Three times (S4):
- Init: orphan backticks → before derivation
- Playback: backticks in the stream → at time T
- Resolution: parameter backticks → evaluated, value injected
| Evaluator | Language | Status |
|---|---|---|
| JS inline | JavaScript (new Function()) | Implemented |
| sclang | SuperCollider | Planned |
| Python | Python (Pyodide or exec) | Planned |
| GHCi + Tidal | Haskell | Planned |
The three interfaces
| Interface | Between | Format | Status |
|---|---|---|---|
| Interface 1 | Frontend → engine | Scene tree (direct input) | Exists |
| Interface 2 | BPx → Dispatcher | Timed tokens (projection of the tree, actor + controls included) | Exists |
| Interface 3 | Dispatcher → Transports + evaluators | Timestamped messages + code | Exists (Web Audio + MIDI + JS) |
Interface 1 is the central contract: both frontends (BPScript and BP3) produce the same tree, and the engine consumes it as is.
Key takeaways
- 3 compilation steps: tokenizer → parser → scene tree
- The tree is the single source: the flat token sequence is a projection of it
- The actor travels in the token: the dispatcher routes by actor, with no flat table
- One resolver per actor: same note, different frequencies depending on the tuning
- Controls resolved upstream: the dispatcher reads them, it does not recompute them
- Two streams: terminals → transports, backticks → evaluators; text = a view
- BP3 text serves only to prove parity with the native engine
Glossary
- Tokenizer: Step 1 — splits the source into tokens, reads alphabets and octaves
- Parser: Step 2 — builds the AST (and expands macros)
- Scene tree: Normalized form produced by the frontend, the engine’s direct input — each leaf carries its actor and its opaque charge
- BPx: TypeScript port of Bernard Bel’s engine; derives the tree in the browser
- Projection: The flat sequence of timed tokens, a view of the tree by layers (time, identity, controls, actor…)
- Resolver: Downstream component (1 per actor) that translates token → frequency through the six pitch layers
- CV engine: Downstream component that resolves CV objects (ADSR, LFO, ramp) into audio buses or CC messages
- Resolved controls: The runtime
(), arranged by the engine on each terminal, read as is by the dispatcher - Transport: Output protocol for timestamped data (Web Audio, MIDI, OSC)
- Parity: Comparison of the output against Bernard Bel’s native engine, via an encoding to BP3 text
Links in the series
- S3 — Types, actors and bindings — how actors are declared
- S4 — Backticks — init, playback, resolution
- S9 — The pitch system — what the resolver does
- S11 — Live — hot-swap and loop mode
- S12 — The EBNF — the grammar the parser implements
- S13 — The AST — what the parser produces
- B13 — PolyMake — the polymetric expansion algorithm
Prerequisite: S3, S9 Reading time: 14 min Tags: #BPscript #architecture #compiler #BPx #dispatcher #actors
Next article: S11 — Live: modifying while it plays