Skip to content
roomi fields

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 .bps file 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 .bps source 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.eval or 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.

TransportProtocolUseStatus
Web Audiobrowser APISound in the browserImplemented
MIDIWeb MIDI APIDAW, hardware, synthsImplemented
OSCUDPscsynth, Processing, TouchDesignerPlanned
DMXOSC/serialLights, motorsPlanned

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:

DirectionWhat happens
Dispatcher → evaluatorCode sent at time T (playback)
Evaluator → dispatcherValue returned (parameter resolution)

Three times (S4):

  1. Init: orphan backticks → before derivation
  2. Playback: backticks in the stream → at time T
  3. Resolution: parameter backticks → evaluated, value injected
EvaluatorLanguageStatus
JS inlineJavaScript (new Function())Implemented
sclangSuperColliderPlanned
PythonPython (Pyodide or exec)Planned
GHCi + TidalHaskellPlanned

The three interfaces

InterfaceBetweenFormatStatus
Interface 1Frontend → engineScene tree (direct input)Exists
Interface 2BPx → DispatcherTimed tokens (projection of the tree, actor + controls included)Exists
Interface 3Dispatcher → Transports + evaluatorsTimestamped messages + codeExists (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

  1. 3 compilation steps: tokenizer → parser → scene tree
  2. The tree is the single source: the flat token sequence is a projection of it
  3. The actor travels in the token: the dispatcher routes by actor, with no flat table
  4. One resolver per actor: same note, different frequencies depending on the tuning
  5. Controls resolved upstream: the dispatcher reads them, it does not recompute them
  6. Two streams: terminals → transports, backticks → evaluators; text = a view
  7. 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

  • 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


← Back to index