S3) Types, actors et bindings
Comment BPscript sait ce qui occupe du temps, où l’envoyer, et à quelle fréquence
En eurorack, chaque câble porte un type de signal : gate, trigger ou CV. BPscript fait pareil avec ses symboles. Mais un câble va quelque part — et ce « quelque part » est défini par l’actor.
Où se situe cet article ?
On a vu en S2 que BPscript a trois mots-types : gate, trigger, cv. Ici on comprend pourquoi ces trois-là, et comment l’actor lie chaque symbole à son contexte complet : quel alphabet, quelle gamme, quelle sortie.
Trois types temporels
L’inspiration vient du monde modulaire (eurorack, synthèse analogique). Dans un synthétiseur modulaire, trois types de signaux circulent :
| Signal | Comportement | Analogie musicale |
|---|---|---|
| Gate | Tension haute tant que la touche est enfoncée | Une note qui dure — début et fin |
| Trigger | Impulsion brève, quasi-instantanée | Un coup de caisse claire — on déclenche, ça ne « dure » pas |
| CV | Tension qui varie continûment | Un pitch bend, un crescendo — la valeur change |
BPscript emprunte cette trichotomie parce qu’elle répond exactement à la question que l’ordonnanceur doit se poser : est-ce que ça occupe du temps ?
- Un
gateoccupe du temps. Sa durée est calculée par BP3. - Un
triggern’occupe pas de temps. C’est un événement ponctuel — zéro durée. - Un
cvoccupe du temps ET sa valeur change pendant la durée.
Le problème : un alphabet ne suffit pas
Imaginons une scène avec deux musiciens. Ils jouent les mêmes notes (Sa, Re, Ga…) mais :
- vont vers des sorties différentes (Web Audio vs MIDI canal 3)
- utilisent des accordages différents (22 shruti vs 12-TET)
- peuvent avoir des conventions d’octave différentes
Un simple binding alphabet → runtime (@alphabet.raga:sc) ne suffit pas pour distinguer deux instruments partageant le même vocabulaire. Il faut une unité qui regroupe toutes les couches de résolution d’un symbole. C’est l’actor.
L’actor : tout lier ensemble
L’actor est l’unité qui lie toutes les couches de résolution d’un symbole :
@actor sitar1 alphabet:sargam scale:sargam_22shruti transport:webaudio
@actor sitar2 alphabet:sargam scale:sargam_12TET transport:midi(ch:3)
@actor tabla alphabet:tabla sounds:tabla_perc transport:midi(ch:10)
@actor lights alphabet:dmx_cues transport:dmx
Un actor = alphabet + scale + sounds + transport (+ optionnellement eval pour les backticks).
| Clé | Obligatoire | Rôle | Exemple |
|---|---|---|---|
alphabet |
oui | Les noms de symboles disponibles | alphabet:sargam |
scale |
non | Comment les degrés deviennent des fréquences (via le tempérament) | scale:sargam_22shruti |
sounds |
non | Définitions par terminal : timbre, percussions, samples | sounds:tabla_perc |
transport |
oui | Où envoyer les événements | transport:webaudio |
eval |
non | Quel REPL évalue les backticks | eval:sclang |
Si scale est omis → pas de résolution de fréquence (percussions, DMX, lumières).
Si sounds est omis → le transport applique son rendu par défaut.
Si eval est omis → aucun REPL n’est associé : les backticks de cet acteur restent alors sans évaluateur (il faut déclarer eval pour les utiliser).
Résolution : implicite ou explicite
Résolution explicite — la notation pointée actor.terminal
Dans les règles, un terminal est qualifié par son actor via la notation pointée (dot notation) :
sitar1.Sa // Sa résolu via sitar1 (sargam + 22 shruti + webaudio)
sitar2.Sa // même note, autre actor (sargam + 12-TET + midi ch3)
tabla.tin // tin résolu via tabla (tabla + midi ch10)
lights.spot // spot résolu via lights (dmx)
Résolution implicite — quand c’est évident
Si un symbole n’apparaît que dans un seul actor, BPscript le résout automatiquement :
@actor sitar1 alphabet:sargam scale:sargam_22shruti transport:webaudio
@actor tabla alphabet:tabla sounds:tabla_perc transport:midi(ch:10)
Sa Re Ga Pa // → sitar1 (seul actor avec sa, re, ga, pa)
tin ta ke dha // → tabla (seul actor avec tin, ta, ke, dha)
Pas besoin d’écrire sitar1. partout — le compilateur déduit.
Si un symbole apparaît dans plusieurs actors sans notation pointée explicite → erreur :
@actor sitar1 alphabet:sargam ...
@actor sitar2 alphabet:sargam ...
Sa Re Ga Pa // Erreur : 'Sa' est dans sitar1 ET sitar2 — préciser l'actor
Import en bloc
Un @actor avec un alphabet importe tous les symboles de cet alphabet, liés à cet actor :
@actor sitar1 alphabet:sargam scale:sargam_22shruti transport:webaudio
// Sa, Re, Ga, Ma, Pa, Dha, Ni → automatiquement déclarés comme gate, liés à sitar1
// Pas besoin de "gate sitar1.Sa" pour chaque note
Surcharge individuelle possible via une déclaration explicite :
trigger dha:sitar1 // surcharge : dha est un trigger dans cet actor, pas un gate
Paramètres : () runtime vs [] moteur
Deux syntaxes pour deux destinations (S5) :
| Syntaxe | Destination | Exemples |
|---|---|---|
[] |
Moteur BP3 | [speed:2], [weight:50], [tempo:2] |
() |
Runtime (couche aval) | (vel:80), (wave:sawtooth), (filter:300) |
Les parenthèses () transportent les paramètres vers le runtime — BPscript ne les interprète pas, il les compile en contrôles opaques (_script(CT n)) que le runtime aval consomme :
Sa(vel:120) // symbole : vel=120 pour cette note
{A B C}(vel:80) // groupe : vel=80 pour tout le groupe
S -> A B (vel:120) // règle : vel=120 en fin de RHS
Les crochets [] sont des instructions pour le moteur :
{A B C}[speed:2] // vitesse doublée (moteur)
S -> A B C [weight:3] // poids de la règle (moteur)
La distinction est fondamentale : [] change le comportement du moteur (vitesse, poids, mode), () change le rendu par le runtime (vélocité, pan, instrument).
CV : valeurs continues
Certains gestes ne sont ni une note tenue ni une impulsion : un filtre qui s’ouvre, un crescendo, un vibrato. C’est ce que porte le cv — une valeur qui évolue pendant sa durée. Les objets CV sont déclarés en tête de scène et utilisés dans la grammaire comme des symboles ordinaires. La signature est nom(cible, transport) = lib.type(args) :
// Déclaration d'un CV — enveloppe ADSR ciblant le filtre, évaluée par SC
env1(filter, sc) = filter.adsr(10, 100, 0.7, 200)
// Utilisation dans une règle
S -> env1 Sa Re Ga Pa
Les CV couvrent plusieurs types : enveloppes ADSR, LFO (oscillateur basse fréquence), ramps (interpolation linéaire). Ils reçoivent leur durée de la grammaire et sont résolus dans la couche aval (S10).
La résolution de fréquence est en aval
Point important : Contrairement à l’écriture de grammaires BP3, ici on l’utilise uniquement en tant qu’ordonnanceur, donc BP3 ne traite pas les objets ordonnancés, les sons. Le compilateur lui transmet uniquement des noms opaques préfixés bol (bolSa, bolRe…). BP3 dérive la structure temporelle ; il ne sait ni quel actor, ni quel accordage, ni quelle sortie.
La traduction d’un symbole en hertz se fait dans la couche aval (le runtime), via un système de hauteurs en 6 couches :
Layer 0 — Acteur : quel contexte de résolution (sitar1 vs sitar2) ?
1 — Alphabet : quel degré (Sa = degré 1, Ga = degré 3) ?
2 — Octaves : quel registre (saptak) ?
3 — Tempérament : quel rapport pour ce degré (5/4, 2^(4/12)) ?
4 — Tuning : quelle fondamentale, quel diapason ?
5 — Resolver : le calcul final en Hz
C’est pourquoi le même Ga peut produire deux fréquences différentes selon l’actor :
sitar1 : Ga (registre médian) → 327.03 Hz (22 shruti, ratio 5/4)
sitar2 : Ga (registre médian) → 329.63 Hz (12-TET, ratio 2^(4/12))
Même symbole, même registre, fréquences différentes — parce que l’accordage (couches 3-4) diffère. Les détails du système de hauteurs en 6 couches sont dans S9.
Un exemple complet
// Actors
@actor sitar alphabet:sargam scale:sargam_22shruti transport:webaudio
@actor tabla alphabet:tabla sounds:tabla_perc transport:midi(ch:10)
@actor lights alphabet:dmx_cues transport:dmx
// Inits
`js: // Web Audio setup`
// Composition — les actors résolvent automatiquement
S -> { melodie, rythme, eclairage }
melodie -> Sa Re Ga(vel:120) Pa // → sitar (seul à avoir ces notes)
rythme -> tin ta ke dha // → tabla (seul à avoir ces bols)
eclairage -> -!spot _ _ -!fade // → lights (seul à avoir spot, fade)
Trois instruments, trois actors, trois destinations — une seule grammaire. La résolution est implicite parce que chaque symbole n’appartient qu’à un seul actor.
Ce qu’il faut retenir
- Trois types temporels :
gate(durée),trigger(instant),cv(continu) - L’actor lie tout : alphabet + scale + sounds + transport + eval
@actordéclare un actor avec ses propriétés- Résolution implicite : si un symbole n’est que dans un actor, pas besoin de le qualifier
- Résolution explicite : notation pointée
sitar1.Saquand plusieurs actors partagent le même alphabet ()= runtime,[]= moteur — deux syntaxes, deux destinations- La fréquence se résout en aval — BP3 ne voit que des noms opaques
bol…; les 6 couches pitch tournent dans le runtime
Glossaire
- Actor : Unité de binding qui lie un alphabet, une gamme (
scale), des sons (sounds), un transport et un évaluateur — le contexte complet de résolution d’un symbole - Gate : Signal qui reste actif pendant une durée — en BPscript, un symbole qui occupe du temps
- Trigger : Impulsion instantanée de durée zéro
- CV (Control Voltage) : Valeur qui varie continûment sur une durée (ADSR, LFO, ramp)
- Resolver : Dernière des 6 couches pitch (en aval) qui traduit un symbole en fréquence
- Binding : Liaison entre un symbole et son actor — implicite (déduite) ou explicite (notation pointée)
- Transport : Protocole de sortie (Web Audio, MIDI, OSC, DMX) déclaré dans l’actor
- Résolution implicite : Le compilateur déduit l’actor d’un symbole quand il n’y a pas d’ambiguïté
Liens dans la série
- S2 — Les 3 mots et 24 symboles
- S4 (à venir) — Les backticks — le champ
evalde l’actor détermine quel REPL évalue - S5 —
[]moteur vs()runtime en détail - S9 — Le système de hauteurs en 6 couches — ce que le resolver fait
- S10 — Le pipeline — comment les actors traversent la compilation
Prérequis : S2
Temps de lecture : 14 min
Tags : #BPscript #actors #gate #trigger #cv #tuning
Prochain article : S4 — Les backticks : JS inline et REPLs