S10) Under the Hood

Compiler, engine, and dispatcher

The complete pipeline — from .bps file to sound

A .bps file goes in. Sound comes out. Between the two: a JavaScript compiler, a C engine compiled into WebAssembly, and — downstream — a dispatcher that resolves notes by actor and routes them to transports.

Scope: the repository covers the compile-time chain (source → BP3 grammar) and the derivation by the WASM engine, whose output is a sequence of timed tokens. The runtime (dispatcher, resolver, transports, code sessions) is the downstream consumer of the timed tokens — its implementation lives outside the repository. It is described here as a specification of the complete system.

Where does this article fit in?

After actors (S3) and the 5 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["Repository: language + transpiler + engine"]
        SRC["Source .bps"]
        TOK["Tokenizer"]
        PAR["Parser"]
        ENC["Encoder"]
        BP3["BP3 WASM"]
        TOKENS["Timed tokens"]
    end

    subgraph Data["lib/"]
        ALP[("alphabets")]
        OCT[("octaves")]
        TUN[("tunings")]
        TMP[("temperaments")]
        CTR[("controls")]
        ROU[("routing")]
    end

    subgraph Aval["Runtime (downstream — outside repository)"]
        DISP["Dispatcher"]
        RES["Resolver by actor"]
        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 --> ENC
    ALP -.-> ENC
    OCT -.-> ENC
    CTR -.-> ENC
    ENC --> BP3
    BP3 --> TOKENS
    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 ENC fill:#4a9a8a,stroke:#3a8a7a,color:#fff
    style BP3 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 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 “Repository” subgraph covers compilation (tokenizer → parser → encoder) and BP3/WASM derivation up to the timed tokens. The “Runtime (downstream)” subgraph is the consumer of the timed tokens, outside the repository. Solid line = implemented (Web Audio, MIDI, JS inline). Dashed line = planned (OSC, sclang, Python). JSON files (lib/) feed the compiler and the resolver.


The BPscript compiler (3 steps)

The compiler is written in JavaScript. It transforms the .bps source into BP3 grammar in three steps.

1. Tokenizer

Breaks the source into tokens. Reads alphabets.json and octaves.json to recognize note names and register suffixes/prefixes.

 

@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 — the content is not analyzed.

2. Parser

Builds the AST (Abstract Syntax Tree — see S13). Checks syntax, registers @actor and declarations. No separate type checker (planned, not implemented). Textual expansion of macros (accent(x) = x(vel:120)) occurs in this phase.

3. Encoder

Translates the AST into BP3 grammar:

  • Note names → BP3-safe names with bol prefix (SabolSa)
  • [] engine → BP3 instructions ([speed:2]{2, ...}, [weight:50]<50>)
  • () runtime → _script(CTn) with control table
  • Guards [X==N]/X=N/
  • Captures ?n → BP3 metavariables
  • Templates $/&(=X)/(:X)
  • Ties ~&

The encoder also produces the terminalActorMap — a dictionary {BP3 terminal → actor name} passed to the dispatcher — as well as the controlTable, the transcriptionTable (homomorphism labels) and, in multi-scene mode, the mapTable / sceneTable / exposeTable.

The flat alphabet — a major change

The historical BP3 OCT format has been abandoned. BP3 no longer knows what a note is — it sees opaque names (bolC4, bolSa). Controls (velocity, tempo, etc.) are no longer encoded in the terminals but pass through _script(CTn) with a separate control table. BP3 handles the structure (when, for how long, in what order), while the downstream resolver handles the content (which frequency, which channel, which sound).

The prototype generator

The encoder also emits the -so. file (sound-object prototypes): NoteOn/NoteOff for each terminal, using alphabets.json and octaves.json.

Total output: BP3 grammar + flat alphabet + prototypes + settings + controlTable + terminalActorMap (+ transcriptionTable and scene tables).


The BP3 WASM engine

The BP3 C code, compiled into WebAssembly via Emscripten. It is the same code that Bernard Bel has maintained since 1981, ported to run in a browser — with extensions. It receives the grammar and produces timed tokens:

 

[
  { terminal: "bolSa",        start: 0,    duration: 1000 },
  { terminal: "_script(CT0)", start: 0,    duration: 0 },
  { terminal: "bolRe",        start: 1000, duration: 1000 },
  ...
]

 

The engine handles: derivation, polymetry (PolyMake — B13), indeterminate rests, flags, captures, templates, homomorphisms, weights, _script(CTn) controls.

WASM extensions: bp3_set_object_duration (custom duration for terminals), _script(CTn) (table-based controls).


The dispatcher (downstream runtime)

The richest component of the runtime — downstream from the repository. For each timed token at time T:

1. Remove the prefix

bolSaSa (removes the opaque prefix added by the encoder).

2. Identify the actor

Via the terminalActorMap emitted at compilation: terminalActorMap["bolSa"]"sitar".

3. Resolve the pitch

If the actor has a tuning, its resolver translates the token into frequency via the 5 layers (S9):

 

"Sa_^" → parse → note="sa", register=2
       → alphabet: sa = degree 0
       → tuning: degree 0 → step 0
       → temperament: step 0 → ratio 1/1
       → octave: +1 → ratio × period_ratio
       → freq = baseHz × ratio = 480 Hz

 

4. Resolve controls

If the token is a _script(CTn), look up in the controlTable{vel: 120, pan: 0.5}.

5. The CV engine

CV objects (Control Voltage — ADSR envelopes, LFOs, ramps) are a layer in their own right. Declared at the head of the scene and placed in the grammar as ordinary symbols, they are resolved by the dispatcher into audio buses (Web Audio), CC message sequences (MIDI), or interpolated parameters (OSC) depending on the destination transport.

6. Route

Two streams:

  • Terminals → actor transport (actor.transport.send({frequency, duration, controls}))
  • Backticks → code evaluation (actor.eval or tag for orphans)

7. Advanced management

  • Simultaneity (!): same timestamp, multiple outputs
  • Loop mode: automatic re-derivation (the grammar produces variations each cycle)
  • Hot swapping: hot recompilation, quantized by default (S11 (coming soon))

Transports

Implemented: Web Audio and MIDI.

Planned: OSC, DMX.

Transport Protocol Usage Status
Web Audio Browser API In-browser sound Implemented
MIDI Web MIDI API DAWs, 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 response.


Code evaluation

Today: inline JS (new Function()) in the browser.

Planned: external code sessions (sclang, Python, GHCi+Tidal) via stdin/TCP/WebSocket.

Code evaluation is not a simple output adapter — it is a bidirectional component:

Direction What happens
Dispatcher → evaluator Code sent at time T (playback)
Evaluator → dispatcher Returned value (parameter resolution)

Three timings (S4):

  1. Init: orphan backticks → before derivation
  2. Playback: backticks in the stream → at time T
  3. 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 Compiler → BP3 BP3 grammar + alphabet + prototypes + settings + controlTable Exists
Interface 2 BP3 → Dispatcher JS array of timed tokens Exists
Interface 3 Dispatcher → Transports + evaluators Timestamped messages + code Exists (Web Audio + MIDI + JS)

Interface 2 marks the boundary of the repository: everything that follows (interface 3 and beyond) is the downstream runtime.


Key takeaways

  1. 3 compilation steps: tokenizer → parser → encoder
  2. Flat alphabet: BP3 sees opaque names (bolSa), the resolver handles the mapping
  3. terminalActorMap: each terminal is associated with an actor by the compiler
  4. One resolver per actor: same note, different frequencies depending on the tuning
  5. Two streams: terminals → transports, backticks → evaluators
  6. Today: inline JS + Web Audio + MIDI
  7. Scope: the repository ends at timed tokens; the dispatcher and transports are downstream

Glossary

  • Tokenizer: Step 1 — breaks the source into tokens, reads alphabets and octaves
  • Parser: Step 2 — builds the AST (and expands macros)
  • Encoder: Step 3 — translates the AST into BP3 grammar + emits the terminalActorMap and prototypes
  • terminalActorMap: Dictionary {BP3 terminal → actor} — allows the dispatcher to identify the actor
  • Flat alphabet: Convention where terminals are opaque names prefixed with bol — BP3 does not know what they mean
  • Resolver: Downstream component (1 per actor) that translates token → frequency via the 5 pitch layers
  • CV engine: Downstream component that resolves CV objects (ADSR, LFO, ramp) into audio buses or CC messages
  • controlTable: Table {CTn → parameters} — runtime () compiled into _script(CTn)
  • Transport: Output protocol for timestamped data (Web Audio, MIDI, OSC)

Links in the series

  • S3 — Types, actors, and attachments — how actors are declared
  • S4 — Backticks — init, playback, resolution
  • S9 — The pitch system — what the resolver does
  • S11 — Live — hot swapping and loop mode
  • S12 — The EBNF — the grammar the parser implements
  • S13 — The AST — what the parser produces
  • B13 — PolyMake — the polymetric expansion algorithm

Prerequisites: S3, S9
Reading time: 14 min
Tags: #BPscript #architecture #compiler #WebAssembly #dispatcher #actors


Next article: S11 — Live: modifying while it plays


Back to index