S10) Sous le capot
Compilateur, moteur et dispatcher
Le pipeline complet — du fichier .bps au son
Un fichier
.bpsentre. 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(Sa→bolSa) []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
bolSa → Sa (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.evalou 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) :
- Init : backticks orphelins → avant la dérivation
- Playback : backticks dans le flux → au temps T
- 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
- 3 étapes de compilation : tokenizer → parser → encoder
- Alphabet plat : BP3 voit des noms opaques (
bolSa), le resolver fait le mapping - terminalActorMap : chaque terminal est associé à un acteur par le compilateur
- Un resolver par acteur : même note, fréquences différentes selon le tuning
- Deux flux : terminaux → transports, backticks → évaluateurs
- Aujourd’hui : JS inline + Web Audio + MIDI
- 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