S10) Sous le capot

Compilateur, moteur et dispatcher

Le pipeline complet — du fichier .bps au son

Un fichier .bps entre. Du son en sort. Entre les deux : un compilateur JavaScript, un moteur C compilé en WebAssembly, et — en aval — un dispatcher qui résout les notes par acteur et route vers les transports.

Périmètre : le dépôt couvre la chaîne compile time (source → grammaire BP3) et la dérivation par le moteur WASM, dont la sortie est une séquence de timed tokens. Le runtime (dispatcher, resolver, transports, sessions de code) est le consommateur aval des timed tokens — son implémentation vit hors du dépôt. On le décrit ici comme spécification du système complet.

Où se situe cet article ?

Après les acteurs (S3) et les 5 couches pitch (S9), on ouvre le capot. Cet article est technique — il décrit comment tout s’assemble.


Le pipeline en un schéma

 

flowchart TD
    subgraph Depot["Dépôt : langage + transpileur + moteur"]
        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 (aval — hors dépôt)"]
        DISP["Dispatcher"]
        RES["Resolver par acteur"]
        WAUDIO["Web Audio"]
        MIDIT["MIDI"]
        JSEVAL["JS inline"]
    end

    subgraph Prevu["Prévu (aval)"]
        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 — Pipeline BPscript. Le sous-graphe « Dépôt » couvre la compilation (tokenizer → parser → encoder) et la dérivation BP3/WASM jusqu’aux timed tokens. Le sous-graphe « Runtime (aval) » est le consommateur des timed tokens, hors dépôt. Trait plein = implémenté (Web Audio, MIDI, JS inline). Trait pointillé = prévu (OSC, sclang, Python). Les fichiers JSON (lib/) alimentent le compilateur et le resolver.


Le compilateur BPscript (3 étapes)

Le compilateur est écrit en JavaScript. Il transforme le source .bps en grammaire BP3 en trois étapes.

1. Tokenizer

Découpe le source en tokens. Lit alphabets.json et octaves.json pour reconnaître les noms de notes et les suffixes/préfixes de registre.

 

@actor sitar alphabet:sargam ...  →  [@, actor, sitar, alphabet, :, sargam, ...]
Sa_^(vel:120)                     →  [Sa_^, (, vel, :, 120, )]
[phase==1] S -> A                 →  [[, phase, ==, 1, ], S, ->, A]

 

Les backticks sont tokenizés comme des blocs opaques — le contenu n’est pas analysé.

2. Parser

Construit l’AST (Abstract Syntax Tree — voir S13). Vérifie la syntaxe, enregistre les @actor et les déclarations. Pas de vérificateur de types séparé (prévu, pas implémenté). L’expansion textuelle des macros (accent(x) = x(vel:120)) se fait dans cette phase.

3. Encoder

Traduit l’AST en grammaire BP3 :

  • Noms de notes → noms BP3-safe avec préfixe bol (SabolSa)
  • [] engine → instructions BP3 ([speed:2]{2, ...}, [weight:50]<50>)
  • () runtime → _script(CTn) avec table de contrôle
  • Guards [X==N]/X=N/
  • Captures ?n → métavariables BP3
  • Templates $/&(=X)/(:X)
  • Ties ~&

L’encoder produit aussi le terminalActorMap — un dictionnaire {terminal BP3 → nom d'acteur} transmis au dispatcher — ainsi que la controlTable, la transcriptionTable (étiquettes d’homomorphisme) et, en multi-scènes, les tables mapTable / sceneTable / exposeTable.

L’alphabet plat — un changement majeur

Le format OCT historique de BP3 a été abandonné. BP3 ne sait plus ce qu’est une note — il voit des noms opaques (bolC4, bolSa). Les contrôles (vélocité, tempo, etc.) ne sont plus encodés dans les terminaux mais passent par _script(CTn) avec une table de contrôle séparée. BP3 s’occupe de la structure (quand, combien de temps, dans quel ordre), le resolver en aval s’occupe du contenu (quelle fréquence, quel canal, quel son).

Le générateur de prototypes

L’encoder émet aussi le fichier -so. (prototypes sound-objects) : NoteOn/NoteOff pour chaque terminal, en utilisant alphabets.json et octaves.json.

Sortie totale : grammaire BP3 + alphabet plat + prototypes + settings + controlTable + terminalActorMap (+ transcriptionTable et tables de scène).


Le moteur BP3 WASM

Le code C de BP3, compilé en WebAssembly via Emscripten. C’est le même code que Bernard Bel maintient depuis 1981, porté pour tourner dans un navigateur — avec des extensions. Il reçoit la grammaire et produit des timed tokens :

 

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

 

Le moteur gère : dérivation, polymétrie (PolyMake — B13), repos indéterminés, flags, captures, templates, homomorphismes, poids, contrôles _script(CTn).

Extensions WASM : bp3_set_object_duration (durée custom pour terminaux), _script(CTn) (contrôles par table).


Le dispatcher (runtime aval)

Le composant le plus riche du runtime — en aval du dépôt. Pour chaque timed token à l’instant T :

1. Retirer le préfixe

bolSaSa (retire le préfixe opaque ajouté par l’encoder).

2. Identifier l’acteur

Via le terminalActorMap émis à la compilation : terminalActorMap["bolSa"]"sitar".

3. Résoudre le pitch

Si l’acteur a un tuning, son resolver traduit le token en fréquence via les 5 couches (S9) :

 

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

 

4. Résoudre les contrôles

Si le token est un _script(CTn), recherche dans la controlTable{vel: 120, pan: 0.5}.

5. Le CV engine

Les objets CV (Control Voltage — enveloppes ADSR, LFO, ramps) sont une couche à part entière. Déclarés en tête de scène et placés dans la grammaire comme des symboles ordinaires, ils sont résolus par le dispatcher en bus audio (Web Audio), en séquences de messages CC (MIDI), ou en paramètres interpolés (OSC) selon le transport de destination.

6. Router

Deux flux :

  • Terminaux → transport de l’acteur (acteur.transport.send({frequency, duration, controls}))
  • Backticks → évaluation de code (acteur.eval ou tag pour les orphelins)

7. Gestion avancée

  • Simultanéité (!) : même timestamp, plusieurs sorties
  • Mode boucle : re-dérivation automatique (la grammaire produit des variations à chaque cycle)
  • Remplacement à chaud : recompilation à chaud, quantisée par défaut (S11 (à venir))

Transports

Implémentés : Web Audio et MIDI.

Prévu : OSC, DMX.

Transport Protocole Usage État
Web Audio API navigateur Son dans le navigateur Implémenté
MIDI Web MIDI API DAW, hardware, synthés Implémenté
OSC UDP scsynth, Processing, TouchDesigner Prévu
DMX OSC/serial Lumières, moteurs Prévu

Les transports sont universels — pas d’état, pas de session. Le dispatcher convertit l’événement en appel natif et l’envoie, sans attendre de réponse.


Évaluation de code

Aujourd’hui : JS inline (new Function()) dans le navigateur.

Prévu : sessions de code externes (sclang, Python, GHCi+Tidal) via stdin/TCP/WebSocket.

L’évaluation de code n’est pas un simple adaptateur de sortie — c’est un composant bidirectionnel :

Direction Ce qui se passe
Dispatcher → évaluateur Code envoyé au temps T (playback)
Évaluateur → dispatcher Valeur retournée (résolution de paramètres)

Trois temps (S4) :

  1. Init : backticks orphelins → avant la dérivation
  2. Playback : backticks dans le flux → au temps T
  3. Résolution : backticks-paramètres → évalués, valeur injectée
Évaluateur Langage État
JS inline JavaScript (new Function()) Implémenté
sclang SuperCollider Prévu
Python Python (Pyodide ou exec) Prévu
GHCi + Tidal Haskell Prévu

Les trois interfaces

Interface Entre Format État
Interface 1 Compilateur → BP3 Grammaire BP3 + alphabet + prototypes + settings + controlTable Existe
Interface 2 BP3 → Dispatcher Tableau JS de timed tokens Existe
Interface 3 Dispatcher → Transports + évaluateurs Messages horodatés + code Existe (Web Audio + MIDI + JS)

L’interface 2 marque la frontière du dépôt : tout ce qui suit (interface 3 et au-delà) est le runtime aval.


Ce qu’il faut retenir

  1. 3 étapes de compilation : tokenizer → parser → encoder
  2. Alphabet plat : BP3 voit des noms opaques (bolSa), le resolver fait le mapping
  3. terminalActorMap : chaque terminal est associé à un acteur par le compilateur
  4. Un resolver par acteur : même note, fréquences différentes selon le tuning
  5. Deux flux : terminaux → transports, backticks → évaluateurs
  6. Aujourd’hui : JS inline + Web Audio + MIDI
  7. Périmètre : le dépôt s’arrête aux timed tokens ; le dispatcher et les transports sont en aval

Glossaire

  • Tokenizer : Étape 1 — découpe le source en tokens, lit alphabets et octaves
  • Parser : Étape 2 — construit l’AST (et expanse les macros)
  • Encoder : Étape 3 — traduit l’AST en grammaire BP3 + émet le terminalActorMap et les prototypes
  • terminalActorMap : Dictionnaire {terminal BP3 → acteur} — permet au dispatcher d’identifier l’acteur
  • Alphabet plat : Convention où les terminaux sont des noms opaques préfixés bol — BP3 ne sait pas ce qu’ils signifient
  • Resolver : Composant aval (1 par acteur) qui traduit token → fréquence via les 5 couches pitch
  • CV engine : Composant aval qui résout les objets CV (ADSR, LFO, ramp) en bus audio ou messages CC
  • controlTable : Table {CTn → paramètres} — les () runtime compilés en _script(CTn)
  • Transport : Protocole de sortie pour les données horodatées (Web Audio, MIDI, OSC)

Liens dans la série

  • S3 — Types, acteurs et rattachements — comment les acteurs sont déclarés
  • S4 — Les backticks — init, playback, résolution
  • S9 — Le système de hauteurs — ce que le resolver fait
  • S11 — En live — remplacement à chaud et mode boucle
  • S12 — L’EBNF — la grammaire que le parser implémente
  • S13 — L’AST — ce que le parser produit
  • B13 — PolyMake — l’algorithme d’expansion polymétrique

Prérequis : S3, S9
Temps de lecture : 14 min
Tags : #BPscript #architecture #compilateur #WebAssembly #dispatcher #acteurs


Prochain article : S11 — En live : modifier pendant que ça joue


Retour à l’index