B7) De BP3 à SuperCollider
Anatomie du transpileur
Vous savez maintenant ce qu’est un AST, comment les grammaires BP3 dérivent des séquences musicales, et comment SuperCollider orchestre des patterns. Mais comment passe-t-on concrètement de l’un à l’autre ?
Où se situe cet article ?
Cet article est le point de convergence des séries précédentes. Il réunit les AST (Abstract Syntax Tree, arbre de syntaxe abstraite — L4) de la série L, les patterns SuperCollider (I3) de la série I, et tout le formalisme BP3 détaillé dans B1 à B6. C’est ici que la théorie rencontre l’implémentation : on décortique le transpileur bp2sc, qui transforme du code BP3 en code SuperCollider jouable.
Encart : Transpileur vs compilateur
Un compilateur traduit un langage source vers un langage de plus bas niveau (C vers assembleur, par exemple). Un transpileur (ou source-to-source compiler) traduit entre deux langages de même niveau d’abstraction. Ici, BP3 et SuperCollider sont tous deux des langages de haut niveau dédiés à la musique. On traduit donc d’un formalisme génératif vers un formalisme de patterns — c’est bien une transpilation.
Pourquoi c’est important ?
Comprendre le transpileur, c’est comprendre le pont entre deux mondes : celui de la grammaire formelle (BP3) et celui de la synthèse sonore (SuperCollider). Sans ce pont, une grammaire BP3 reste un texte inerte — des règles sur papier. Avec lui, chaque règle se transforme en un flux d’événements musicaux.
L’analogie la plus parlante : imaginez un traducteur simultané lors d’une conférence. L’orateur parle en BP3 (grammaire, poids, modes) et le traducteur doit restituer exactement le même discours en SuperCollider (patterns, events, routines). La fidélité de la traduction est cruciale : une erreur et la musique ne correspond plus à l’intention du compositeur.
Mais ce traducteur ne travaille pas mot à mot. Il décompose d’abord le discours en unités de sens (l’analyse syntaxique), construit une représentation intermédiaire (l’AST), puis reformule dans la langue cible. C’est exactement ce que fait notre transpileur en cinq étapes.
L’idée en une phrase
Le transpileur bp2sc transforme du code BP3 en code SuperCollider en cinq phases distinctes — lexer (analyseur lexical), classifieur de lignes, parseur (analyseur syntaxique), validation, émetteur (générateur de code) — chacune avec une responsabilité unique.
Expliquons pas à pas
1. Le pipeline complet
Le transpileur suit un pipeline en cinq phases :
Code BP3 → [Lexer/Classifieur] → [Parseur] → AST → [Validation] → [Émetteur] → Code SC
Pourquoi cinq phases et pas une seule ? Pour la même raison qu’on ne construit pas une maison en une seule étape : chaque phase a une responsabilité précise, et cette séparation permet de tester et déboguer chaque étape indépendamment.
Encart : Séparation des responsabilités
En génie logiciel, le principe de séparation des responsabilités (separation of concerns) stipule que chaque module doit faire une seule chose et la faire bien. Un pipeline en cinq phases signifie cinq modules testables indépendamment :
- Le lexer/classifieur lit le texte brut et identifie le type de chaque ligne (commentaire, header, mode, préambule, règle)
- Le parseur analyse chaque ligne selon son type et construit les nœuds AST correspondants
- L’AST sert de représentation intermédiaire — un arbre structuré qui n’est lié ni à BP3 ni à SC
- La validation vérifie la cohérence de l’AST (poids normalisés, symboles définis, etc.)
- L’émetteur traverse l’AST et génère le code SuperCollider cible
Si le parseur a un bug, on le corrige sans toucher à l’émetteur. Si on veut ajouter un nouveau pattern SC, seul l’émetteur change. C’est la puissance de la modularité.
Encart : Le pipeline et la hiérarchie de Chomsky
Cette séparation en phases n’est pas arbitraire — elle reflète les niveaux de la hiérarchie de Chomsky (L1) :
Phase Niveau Chomsky Modèle de calcul Complexité Lexer/classifieur Type 3 (régulier) Automate fini / Regex O(n) Parseur Type 2 (context-free) Automate à pile / Earley O(n³) Validation Type 1 (context-sensitive) Visitors sur l’AST Variable Émetteur Transformation Parcours d’arbre O(n) >
En traitant d’abord les aspects simples (regex pour les tokens), on évite de payer le coût des niveaux supérieurs sur tout le texte. Chaque niveau enrichit la représentation : caractères → tokens → arbre → arbre vérifié → code cible.
Détaillons chaque phase.
Phase 1-2 : Lexer et classifieur de lignes
BP3 est un format orienté lignes : chaque ligne a un rôle spécifique (contrairement à un langage comme Python où une instruction peut s’étendre sur plusieurs lignes). Le classifieur utilise des expressions régulières (regex — notation compacte pour décrire des motifs de texte, cf. L9) pour identifier le type de chaque ligne :
| Pattern regex | Type de ligne | Exemple |
|---|---|---|
^\s*//(.*)$ |
Commentaire | // Indian raga composition |
^-(\w+)\.(.+)$ |
Référence fichier | -se.Mohanam |
^\s*INIT:\s*(.+)$ |
Directive INIT | INIT: MIDI program 110 |
^\s*(ORD|RND|LIN|SUB1|SUB)(\[...\])? |
Ligne de mode | ORD[1] |
^\s*(_\w+\(...\))+ |
Préambule | _mm(120) _striated |
^\s*gram#(\d+)\[(\d+)\] |
Règle | gram#1[1] S --> A B |
C’est un parseur à deux passes (two-phase parser) :
- Passe 1 : classification par type (quelle regex matche)
- Passe 2 : analyse détaillée du contenu (poids, flags, LHS, flèche, RHS)
Pour les lignes de règles, la passe 2 utilise un parseur par descente récursive (recursive descent parser — technique où chaque règle de grammaire est implémentée par une fonction qui s’appelle récursivement pour les sous-règles) qui extrait, dans l’ordre : le poids optionnel <N> ou <N-D>, les flags conditionnels /flag/ (cf. B4), le symbole de gauche (LHS, Left-Hand Side — partie gauche de la règle), la flèche -->, et la séquence de symboles à droite (RHS, Right-Hand Side — partie droite de la règle).
Phase 3 : L’AST (Arbre de Syntaxe Abstraite)
Le parseur produit un AST (L4) dont la racine est un noeud BPFile. Voici sa structure :
@dataclass
class BPFile:
headers: list[Header] # Comment, FileRef, InitDirective
grammars: list[GrammarBlock] # un ou plusieurs blocs de grammaire
@dataclass
class GrammarBlock:
mode: str # "ORD", "RND", "LIN", "SUB", "SUB1"
index: int | None # numéro de sous-grammaire [N]
label: str | None # étiquette optionnelle
preamble: list[SpecialFn] # fonctions avant les règles
rules: list[Rule] # les règles de production
@dataclass
class Rule:
grammar_num: int # gram#N
rule_num: int # [M]
weight: Weight | None # <3> ou <50-12>
flags: list[Flag] # /Ideas/, /NumR>5/
lhs: list[RHSElement] # côté gauche (S, A, etc.)
rhs: list[RHSElement] # côté droit (notes, symboles, etc.)
Cette structure est indépendante de la syntaxe textuelle de BP3 et du format de sortie SC. Mais attention : ses nœuds (BPFile, GrammarBlock, Rule, SpecialFn) restent des concepts propres à BP3. C’est un code intermédiaire (intermediate representation, IR) spécifique à BP3, pas une IR musicale générique. Une IR générique aurait des nœuds comme Section, Voice, Note, Tempo — indépendants de toute notation. Le transpileur pourrait émettre du LilyPond, du MIDI (Musical Instrument Digital Interface — protocole standard de communication entre instruments numériques, cf. M1) ou tout autre format à partir de cet AST, mais il faudrait toujours connaître la sémantique BP3 pour l’interpréter.
Phase 4-5 : Validation et émission
La validation vérifie que l’AST est bien formé : chaque symbole référencé dans un RHS est-il défini comme LHS quelque part ? Les poids sont-ils cohérents ?
L’émetteur (SCEmitter) traverse ensuite l’AST nœud par nœud et génère le code SuperCollider. C’est le cœur du transpileur, et nous allons le détailler dans les sections suivantes.
2. Structure d’un fichier BP3
Avant de traduire, il faut comprendre ce qu’on traduit. Un fichier BP3 a une structure précise :
-se.Tabla ← FileRef (settings)
-al.Tabla ← FileRef (alphabet de bols)
// Composition tabla - tintal ← Comment
ORD[1] ← Début du bloc de grammaire (mode, index)
_mm(160) ← Préambule (tempo en BPM)
gram#1[1] S --> A B ← Règle de production
gram#1[2] A --> dha dhin dhin dha ← Bols résonants (1er vibhāg)
gram#1[3] B --> dha tin tin ta ← Bols secs (3e vibhāg, khali)
Le nom « Bol Processor » vient directement de ces bols — les syllabes mnémoniques du tabla indien. L’alphabet (-al.Tabla) définit les bols comme terminaux de la grammaire, exactement comme Kippen et Bel l’ont conçu dans les années 1980 pour modéliser le tabla de Lucknow [KippenBel1989].
Les headers (en-têtes) déclarent les ressources externes :
| Nœud AST | Syntaxe BP3 | Rôle |
|---|---|---|
FileRef(prefix="se", name="Mohanam") |
-se.Mohanam |
Fichier de réglages (settings) |
FileRef(prefix="al", name="Mohanam") |
-al.Mohanam |
Fichier d’alphabet |
FileRef(prefix="ho", name="Mohanam") |
-ho.Mohanam |
Fichier d’homomorphismes |
FileRef(prefix="cs", name="Mohanam") |
-cs.Mohanam |
Fichier de sound-objects (csound) |
Comment(text="...") |
// texte |
Commentaire |
InitDirective(text="...") |
INIT: texte |
Directive d’initialisation |
Chaque bloc de grammaire (GrammarBlock) déclare un mode de dérivation (ORD, RND, LIN, SUB, SUB1 — cf. B3) et contient un préambule optionnel (fonctions spéciales appliquées à tout le bloc) suivi de règles de production.
3. De BP3 à SC : la traduction des modes
C’est le coeur de la correspondance. Chaque mode de dérivation BP3 (cf. B3) se traduit en un pattern SC (SuperCollider pattern — objet qui génère une séquence d’événements musicaux) spécifique. Les non-terminaux BP3 deviennent des Pdef (Pattern Definition — conteneur nommé et réutilisable de SuperCollider) :
| Mode BP3 | Conditions | Pattern SC | Comportement |
|---|---|---|---|
| ORD | — | Pseq([...], 1) |
Séquentiel, déterministe |
| RND | Sans poids | Prand([...], 1) |
Aléatoire uniforme |
| RND | Avec poids <N> |
Pwrand([...], [w1,w2,...].normalizeSum, 1) |
Aléatoire pondéré |
| RND | Avec décréments <N-D> |
Prout({ |ev| ... }) |
Routine avec poids mutables |
| LIN | — | Prand([...], 1) |
Identique à RND côté SC |
| SUB | — | Comme ORD ou RND | Identité globale gérée à la dérivation |
| SUB1 | — | Pseq([...], 1) |
Séquentiel, comme ORD |
| (flags) | /flag/ présent |
Prout({ |ev| ... if/else ... }) |
Conditions dynamiques |
Encart : Pourquoi LIN ≈ RND en SuperCollider ?
Le mode LIN (linéaire cyclique) signifie : « quand un non-terminal a plusieurs règles, applique-les en cyclant dans l’ordre — règle 1 la première fois, règle 2 la deuxième, etc. ». C’est un comportement stateful (à mémoire) : il faut savoir combien de fois le non-terminal a déjà été développé.
Le transpileur ne réalise pas la dérivation — il traduit la structure de la grammaire. Il ne peut pas tracker le compteur de cycles. La traduction fidèle serait un
Proutavec compteur :Pdef(\X, Prout({ |ev| var rules = [...]; var idx = 0; inf.do { rules[idx % rules.size].embedInStream(ev); idx = idx + 1; } }));
Par simplicité, le transpileur utilise
Prandcomme approximation : il capture l’aspect « plusieurs alternatives possibles » de LIN, mais pas l’ordre cyclique. C’est une limitation connue du transpileur statique — le mode LIN véritable nécessite un moteur de dérivation à état.
Exemple de traduction ORD — theka de tintāl :
BP3 :
ORD[1]
gram#1[1] S --> Vibhag1 Vibhag2
gram#1[2] Vibhag1 --> dha dhin dhin dha
gram#1[3] Vibhag2 --> dha tin tin ta
SC généré :
Pdef(\S,
Pseq([Pdef(\Vibhag1), Pdef(\Vibhag2)], 1)
);
Pdef(\Vibhag1,
Pbind(\midinote, Pseq([dha, dhin, dhin, dha], 1), \dur, 0.25)
);
Pdef(\Vibhag2,
Pbind(\midinote, Pseq([dha, tin, tin, ta], 1), \dur, 0.25)
);
Chaque vibhāg (section du tāla) devient un Pdef nommé. Les bols — dha, dhin, tin, ta — sont les terminaux de l’alphabet tabla, mappés vers des numéros MIDI ou des SynthDefs de percussion par le fichier -al.Tabla.
Exemple de traduction RND avec poids — improvisation tabla :
BP3 :
RND[2]
gram#2[1] <3> Motif --> dha tirakita dhin dhin
gram#2[2] <1> Motif --> tin ta dha ge na
SC généré :
Pdef(\Motif,
Pwrand([
Pbind(\midinote, Pseq([dha, tirakita, dhin, dhin], 1), \dur, 0.25),
Pbind(\midinote, Pseq([tin, ta, dha, ge, na], 1), \dur, 0.25)
], [3, 1].normalizeSum, 1)
);
Les motifs résonants (dha tirakita dhin dhin, section thali du tāla) sont trois fois plus probables que les motifs secs (tin ta dha ge na, section khali) — un reflet fidèle de la pratique du tabla, où les sections résonantes dominent. Le .normalizeSum de SuperCollider normalise automatiquement les poids en probabilités : [3, 1].normalizeSum donne [0.75, 0.25]. C’est l’équivalent de la normalisation vue dans B1.
Exemple de poids décrémentaux (Prout) :
BP3 :
RND[1]
gram#1[1] <50-12> S --> A S
gram#1[2] <1> S --> fin
SC généré :
Pdef(\S, Prout({ |ev|
var w0 = 50; // décrément: 12
var w1 = 1;
inf.do {
var total = w0 + w1;
var r = total.rand;
if(r < w0) {
Pseq([Pdef(\A), Pdef(\S)], 1).embedInStream(ev);
w0 = (w0 - 12).max(0);
} {
Pdef(\fin).embedInStream(ev);
}
}
}));
Les variables w0 et w1 sont mutables (modifiables en cours d’exécution) : après chaque utilisation de la première règle, w0 diminue de 12. La méthode .embedInStream(ev) insère les événements d’un pattern dans le flux de la routine Prout. Quand il atteint 0, seule la deuxième règle (terminaison) reste possible. C’est le mécanisme de poids dynamiques décrit dans B1, traduit en routine SuperCollider.
4. Les 42+ fonctions spéciales (SpecialFn — noeud AST pour les fonctions intégrées)
BP3 dispose d’un riche ensemble de fonctions spéciales qui modifient le comportement musical. Dans l’AST, elles sont représentées par le nœud SpecialFn(name, args). Le transpileur les traduit en paires clé-valeur SuperCollider.
Les fonctions se répartissent en trois catégories :
- Fonctions de préambule : placées avant les règles, elles s’appliquent à tout le bloc (
_mm,_striated) - Fonctions de modification : dans le RHS, elles modifient les notes suivantes (
_transpose,_vel,_staccato) - Fonctions informationnelles : commentaires ou métadonnées sans effet sonore direct (
_part,_velcont)
Hauteur (Pitch)
| BP3 | SC | Description |
|---|---|---|
_transpose(N) |
\ctranspose, N |
Transposition chromatique de N demi-tons |
_scale(nom, fondamentale) |
\scale, Scale.xxx, \root, N |
Sélection d’échelle |
_pitchbend(N) |
\detune, N |
Micro-detuning en cents |
Exemple : _transpose(5) devant do4 ré4 produit :
Pbindf(Pbind(\midinote, Pseq([60, 62], 1), \dur, 0.25), \ctranspose, 5)
Le Pbindf est le pattern SC qui « ajoute » des clés à un pattern existant. Ici, il ajoute \ctranspose, 5 à toutes les notes du Pbind interne. Le do4 (MIDI 60) jouera donc comme MIDI 65, et le ré4 (MIDI 62) comme MIDI 67.
Vélocité (Velocity — intensité de frappe d’une note, Dynamics)
| BP3 | SC | Description |
|---|---|---|
_vel(N) |
\amp, N/127 |
Vélocité fixe (MIDI 0-127 vers amplitude 0-1) |
_volume(N) |
\amp, N/127 |
Synonyme de _vel |
_rndvel(N) |
\amp, Pwhite(lo, hi) |
Vélocité aléatoire dans un intervalle +-N |
Exemple : _vel(100) suivi de _rndvel(20) produit :
\amp, Pwhite(0.63, 0.945) // (100-20)/127 à (100+20)/127
Le Pwhite génère des valeurs uniformément distribuées entre lo et hi — une façon élégante de traduire la variabilité naturelle de l’interprétation.
Articulation
| BP3 | SC | Description |
|---|---|---|
_staccato(N) |
\legato, N/100 |
Rapport durée jouée / durée théorique |
_legato(N) |
\legato, N/100 |
Idem (les deux utilisent le même calcul) |
En SuperCollider, \legato contrôle la fraction de la durée pendant laquelle la note sonne : 0.5 = staccato (notes courtes et détachées), 1.0 = plein, 1.5 = legato (notes liées, débordant sur la suivante).
Timing et tempo
| BP3 | SC | Description |
|---|---|---|
_mm(BPM) |
TempoClock.default.tempo = BPM/60; |
Tempo global en BPM (Beats Per Minute — battements par minute), en préambule |
_tempo(N) |
\stretch, 1/N |
Facteur de vitesse relative |
_rndtime(N) |
\dur, Pwhite(lo, hi) |
Variation aléatoire du timing (+-N%) |
Encart : _mm vs _tempo
_mm(120)définit le tempo absolu (120 battements par minute). C’est un réglage global, émis une seule fois dans le préambule du fichier SC.
_tempo(2)est un facteur relatif : « joue 2 fois plus vite ». En SC, cela se traduit par\stretch, 0.5(chaque note dure la moitié de sa durée normale). C’est un modificateur local, qui affecte uniquement les notes qui le suivent.
Instruments et programmes MIDI
| BP3 | SC | Description |
|---|---|---|
_ins(nom) |
\instrument, \nom |
Sélection d’instrument (SynthDef — Synth Definition, définition de synthétiseur en SC) |
_script(MIDI program N) |
\program, N |
Programme MIDI |
_chan(N) |
\chan, N |
Canal MIDI |
Pédales (import MusicXML)
| BP3 | SC | Description |
|---|---|---|
_sustainstart() |
\sustain, 1 |
Pédale de sustain enfoncée |
_sustainstop() |
\sustain, 0 |
Pédale relâchée |
_sostenutostart() |
\sostenuto, 1 |
Pédale sostenuto (maintient uniquement les notes déjà enfoncées) |
_softstart() |
\softPedal, 1 |
Una corda (pédale douce — atténue le son) |
Structure et répétition
| BP3 | SC | Description |
|---|---|---|
_repeat(N) |
Pn(élément_suivant, N) |
Répéter N fois l’élément suivant |
_retro() |
Inversion de la liste | Rétrograde (miroir) |
_rotate(N) |
Rotation de la liste | Rotation de N positions |
_retro et _rotate opèrent à l’intérieur d’expressions polymétriques (entre accolades {}). Le transpileur les détecte, applique la transformation sur la liste d’éléments, puis génère le pattern résultant.
5. Polymétrie, liaisons et constructions avancées
Les modes de dérivation et les fonctions spéciales ne couvrent pas toutes les constructions BP3. Le transpileur doit aussi gérer la polymétrie (cf. B5), les liaisons, et les homomorphismes (cf. B6).
Polymétrie mono-voix : compression et dilatation
Une expression polymétrique mono-voix {M, éléments} compresse ou dilate une séquence pour qu’elle tienne dans M unités de temps. Le transpileur utilise Pbindf avec la clé \stretch :
BP3 :
{3, dha dhin dhin dha}
SC généré :
Pbindf(
Pbind(\midinote, Pseq([36, 42, 42, 36], 1), \dur, 0.25),
\stretch, 3/4
)
La formule est \stretch = M / N où M est la durée cible et N le nombre d’éléments. Ici, 3/4 = 0.75 : chaque bol dure 75% de sa durée normale, compressant 4 bols dans l’espace de 3 temps.
Encart :
\stretchvs\durEn SuperCollider,
\durfixe la durée absolue d’un événement (en beats).\stretchest un multiplicateur appliqué à\dur: la durée effective estdur × stretch. Le transpileur utilise\stretchplutôt que de recalculer\durpour préserver les rapports de durée internes — si une note est deux fois plus longue qu’une autre, elle le reste après compression.
Polymétrie multi-voix : superposition parallèle
Une expression multi-voix {voix1, voix2} superpose deux séquences en parallèle. Le transpileur utilise Ppar (Parallel Pattern — pattern qui joue plusieurs flux simultanément) :
BP3 :
{dha dhin dhin dha, Sa Re Ga Ma Pa}
SC généré :
Ppar([
Pbindf(
Pbind(\midinote, Pseq([36, 42, 42, 36], 1), \dur, 0.25),
\stretch, 1/1
),
Pbindf(
Pbind(\midinote, Pseq([60, 62, 64, 65, 67], 1), \dur, 0.25),
\stretch, 4/5
)
])
La première voix (tabla, 4 bols) définit la durée de référence. La seconde (sargam, 5 notes) est compressée avec \stretch = 4/5 pour tenir dans le même temps — une polymétrie 4 contre 5, typique des textures croisées entre percussion et mélodie.
Liaisons (Tie)
Les liaisons (ties) prolongent une note en fusionnant deux notes consécutives de même hauteur. Le transpileur les traduit en deux mécanismes complémentaires :
| Position | SC | Effet |
|---|---|---|
| Début de liaison | \legato, 2.0 |
La note sustains au-delà de sa durée nominale |
Continuation (~) |
Event.silent(dur) |
Événement silencieux — la note précédente résonne |
Event.silent(dur) est un événement SuperCollider qui n’émet aucun son pendant la durée dur. En le plaçant là où la continuation de la liaison devrait être, on « remplit » le temps sans interrompre la note liée.
Le transpileur maintient un suivi interne (_pending_tie_midi) : il mémorise le numéro MIDI de la note de départ et vérifie que chaque continuation a la même hauteur. Si les hauteurs diffèrent, un avertissement est émis — une liaison entre deux notes différentes est probablement une erreur du compositeur.
Signatures de mesure
Les signatures comme 4/4 ou 7/8 n’ont pas d’équivalent natif en SuperCollider (qui raisonne en beats, pas en mesures). Le transpileur les émet actuellement comme commentaires informatifs :
// Time signature: 7/8 (tala rupak)
Ces commentaires n’affectent pas la lecture. Pourtant, les signatures pourraient influencer la structure générée — par exemple, un 7/8 en tāla rupak (schéma additif 3+2+2) pourrait se traduire en groupements de notes via Pn ou en accentuation automatique des temps forts. C’est une simplification actuelle du transpileur, pas une impossibilité technique. Un transpileur plus complet pourrait exploiter les signatures pour refléter la structure métrique, notamment pour les tāla indiens aux schémas additifs complexes (cf. B5).
Variables et symboles
Les variables BP3 (|x| — cf. B6) sont traduites comme des Pdef, identiquement aux non-terminaux :
BP3 :
S --> |x| - |x| - |x|
SC généré :
Pdef(\S,
Pseq([Pdef(\x), Rest(0.25), Pdef(\x), Rest(0.25), Pdef(\x)], 1)
);
La contrainte d’identité du tihāī (cf. B5) — les trois |x| doivent être le même motif — est naturellement assurée : toutes les occurrences pointent vers le même Pdef(\x).
Les symboles quotés ("nom") sont également traduits en Pdef(\nom), avec un avertissement si le symbole n’est défini nulle part dans la grammaire.
Homomorphismes (MASTER / SLAVE)
Les homomorphismes (cf. B6) définissent des tables de substitution systématique. Le transpileur gère les trois types du nœud HomoApply :
| Type | Traduction SC | Rôle |
|---|---|---|
| MASTER | Séquence avec substitutions SLAVE appliquées | La musique transformée |
| SLAVE | (intégré dans MASTER) | La table de correspondance |
| REF | Aucune (skip) | Étiquette sans contenu sonore |
Le transpileur résout l’homomorphisme au moment de la génération : il prend la séquence MASTER, applique les substitutions SLAVE, et émet directement le pattern résultant. L’homomorphisme est « compilé » — sa table de correspondance disparaît du code SC généré.
Exemple — graha bheda : si le MASTER contient Sa Re Ga Pa et le SLAVE substitue chaque note un ton plus haut (Sa→Re, Re→Ga, Ga→Ma, Pa→Dha), le code SC émis contiendra directement les numéros MIDI transposés (62, 64, 65, 69), sans trace de la table d’homomorphisme originale.
6. Éléments non émis
Certains noeuds de l’AST n’ont pas de traduction directe en SuperCollider (ces constructions avancées sont détaillées dans B6). Le transpileur les signale par des warnings (avertissements) et les émet comme commentaires :
| Nœud AST | Raison de l’omission | Émission SC |
|---|---|---|
ContextMarker |
Nécessite un moteur de dérivation context-sensitive | Aucune (skip) |
GotoDirective |
Nécessite un moteur de dérivation avec sauts | Aucune (skip) |
Wildcard |
Nécessite un système de pattern-matching | Rest() |
HomoApply(kind=REF) |
Étiquette d’homomorphisme, pas un son | Aucune (skip) |
_rotate(N) dans le RHS |
Ne s’applique qu’en contexte polymétrique | SC comment |
_failed(...) |
Gestion d’erreurs du moteur BP3 | SC comment |
Lambda |
Noeud interne sans contenu musical | Event.silent(0) |
Annotation |
Métadonnée textuelle (titre, commentaire structurel) | Aucune (skip) |
Encart : Pourquoi ne pas tout traduire ?
Le transpileur traduit la structure statique d’une grammaire BP3. Mais BP3 est aussi un moteur de dérivation : il applique les règles, évalue les flags, gère les contextes. Les éléments comme
ContextMarkerouGotoDirectiven’ont de sens que pendant la dérivation — ils contrôlent le processus, pas le résultat.Traduire ces éléments nécessiterait d’embarquer un moteur de dérivation complet dans SuperCollider, ce qui dépasse le cadre du transpileur actuel. Le transpileur génère la « partition potentielle » ; le moteur de dérivation est une fonctionnalité séparée.
7. Les patterns SuperCollider utilisés
Voici un résumé des patterns SC générés par le transpileur, avec leur rôle :
| Pattern SC | Signature | Rôle |
|---|---|---|
Pdef(\nom, pattern) |
Pdef(\name, pattern) |
Définition nommée — permet la réactivité en temps réel |
Pbind(\clé, val, ...) |
Pbind(\key, val, ...) |
Flux d’événements avec paires clé-valeur |
Pseq([...], N) |
Pseq([items], repeats) |
Jouer les éléments en séquence, N fois |
Prand([...], N) |
Prand([items], repeats) |
Choisir un élément au hasard, N fois |
Pwrand([...], poids, N) |
Pwrand([items], [weights], repeats) |
Choix pondéré |
Ppar([...]) |
Ppar([patterns]) |
Jouer plusieurs patterns en parallèle |
Pbindf(pattern, \clé, val) |
Pbindf(pat, \key, val) |
Ajouter des clés à un pattern existant |
Pn(pattern, N) |
Pn(pattern, repeats) |
Répéter un pattern N fois |
Prout({...}) |
Prout({ |ev| ... }) |
Routine (pour flags et poids mutables) |
Event.silent(dur) |
Event.silent(dur) |
Événement silencieux (liaisons, placeholders) |
Encart : Pourquoi Pdef et pas une variable ?
Pdef(Pattern Definition) est un conteneur nommé et réactif de SuperCollider. Contrairement à une simple variable (~monPattern = Pseq(...)) :
- Il peut être modifié en temps réel pendant la lecture (live coding — pratique de programmation musicale en direct)
- Il est accessible globalement par son nom symbolique (
\S,\A, etc.)- Il gère automatiquement les transitions entre anciennes et nouvelles versions
C’est exactement ce qu’il faut pour représenter les non-terminaux BP3 : chaque symbole (
S,A,B) devient unPdefque l’on peut référencer, modifier et combiner.
La correspondance fondamentale :
Non-terminal BP3 → Pdef(\nom, ...)
Règle BP3 → Pattern SC (Pseq, Prand, Pwrand, Prout)
Note BP3 → Numéro MIDI dans Pbind
Silence BP3 (-) → Rest() ou Event.silent(0.25)
Fonction spéciale → Paire \clé, valeur dans Pbindf
Polymétrie {M, …} → Pbindf(..., \stretch, M/N)
Voix parallèles → Ppar([...])
Liaison (~) → \legato, 2.0 + Event.silent
Variable |x| → Pdef(\x)
Homomorphisme → Substitution inline (résolu à la compilation)
8. Cas concret : dérivation complète BP3 vers code SC
Prenons un exemple complet et suivons-le à travers tout le pipeline. Fidèles à l’esprit du Bol Processor, nous utilisons une grammaire de tabla — l’instrument pour lequel BP a été créé par Kippen et Bel [BelKippen1992a].
Fichier BP3 source — Kayda en tintāl :
-se.Tabla
-al.Tabla
// Kayda - composition tabla en tintal
ORD[1]
_mm(160)
gram#1[1] S --> Theka Impro
RND[2]
gram#2[1] <3> Impro --> dha tirakita dhin dhin
gram#2[2] <1> Impro --> tin ta dha ge na
gram#2[3] Theka --> dha dhin dhin dha
Ce fichier encode un kayda (composition de tabla avec thème fixe et improvisations) : le theka (motif de base du tintāl) est déterministe (ORD), tandis que les motifs d’improvisation sont probabilistes (RND), les motifs résonants étant trois fois plus fréquents que les motifs secs.
Étape 1 : Classification des lignes
Le classifieur identifie chaque ligne :
| Ligne | Type | Nœud AST |
|---|---|---|
-se.Tabla |
FileRef | FileRef(prefix="se", name="Tabla") |
-al.Tabla |
FileRef | FileRef(prefix="al", name="Tabla") |
// Kayda - composition... |
Comment | Comment(text="Kayda - composition tabla en tintal") |
ORD[1] |
Mode | GrammarBlock(mode="ORD", index=1) |
_mm(160) |
Préambule | SpecialFn(name="mm", args=["160"]) |
gram#1[1] S --> Theka Impro |
Règle | Rule(grammar_num=1, rule_num=1, ...) |
RND[2] |
Mode | GrammarBlock(mode="RND", index=2) |
gram#2[1] <3> Impro --> ... |
Règle + poids | Rule(..., weight=Weight(value=3), ...) |
| etc. |
Étape 2 : Construction de l’AST
L’AST résultant :
BPFile(
headers=[
FileRef(prefix="se", name="Tabla"),
FileRef(prefix="al", name="Tabla"),
Comment(text="Kayda - composition tabla en tintal"),
],
grammars=[
GrammarBlock(
mode="ORD", index=1,
preamble=[SpecialFn(name="mm", args=["160"])],
rules=[
Rule(gram=1, rule=1, lhs=[NonTerminal("S")],
rhs=[NonTerminal("Theka"), NonTerminal("Impro")])
]
),
GrammarBlock(
mode="RND", index=2,
preamble=[],
rules=[
Rule(gram=2, rule=1, weight=Weight(3),
lhs=[NonTerminal("Impro")],
rhs=[Terminal("dha"), Terminal("tirakita"),
Terminal("dhin"), Terminal("dhin")]),
Rule(gram=2, rule=2, weight=Weight(1),
lhs=[NonTerminal("Impro")],
rhs=[Terminal("tin"), Terminal("ta"),
Terminal("dha"), Terminal("ge"), Terminal("na")]),
Rule(gram=2, rule=3, weight=None,
lhs=[NonTerminal("Theka")],
rhs=[Terminal("dha"), Terminal("dhin"),
Terminal("dhin"), Terminal("dha")]),
]
),
]
)
Étape 3 : Analyse par l’émetteur
L’émetteur (SCEmitter) analyse l’AST :
- Collecte des règles par symbole LHS :
– S est défini dans le bloc 1 (ORD) avec 1 règle
– Impro est défini dans le bloc 2 (RND) avec 2 règles (poids 3 et 1)
– Theka est défini dans le bloc 2 (RND) avec 1 règle (sans poids)
- Détection multi-bloc :
Sn’est défini que dans le bloc 1,ImproetThekadans le bloc 2 — pas de conflit de noms.
- Extraction du tempo :
_mm(160)dans le préambule du bloc 1 fixe le tempo à 160 BPM — un tempo rapide typique du drut laya (tempo rapide) du tabla.
- Résolution des terminaux : Les bols (
dha,dhin,tin,ta,tirakita,ge,na) sont résolus via le fichier-al.Tablaqui mappe chaque bol vers un numéro MIDI ou un identifiant de SynthDef percussif. Par exemple :dha→ MIDI 36 (grosse caisse),tin→ MIDI 38 (caisse claire), etc.
Étape 4 : Génération du code SuperCollider
Voici le code SC complet généré :
// BP3 Grammar: Tabla
// Generated by bp2sc from: Tabla Kayda
// Bol Processor BP3 → SuperCollider Pattern transpiler
//
// Usage:
// 1. Boot the server: s.boot;
// 2. Execute this file: Ctrl+Enter (select all)
// 3. To stop: Cmd+. or Ctrl+.
// 4. Morph live: re-evaluate individual Pdef blocks
(
// Default SynthDef for testing (replace with your own)
SynthDef(\bp2sc_default, {
|out=0, freq=440, amp=0.1, gate=1, pan=0|
var sig, env;
env = EnvGen.kr(Env.adsr(0.01, 0.1, 0.6, 0.3), gate, doneAction: 2);
sig = SinOsc.ar(freq) + Pulse.ar(freq * 1.001, 0.3, 0.3);
sig = LPF.ar(sig, freq * 4);
Out.ar(out, Pan2.ar(sig * env * amp, pan));
}).add;
TempoClock.default.tempo = 160.0 / 60;
// BP3 reference: -se.Tabla, -al.Tabla
// Kayda - composition tabla en tintal
// --- Subgrammar 1 (ORD) ---
Pdef(\S,
Pseq([Pdef(\Theka), Pdef(\Impro)], 1)
);
// --- Subgrammar 2 (RND) ---
Pdef(\Impro,
Pwrand([
Pbind(\midinote, Pseq([36, 42, 38, 38], 1), \dur, 0.25),
Pbind(\midinote, Pseq([38, 37, 36, 43, 40], 1), \dur, 0.25)
], [3, 1].normalizeSum, 1)
);
Pdef(\Theka,
Pbind(\midinote, Pseq([36, 38, 38, 36], 1), \dur, 0.25)
);
// --- Play ---
Pdef(\S).play;
)
Décortiquons chaque élément généré :
Ligne 1-9 : L’en-tête
Un commentaire SC standard avec le nom du fichier source et des instructions d’utilisation. Généré par sc_header().
Ligne 11 : L’ouverture (
En SuperCollider, les parenthèses englobantes ( ... ) créent un bloc d’exécution : tout le code entre ( et ) est évalué d’un seul coup quand on appuie sur Ctrl+Entrée.
Lignes 13-21 : Le SynthDef
Un synthétiseur minimal (SinOsc + Pulse filtré passe-bas) fourni par défaut. Le compositeur le remplacera par ses propres SynthDefs — idéalement un synthétiseur de tabla avec les timbres réalistes de chaque bol. Généré par sc_synthdef_default().
Ligne 23 : Le tempoTempoClock.default.tempo = 160.0 / 60; fixe le tempo à ~2.67 battements par seconde (160 BPM, tempo rapide typique du tabla). Traduit de _mm(160) dans le préambule. Généré par sc_tempo(160.0).
Lignes 25-26 : Les références
Les headers BP3 (-se.Tabla, -al.Tabla, commentaire) deviennent des commentaires SC. Le fichier -al.Tabla (alphabet de bols) est crucial — c’est lui qui contient le mappage entre les noms de bols et les numéros MIDI.
Ligne 30-32 : Pdef pour SS --> Theka Impro en mode ORD se traduit par Pseq([Pdef(\Theka), Pdef(\Impro)], 1). La structure est claire : d’abord le theka (motif de base), puis l’improvisation.
Lignes 36-41 : Pdef pour ImproImpro a deux règles avec poids 3 et 1 en mode RND. Le Pwrand donne 75% de chance au motif résonant (dha tirakita dhin dhin) et 25% au motif sec (tin ta dha ge na) — reflet fidèle de la pratique du tabla, où les sections thali (résonantes) dominent.
Lignes 43-45 : Pdef pour ThekaTheka a une seule règle sans poids. Un simple Pbind avec les quatre bols du premier vibhāg du tintāl en séquence.
Ligne 48 : La commande de lecturePdef(\S).play; déclenche la lecture. SuperCollider va :
- Évaluer
Pdef(\S), qui lancePseq([Pdef(\Theka), Pdef(\Impro)]) - Jouer le theka :
dha dhin dhin dha - Évaluer
Pdef(\Impro), qui choisit aléatoirement un des deux motifs - Jouer le motif d’improvisation sélectionné
Le résultat sera par exemple : dha dhin dhin dha dha tirakita dhin dhin (75% des cas) ou dha dhin dhin dha tin ta dha ge na (25% des cas) — exactement comme un tabliste qui joue d’abord le theka puis improvise.
Encart : La puissance du Pdef pour le live coding
Une fois le code lancé, vous pouvez modifier un
Pdefindividuel et le ré-évaluer sans arrêter la musique. Par exemple, changerPdef(\B)pour utiliser d’autres notes mettra à jour le pattern en temps réel. C’est le morphing live mentionné dans l’en-tête du fichier généré.
Invariants du code généré
Le transpileur respecte trois invariants structurels, documentés dans le code source :
INV-1 : Pas de commentaires dans les tableaux. Les commentaires SC (//) ne doivent jamais apparaître à l’intérieur de Pseq([...]), Prand([...]) ou Pwrand([...]), car SC les interprète comme des erreurs de syntaxe. Les commentaires sont émis avant ou après les expressions.
INV-2 : Pas d’entiers MIDI nus dans Pseq. Chaque nombre MIDI doit être enveloppé dans un Pbind. Un Pseq([60, 62, 64]) nu ne produit pas d’événements musicaux — il faut Pbind(\midinote, Pseq([60, 62, 64], 1)).
INV-3 : Délimiteurs équilibrés. Toutes les parenthèses, crochets et accolades sont vérifiés pour être correctement appariés.
Ce qu’il faut retenir
- Le transpileur bp2sc fonctionne en cinq phases : lexer, classifieur, parseur, validation, émetteur. Chaque phase a une responsabilité unique.
- L’AST sert de représentation intermédiaire neutre entre BP3 et SuperCollider. Sa racine est un nœud
BPFilecontenant des headers et des blocs de grammaire. - Les modes de dérivation se traduisent en patterns SC précis :
Pseqpour ORD,Prand/Pwrandpour RND,Proutpour les flags et les poids décrémentaux. - Les 42+ fonctions spéciales couvrent la hauteur, la vélocité, l’articulation, le timing, les instruments et les pédales. Chacune se traduit en une paire
\clé, valeurSC. - Les constructions avancées — polymétrie, liaisons, variables, homomorphismes — sont traduites en patterns SC adaptés :
Pbindfavec\stretchpour la compression temporelle,Pparpour les voix parallèles,Pdefpour les variables. - Certains éléments BP3 (ContextMarker, GotoDirective, Wildcard, Lambda, Annotation) ne sont pas émis car ils nécessitent un moteur de dérivation ou n’ont pas de contenu sonore.
- Chaque non-terminal BP3 devient un Pdef SuperCollider, ce qui permet le live coding et le morphing en temps réel.
Pour aller plus loin
- Code source : le transpileur bp2sc est disponible sur GitHub, avec 198 tests couvrant chaque nœud AST
- Documentation SC : SuperCollider Pattern Guide
- Documentation BP3 : Bol Processor – Pattern Grammars
- Bel, B. & Kippen, J. (1992). « Modelling Music with Grammars: Formal Language Representation in the Bol Processor » — l’article fondateur décrivant le pipeline complet grammaire → dérivation → sortie musicale pour le tabla.
- Bel, B. (1998). « Migrating Musical Concepts — An Overview of the Bol Processor », Computer Music Journal 22(3) — vue d’ensemble de l’architecture BP2, le prédécesseur de BP3.
- Bel, B. (2001). « Rationalizing Musical Time: Syntactic and Symbolic-Numeric Approaches » — formalisation du temps musical dans BP, influence directe sur TidalCycles.
- Kippen, J. & Bel, B. (2016). « Computers, Composition and New Music in Modern India » — bilan de 30 ans de collaboration sur BP et le tabla.
- Livre : Aho, Lam, Sethi & Ullman, Compilers: Principles, Techniques, and Tools (le « Dragon Book »), chapitres 1-2 pour les pipelines de compilation
Glossaire
- AST (Abstract Syntax Tree) : Représentation arborescente et structurée du code source, indépendante de la syntaxe textuelle. L’AST de BP3 a pour racine un nœud BPFile.
- BPFile : Nœud racine de l’AST BP3, contenant les headers (références fichiers, commentaires) et les blocs de grammaire.
- Classifieur de lignes : Première passe du parseur qui identifie le type de chaque ligne (commentaire, header, mode, préambule, règle) via des expressions régulières.
- Descente récursive : Technique de parsage où chaque règle de grammaire correspond à une fonction qui s’appelle récursivement pour les sous-règles.
- Émetteur (Emitter) : Module qui traverse l’AST et génère le code cible (ici, SuperCollider). Aussi appelé code generator ou backend.
- embedInStream : Méthode SuperCollider qui insère les événements d’un pattern dans le flux d’une routine (
Prout), permettant le contrôle dynamique du flux. - GrammarBlock : Nœud AST représentant un bloc de grammaire avec un mode (ORD, RND, LIN, SUB, SUB1), un préambule et des règles.
- Invariant : Propriété du code généré qui doit toujours être vraie (par exemple : pas de commentaire dans un tableau SC).
- Lexer : Module qui transforme le texte brut en unités lexicales (tokens). Ici, combiné avec le classifieur de lignes.
- Live coding : Pratique de modification du code en temps réel pendant l’exécution, facilitée par Pdef en SuperCollider.
- Morphing : Transition progressive entre deux versions d’un pattern, rendue possible par la ré-évaluation de Pdef individuels.
- normalizeSum : Méthode SC qui divise chaque élément d’un tableau par la somme totale, convertissant des poids en probabilités.
- Parseur (Parser) : Module qui analyse la structure syntaxique du code source et construit l’AST. Ici, un parseur à deux passes (classifieur + descente récursive).
- Pbind : Pattern SC qui crée un flux d’événements musicaux à partir de paires clé-valeur (instrument, note, durée, etc.).
- Pbindf : Variante de Pbind qui ajoute ou remplace des clés dans un pattern existant, sans le modifier structurellement.
- Pdef : Conteneur nommé et réactif pour un pattern SC. Permet le live coding et le morphing en temps réel.
- Pipeline : Chaîne de transformations où la sortie d’une étape est l’entrée de la suivante. Ici : texte BP3 → tokens → AST → code SC.
- Prand : Pattern SC qui choisit un élément au hasard (distribution uniforme) dans un tableau, N fois.
- Prout : Pattern SC basé sur une routine, permettant un contrôle programmatique du flux (conditions, boucles, variables mutables).
- Pseq : Pattern SC qui joue les éléments d’un tableau en séquence, N fois.
- Pwrand : Pattern SC qui choisit un élément selon des poids (distribution pondérée), N fois.
- Rule : Nœud AST représentant une règle de production BP3 avec numéro de grammaire, numéro de règle, poids, flags, LHS et RHS.
- SpecialFn : Nœud AST représentant une fonction spéciale BP3 (
_transpose,_vel,_mm, etc.) avec nom et arguments. - Transpileur (Transpiler) : Traducteur source-à-source entre deux langages de même niveau d’abstraction (ici, BP3 vers SuperCollider).
- Bol : Syllabe mnémonique du tabla (ex : dha, dhin, tin, ta). Le « Bol » dans « Bol Processor ». Mappé vers un numéro MIDI ou un SynthDef via le fichier
-al.Tabla. - Kayda : Composition de tabla avec un thème fixe (theka) et des variations systématiques. Structure naturellement modélisable par une grammaire BP3 (ORD pour le thème, RND pour les variations).
- Theka : Pattern de base d’un tāla, joué par le tabla en ostinato. En BP3, typiquement une règle ORD déterministe.
- Tintāl : Tāla le plus courant en musique hindustani (16 temps en 4 vibhāg de 4 temps). Son theka :
dha dhin dhin dha | dha dhin dhin dha | dha tin tin ta | ta dhin dhin dha. - Event.silent(dur) : Événement SuperCollider qui n’émet aucun son pendant la durée
dur. Utilisé pour les continuations de liaisons et les nœuds Lambda. - HomoApply : Nœud AST représentant l’application d’un homomorphisme. Trois variantes : MASTER (séquence source transformée), SLAVE (table de substitution, intégrée dans MASTER), REF (étiquette, non émise).
- \stretch : Clé SuperCollider qui multiplie la durée de chaque événement par un facteur.
\stretch, 0.75compresse le temps de 25%. Utilisé pour la traduction des expressions polymétriques (\stretch = M/N). - Tie (liaison) : Construction BP3 qui fusionne deux notes consécutives de même hauteur en une seule note prolongée. Traduite par
\legato, 2.0(début) etEvent.silent(continuation). - Variable : Emplacement nommé
|x|dont la valeur est fixée à la première occurrence. Traduit enPdef(\x), assurant l’identité entre toutes les occurrences (crucial pour le tihāī).
Prérequis : L4 — AST, I3 — SuperCollider, B1 — PCFG à B6 — Homomorphismes
Temps de lecture : 20 min
Tags : #transpileur #pipeline #SuperCollider #patterns #BP3 #AST #traduction