S10) Sous le capot

Compilateur, moteur et dispatcher

Le pipeline complet — du fichier .bp au son

Un fichier .bp entre. Du son en sort. Entre les deux : un compilateur JavaScript, un moteur C compilé en WebAssembly, un resolver qui traduit les noms en fréquences, et un dispatcher qui envoie le tout vers Web Audio.

Cet article décrit l’architecture actuelle et cible. Ce qui est implémenté aujourd’hui tourne dans le navigateur — JS inline + Web Audio. L’architecture cible (REPLs externes, OSC, MIDI) est conçue mais pas encore opérationnelle. Les sections le précisent.

Où se situe cet article ?

Après les cinq couches déclaratives (S9), on ouvre le capot. Cet article est technique — il décrit les composants logiciels qui transforment un fichier .bp en événements concrets.


Le pipeline en un schéma

 

flowchart TD
    subgraph BPscript["BPscript"]
        ED["Editeur (.bp)"]
        COMP["Compilateur JS"]
        ENG["Moteur BP3 WASM"]
    end

    ALPH[("alphabet.json")]
    CTRL[("controls.json")]
    FILT[("filter.json")]
    ROUT[("routing.json")]
    TUN[("tuning.json")]

    SEQ["Timed tokens"]

    subgraph Dispatch["Dispatcher"]
        RES["Resolver"]
        CV["CV engine"]
        DIS["Dispatch"]
    end

    subgraph Transports["Transports"]
        WAUDIO["Web Audio"]
        OSC["OSC"]
        MIDI["MIDI"]
    end

    subgraph Evals["Evaluateurs"]
        JSEVAL["JS inline"]
        SCLANG["sclang"]
        PYTHON["Python"]
    end

    ED -->|".bp"| COMP
    ALPH -.-> COMP
    CTRL -.-> COMP
    FILT -.-> COMP
    COMP -->|"Interface 1"| ENG
    ENG -->|"Interface 2"| SEQ
    SEQ --> RES
    TUN -.-> RES
    ROUT -.-> RES
    RES --> CV
    CV --> DIS

    DIS -->|"terminaux"| WAUDIO
    DIS -->|"backticks"| JSEVAL
    JSEVAL -->|"valeurs"| DIS

    DIS -.-> OSC
    DIS -.-> MIDI
    DIS -.-> SCLANG
    DIS -.-> PYTHON

    style ED fill:#4a7ab5,stroke:#3a6aa5,color:#fff
    style COMP fill:#4a9a8a,stroke:#3a8a7a,color:#fff
    style ENG fill:#c8842a,stroke:#b8742a,color:#fff
    style RES fill:#7a4a9a,stroke:#6a3a8a,color:#fff
    style CV fill:#7a4a9a,stroke:#6a3a8a,color:#fff
    style DIS fill:#8a5ab5,stroke:#7a4aa5,color:#fff
    style WAUDIO fill:#2a6a4a,stroke:#1a5a3a,color:#fff
    style JSEVAL fill:#4a5ab5,stroke:#3a4aa5,color:#fff
    style OSC fill:#555,stroke:#777,color:#999
    style MIDI fill:#555,stroke:#777,color:#999
    style SCLANG fill:#555,stroke:#777,color:#999
    style PYTHON fill:#555,stroke:#777,color:#999
    style ALPH fill:#333,stroke:#666,color:#ccc
    style CTRL fill:#333,stroke:#666,color:#ccc
    style FILT fill:#333,stroke:#666,color:#ccc
    style TUN fill:#333,stroke:#666,color:#ccc
    style ROUT fill:#333,stroke:#666,color:#ccc
    style SEQ fill:#444,stroke:#666,color:#ddd

 

Figure 1 — Pipeline BPscript. Trait plein = implémenté : le compilateur produit une grammaire avec alphabet plat, le moteur WASM dérive des timed tokens, le resolver applique tempérament et routage, le dispatcher envoie les terminaux vers Web Audio et les backticks vers JS inline (évaluation dans le navigateur via new Function()). JS inline retourne des valeurs au dispatcher (flèche bidirectionnelle). Trait pointillé = prévu : transports OSC/MIDI et REPLs externes (sclang, Python).


Le compilateur BPscript

Le compilateur est écrit en JavaScript et s’exécute côté client (navigateur ou Node.js). Il transforme le source .bp en une grammaire BP3 en trois étapes :

Étape 1 — Tokenizer

Le source .bp est découpé en un flux de tokens (unités lexicales) : les 3 mots réservés (gate, trigger, cv), les 24 symboles, les 7 opérateurs, les identifiants, les littéraux, les backticks.

 

@alphabet.raga:sc             →  [@, alphabet.raga, :, sc]
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é.

Étape 2 — Parser

Les tokens deviennent un AST (Abstract Syntax Tree, arbre de syntaxe abstraite — voir L4*). L’AST de BPscript a une dizaine de types de nœuds : Scene, Directive, Declaration, Macro, Subgrammar, Rule, Expression, etc.

Le parser vérifie la syntaxe et enregistre les déclarations (gate Sa:sc). Il n’y a pas de passe de vérification de types séparée — un type-checker dédié qui validerait automatiquement le double contrat (S3) est prévu mais pas encore implémenté. Pour l’instant, les erreurs de type sont détectées au runtime.

Les détails de l’AST sont dans S13.

Étape 3 — Encodeur

L’AST est traduit en texte de grammaire BP3 : blocs gram#N, directives de mode (@mode:random), règles avec la syntaxe BP3 (-->, <-->, flags /flag=N/), et un alphabet plat de terminaux.

L’alphabet plat — un changement majeur

Le format OCT (Object-Controlled-Tempo) historique de BP3 a été abandonné. BP3 ne sait plus ce qu’est une note — il voit des noms opaques (C4, env1, Sa). 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 :

 

// BPscript : [vel:120]Sa
// Grammaire BP3 : _script(CT3) Sa
// CT3 dans la table de contrôle : { type: "vel", value: 120 }

 

Cette séparation est fondamentale : BP3 s’occupe de la structure (quand, combien de temps, dans quel ordre), le resolver JS en aval s’occupe du contenu (quelle fréquence, quel canal, quel son).


Le moteur BP3 WASM

Le code C de BP3, compilé en WebAssembly (WASM) via Emscripten. C’est le même code que Bernard Bel maintient depuis 1989, porté pour tourner dans un navigateur — avec des extensions.

Le moteur reçoit la grammaire encodée et produit des timed tokens — un tableau d’événements horodatés :

 

[
  { terminal: "Sa",  start: 0,    end: 1000 },
  { terminal: "dha", start: 0,    end: 0 },
  { terminal: "Re",  start: 1000, end: 2000 },
  ...
]

 

Les terminaux sont des noms simples — BP3 ne sait pas ce que Sa signifie ni à quelle fréquence il correspond. C’est le resolver en aval qui fait le mapping.

Le moteur gère :

  • La dérivation grammaticale (7 types de dérivation, 3 directions de scan)
  • La résolution de polymétrie (algorithme PolyMake — voir B13)
  • Le calcul des repos indéterminés (...)
  • Les flags, captures, templates, homomorphismes
  • Les poids et leur décrémentation
  • Les contrôles via _script(CTn) (lookup dans la table de contrôle)

Extensions WASM

Le moteur n’est pas une boîte noire intouchée — deux extensions ont été ajoutées :

  • bp3_set_object_duration : API pour donner une durée concrète aux terminaux custom (sons échantillonnés, enveloppes). BP3 historique ne gérait que les durées symboliques ; cette API permet au JS de communiquer une durée en millisecondes au moteur.
  • _script(CTn) : mécanisme de contrôle par table — les qualificateurs BPscript ([vel:120], [tempo:160]) sont compilés en appels _script indexés, que le moteur insère dans le flux comme des instructions zéro-durée.

Le dispatcher — resolver, CV, dispatch

Le dispatcher est en JavaScript. C’est le composant le plus riche du pipeline — il fait bien plus que du routage.

Le resolver

Avant le dispatch, le resolver transforme les noms opaques en événements concrets :

  • Tempérament : degré abstrait (Sa, degré 1) → fréquence (261.63 Hz) via tuning.json
  • Routage : symbole → transport ou REPL via routing.json
  • Control state : lookup de _script(CTn) → paramètres concrets (vel:120, tempo:160)

C’est le resolver, pas BP3, qui sait ce qu’est une note.

Le CV engine

Les objets CV (Control Voltage — enveloppes ADSR, LFO, ramps — voir S3*) sont une couche à part entière. Déclarés dans le header BPscript et placés dans la grammaire comme des symboles ordinaires, ils sont résolus par le dispatcher :

  • ADSR : enveloppe d’amplitude (attaque, déclin, sustain, release)
  • LFO : oscillateur basse fréquence (modulation périodique)
  • Ramp : interpolation linéaire entre deux bornes

Le CV engine traduit ces objets en bus audio (Web Audio), en séquences de messages CC (MIDI), ou en paramètres interpolés (OSC) selon le transport de destination.

Le dispatch — deux flux

Le dispatcher sépare deux flux distincts :

  1. Terminaux typés (gates, triggers, CV) → transports selon le routage — c’est du fire-and-forget (envoi sans attente de réponse)
  2. Backticksévaluation de code selon le tag — JS inline aujourd’hui, REPLs externes à terme

Aujourd’hui : les backticks sont évalués en JS dans le navigateur via new Function(). Pas de REPL externe, pas de session persistante — le code JS s’exécute directement. Le transport principal est Web Audio.

Architecture cible : les backticks taggés (sc:, py:) seront routés vers des REPLs externes (sclang, Python) via stdin/pipe. Les transports OSC et MIDI compléteront Web Audio.

Gestion avancée

  • Simultanéité : un point ! dispatche vers plusieurs sorties au même timestamp
  • Hot-swap : recompilation à chaud pour le live coding (quantized par défaut — voir S11 (à venir))
  • Loop mode : re-dérivation automatique — la grammaire est re-dérivée à chaque cycle, produisant potentiellement des variations différentes à chaque boucle

Évaluation de code — JS inline et REPLs

État actuel : les backticks sont évalués en JavaScript inline dans le navigateur (new Function()). Pas de session externe.

Architecture cible : des REPLs externes (sclang, Python, GHCi+Tidal) prendront le relais pour les backticks taggés.

L’évaluation de code n’est pas un simple adaptateur de sortie en bout de chaîne. C’est un composant d’enrichissement au cœur du pipeline, avec un flux bidirectionnel :

Deux rôles

Rôle Direction Ce qui se passe
Enrichissement REPL → dispatcher Le REPL évalue du code et retourne une valeur ou un événement que BPscript réinjecte dans la scène
Exécution Dispatcher → REPL Le REPL reçoit du code à exécuter au temps T (playback)

L’enrichissement est le rôle le plus original : c’est ce qui permet d’intégrer la puissance de SuperCollider ou Python dans la grammaire sans inventer un langage de programmation. Le REPL évalue rrand(40,127) et retourne 87 — BPscript injecte cette valeur dans le paramètre vel du symbole Sa.

Les trois temps

Les trois temps des backticks (S4) sont gérés par les REPLs :

  1. Init : backticks d’initialisation — exécutés avant la dérivation (définir des SynthDefs, importer des modules)
  2. Playback : backticks dans le flux — exécutés au temps T calculé par BP3 (modifier l’état du runtime)
  3. Résolution : backticks-paramètres — le REPL évalue l’expression et retourne la valeur au dispatcher

Le cas de la résolution est un aller-retour : dispatcher → REPL → valeur → dispatcher. C’est le seul moment où le dispatcher attend une réponse.

Le trigger entrant <!

Le flux retour permet aussi la synchronisation externe (S7) : un signal MIDI, un message OSC, ou un événement Python peut déclencher un <! qui débloque la dérivation. Le REPL reçoit le signal externe et le transmet au dispatcher, qui relance la dérivation en attente.

Interface universelle

L’interface est la même pour tous les REPLs (~100 lignes par langage) :

 

connect()              // ouvrir la session REPL
eval(code, time)       // envoyer du code au temps T (playback)
getValue(expr)         // évaluer et retourner une valeur (résolution)
close()                // fermer la session

 

Évaluateur Langage Sortie État
JS inline JavaScript Web Audio, DOM Implémenté
sclang SuperCollider Audio (via scsynth) Prévu
Python Python DMX, GPIO, calcul Prévu
GHCi + Tidal Haskell Audio (via SuperDirt) Prévu

Les transports — données universelles

Les transports envoient des données structurées à un moment précis, sans évaluer de code.

État actuel : seul Web Audio est implémenté — c’est le transport principal, tout tourne dans le navigateur.

Architecture cible : OSC et MIDI viendront compléter Web Audio pour piloter du hardware et des logiciels externes.

Transport Protocole Usage État
Web Audio API navigateur Son dans le browser Implémenté
OSC UDP SuperCollider (scsynth), Processing, TouchDesigner Prévu
MIDI Web MIDI API Synthétiseurs hardware, DAWs, contrôleurs Prévu

Pas de session, pas d’état. Le dispatcher traduit l’événement en appel Web Audio (aujourd’hui) ou en message OSC/MIDI (à terme) et l’envoie au temps T. C’est du fire-and-forget.

Un fichier .bp sans backticks n’a besoin que des transports — le dispatcher est un pur séquenceur.


Les trois interfaces

Interface Entre Format État
Interface 1 Compilateur → Moteur BP3 Texte grammaire BP3 + table de contrôle Existe
Interface 2 Moteur BP3 → Dispatcher Tableau JS de timed tokens Existe
Interface 3 Dispatcher → Transports + REPLs Messages horodatés + code Existe

Les trois interfaces sont opérationnelles. L’interface 3 gère à la fois les messages de transport (OSC bundles, MIDI events, Web Audio calls) et les commandes REPL (eval, getValue).


Ce qu’il faut retenir

  1. 3 étapes de compilation : tokenizer → parser → encodeur (type-checker prévu, pas encore implémenté)
  2. Alphabet plat : BP3 voit des noms opaques — le format OCT est abandonné, le resolver JS fait le mapping
  3. Le moteur BP3 WASM a des extensions : bp3_set_object_duration et _script(CTn)
  4. Le dispatcher est le composant le plus riche : resolver (tuning + routing), CV engine (ADSR, LFO, ramp), control state, loop mode, hot-swap
  5. L’évaluation de code est bidirectionnelle : enrichissement (valeurs retournées, <! triggers) + exécution (playback au temps T)
  6. Aujourd’hui : JS inline (new Function()) + Web Audio — tout dans le navigateur
  7. Architecture cible : REPLs externes (sclang, Python) + transports OSC/MIDI en complément

Glossaire

  • Compilateur : Programme JS qui transforme le source .bp en grammaire BP3 + table de contrôle
  • Tokenizer : Première étape — découpe le source en tokens
  • Parser : Deuxième étape — construit l’AST, vérifie la syntaxe
  • Encodeur : Troisième étape — traduit l’AST en grammaire BP3 avec alphabet plat
  • Alphabet plat : Convention où les terminaux sont des noms simples (pas d’encodage OCT) — BP3 ne sait pas ce qu’ils signifient
  • Timed token : Événement horodaté produit par BP3 : {terminal, start, end}
  • Resolver : Composant du dispatcher qui traduit noms → fréquences (tuning) et noms → routes (routing)
  • CV engine : Composant du dispatcher qui résout les objets CV (ADSR, LFO, ramp) en bus audio ou messages CC
  • Transport : Protocole de sortie pour les données horodatées (OSC, MIDI, Web Audio) — universel, sans état
  • REPL : Session code persistante (sclang, Python) — bidirectionnelle : exécute du code ET retourne des valeurs
  • _script(CTn) : Mécanisme de contrôle par table — les qualificateurs BPscript sont compilés en index dans une table de contrôle
  • bp3_set_object_duration : API WASM pour communiquer une durée concrète (ms) à un terminal custom

Liens dans la série

  • S9 — Les cinq couches — alphabets, tuning et routing que le pipeline consomme
  • S11 — En live — comment le hot-swap et le loop mode interagissent avec le pipeline
  • S12 — L’EBNF — la grammaire formelle que le parser implémente
  • S13 — L’AST — les types de nœuds que le parser produit
  • S3 — Les types — gate, trigger, cv et le double contrat
  • S4 — Les backticks — les trois temps (init, playback, résolution)
  • B13 — PolyMake — l’algorithme d’expansion polymétrique dans le moteur

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


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


Retour à l’index