BPscript · S10 · Conception
Sous le capot
Le pipeline complet — du fichier .bps au son
Publié le 19 mars 2026 · mis à jour 21 mai 2026 · Read in English
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 qui produit un arbre de scène, le moteur BPx qui dérive cet arbre, et — en aval — un dispatcher qui résout les notes par acteur et route vers les transports.Périmètre : la chaîne va de la source
.bpsà l’arbre de scène, puis à la dérivation par BPx, dont la sortie est une suite de tokens datés (une projection de l’arbre). Le runtime (dispatcher, resolver, transports, sessions de code) est le consommateur aval de ces tokens. On le décrit ici comme spécification du système complet.
Où se situe cet article ?
Après les acteurs (S3) et les six 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["Langage + transpileur + moteur"]
SRC["Source .bps"]
TOK["Tokenizer"]
PAR["Parser"]
TREE["Arbre de scène"]
BPX["BPx — dérivation"]
TOKENS["Tokens datés (projection)"]
end
subgraph Data["lib/"]
ALP[("alphabets")]
OCT[("octaves")]
TUN[("tunings")]
TMP[("temperaments")]
CTR[("controls")]
ROU[("routing")]
end
subgraph Parite["Parité (validation)"]
BP3TXT["Texte BP3"]
NAT["Moteur natif (oracle)"]
end
subgraph Aval["Runtime (aval)"]
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 --> TREE
CTR -.-> TREE
TREE --> BPX
BPX --> TOKENS
TREE -.-> BP3TXT
BP3TXT -.-> NAT
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 TREE fill:#4a9a8a,stroke:#3a8a7a,color:#fff
style BPX 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 BP3TXT fill:#555,stroke:#777,color:#999
style NAT fill:#555,stroke:#777,color:#999
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. La chaîne de production va de la source à l’arbre de scène, dérivé par BPx en tokens datés (une projection de l’arbre). Le runtime aval consomme ces tokens. Trait plein = implémenté (Web Audio, MIDI, JS inline). Trait pointillé = prévu (OSC, sclang, Python) ou voie de parité (encodage en texte BP3 comparé au moteur natif). 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 arbre de scène 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. Arbre de scène
Le parser produit l’arbre de scène : la forme normalisée qui est l’entrée directe du moteur. C’est lui que BPx consomme — pas un texte intermédiaire. Chaque feuille porte sa charge opaque (vélocité, timbre…) et son acteur, qui voyagent avec elle à travers la dérivation. Les [] (moteur) deviennent des attributs de structure ; les () (runtime) deviennent la charge non-temporelle de la feuille ; gardes, captures, templates et liaisons deviennent les nœuds correspondants.
La voie de parité. Un second chemin encode le même arbre en texte BP3 pour le rejouer sur le moteur natif de Bernard Bel et prouver l’équivalence (l’ancien portage WebAssembly de ce moteur sert d’outil de validation, désormais figé). Ce texte n’est pas la voie de production — c’est un échafaudage de vérité, pas le chemin du son.
Structure d’un côté, contenu de l’autre
BPx s’occupe de la structure (quand, combien de temps, dans quel ordre). Le contenu (quelle fréquence, quel canal, quel son) reste une charge opaque que le moteur transporte sans l’interpréter, et que le resolver en aval résout. Cette frontière est ce qui rend possible le rendu multi-runtime.
Le moteur BPx
BPx est le portage TypeScript du moteur de Bernard Bel — sa dimension d’ordonnancement temporel — conçu pour tourner dans le navigateur et consommer l’arbre directement. Il reçoit l’arbre de scène et produit l’arbre de dérivation, dont la suite plate de tokens datés est une projection :
[
{ name: "Sa", actor: "sitar", start: 0, duration: 1000, controls: { vel: 120 } },
{ name: "Re", actor: "sitar", start: 1000, duration: 1000, controls: {} },
...
]
L’arbre est l’artefact de vérité unique : la suite plate n’en est qu’une vue, choisie par calques (temps, identité, contrôles résolus, acteur, structure…). Le moteur gère : dérivation, polymétrie (PolyMake — B13), repos indéterminés, flags, captures, templates, homomorphismes, poids, et le rangement des contrôles non-temporels sur chaque terminal en fin de dérivation.
Le dispatcher (runtime aval)
Le composant le plus riche du runtime — en aval du moteur. Pour chaque token daté à l’instant T :
1. Identifier l’acteur
L’acteur voyage dans le token (il fait partie de sa charge). Le dispatcher route donc par acteur, sans table à plat terminal → acteur : la voie historique en avait besoin parce que le détour par le texte BP3 perdait l’acteur ; l’arbre, lui, le conserve.
2. Résoudre le pitch
Si l’acteur a un tuning, son resolver traduit le token en fréquence via les six couches (S9) :
"Sa_^" → parse → note="sa", register=2
→ alphabet: sa = degré 0
→ octave: register 2 - défaut 1 = +1
→ tuning: degré 0 → step 0
→ tempérament: step 0 → ratio 1/1
→ freq = baseHz × ratio × period_ratio = 480 Hz
3. Lire les contrôles
Les contrôles non-temporels (vélocité, timbre, panoramique) sont déjà résolus par le moteur, rangés sur le token (controls). Le dispatcher les lit — il ne les recalcule pas.
4. 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.
5. Router
Deux flux :
- Terminaux → transport de l’acteur (
acteur.transport.send({frequency, duration, controls})) - Backticks → évaluation de code (
acteur.evalou tag pour les orphelins)
La lecture symbolique (afficher les bols, mots ou nombres joués) n’est pas un troisième transport : c’est une vue de l’arbre de dérivation, au même titre que la visualisation.
6. Gestion avancée
- Simultanéité (
!) : même timestamp, plusieurs sorties - Mode boucle : re-dérivation optionnelle au bouclage (la grammaire produit des variations à chaque cycle)
- Remplacement à chaud : recompilation à chaud, quantisée par défaut (S11)
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 | Frontal → moteur | Arbre de scène (entrée directe) | Existe |
| Interface 2 | BPx → Dispatcher | Tokens datés (projection de l’arbre, acteur + contrôles inclus) | Existe |
| Interface 3 | Dispatcher → Transports + évaluateurs | Messages horodatés + code | Existe (Web Audio + MIDI + JS) |
L’interface 1 est le contrat central : les deux frontaux (BPScript et BP3) produisent le même arbre, et le moteur le consomme tel quel.
Ce qu’il faut retenir
- 3 étapes de compilation : tokenizer → parser → arbre de scène
- L’arbre est la source unique : la suite plate de tokens en est une projection
- L’acteur voyage dans le token : le dispatcher route par acteur, sans table à plat
- Un resolver par acteur : même note, fréquences différentes selon le tuning
- Contrôles résolus en amont : le dispatcher les lit, il ne les recalcule pas
- Deux flux : terminaux → transports, backticks → évaluateurs ; le texte = une vue
- Le texte BP3 ne sert qu’à prouver la parité avec le moteur natif
Glossaire
- Tokenizer : Étape 1 — découpe le source en tokens, lit alphabets et octaves
- Parser : Étape 2 — construit l’AST (et expanse les macros)
- Arbre de scène : Forme normalisée produite par le frontal, entrée directe du moteur — chaque feuille porte son acteur et sa charge opaque
- BPx : Portage TypeScript du moteur de Bernard Bel ; dérive l’arbre dans le navigateur
- Projection : La suite plate de tokens datés, vue de l’arbre par calques (temps, identité, contrôles, acteur…)
- Resolver : Composant aval (1 par acteur) qui traduit token → fréquence via les six couches pitch
- CV engine : Composant aval qui résout les objets CV (ADSR, LFO, ramp) en bus audio ou messages CC
- Contrôles résolus : Les
()runtime, rangés par le moteur sur chaque terminal, lus tels quels par le dispatcher - Transport : Protocole de sortie pour les données horodatées (Web Audio, MIDI, OSC)
- Parité : Comparaison de la sortie au moteur natif de Bernard Bel, via un encodage en texte BP3
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 #BPx #dispatcher #acteurs
Prochain article : S11 — En live : modifier pendant que ça joue