S10) Sous le capot
Compilateur, moteur et dispatcher
Le pipeline complet — du fichier .bp au son
Un fichier
.bpentre. 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_scriptindexé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) viatuning.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 :
- Terminaux typés (gates, triggers, CV) → transports selon le routage — c’est du fire-and-forget (envoi sans attente de réponse)
- 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 :
- Init : backticks d’initialisation — exécutés avant la dérivation (définir des SynthDefs, importer des modules)
- Playback : backticks dans le flux — exécutés au temps T calculé par BP3 (modifier l’état du runtime)
- 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
- 3 étapes de compilation : tokenizer → parser → encodeur (type-checker prévu, pas encore implémenté)
- Alphabet plat : BP3 voit des noms opaques — le format OCT est abandonné, le resolver JS fait le mapping
- Le moteur BP3 WASM a des extensions :
bp3_set_object_durationet_script(CTn) - Le dispatcher est le composant le plus riche : resolver (tuning + routing), CV engine (ADSR, LFO, ramp), control state, loop mode, hot-swap
- L’évaluation de code est bidirectionnelle : enrichissement (valeurs retournées,
<!triggers) + exécution (playback au temps T) - Aujourd’hui : JS inline (
new Function()) + Web Audio — tout dans le navigateur - Architecture cible : REPLs externes (sclang, Python) + transports OSC/MIDI en complément
Glossaire
- Compilateur : Programme JS qui transforme le source
.bpen 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ôlebp3_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