S6) Composer avec des conditions

Flags et parcours

Gardes, mutations et gestion d’échec

Une grammaire qui produit toujours la même chose offre peu de surprises. Les flags transforment une grammaire statique en parcours compositionnel — la structure évolue pendant qu’elle se dérive.

Où se situe cet article ?

On a vu en S2 les gardes [] et les opérateurs de flags (six de comparaison, trois de calcul). Ici on les assemble : comment écrire des compositions qui changent de direction pendant la dérivation, qui comptent, qui réagissent. Pour les fondements de ce mécanisme dans BP3, voir B4.


[guard] — la garde déclarative

Une garde entre crochets [] avant le LHS (côté gauche) teste un flag (variable entière globale) et détermine si une règle existe :

 

[phase==1] S -> Sa Re Ga Pa          // active seulement si phase vaut 1
[phase==2] S -> Ga Pa Dha Ni         // active seulement si phase vaut 2
[count>3]  A -> B C                  // active si count dépasse 3
[tension!=0] A -> B C                // active si tension est non nulle

 

Opérateurs de test disponibles : ==, !=, >, <, >=, <=.

Flag nu et garde multiple

Deux raccourcis simplifient les gardes courantes :

 

[Ideas] S -> A B C                   // flag nu : actif si Ideas != 0 (non nul)
[phase==1] [count<3] S -> A B        // garde multiple : les deux conditions doivent être vraies

 

Le flag nu ([Ideas]) est un raccourci pour [Ideas!=0] — le flag est traité comme un booléen (vrai si non nul). La garde multiple (plusieurs [] consécutifs) combine les conditions par un et logique — la règle n’existe que si toutes les conditions sont satisfaites en même temps.

 

Le point crucial : la garde est déclarative, pas impérative. Ce n’est pas un if/else exécuté pas à pas : c’est une condition d’existence de la règle. La règle existe quand la condition est vraie ; elle n’existe pas quand la condition est fausse. Il n’y a pas de « sinon ».

Ça change la façon de penser : on ne programme pas des branchements, on déclare des mondes possibles. Quand phase vaut 1, le monde est celui de l’ālāp. Quand phase vaut 2, le monde change — et avec lui, toutes les règles disponibles.

Évolution envisagée : combinaisons booléennes explicites

Aujourd’hui, plusieurs gardes consécutives se combinent par un et implicite (la garde multiple ci-dessus), et le ou se contourne en écrivant plusieurs règles. Une évolution du langage est à l’étude pour rendre ces combinaisons explicites : des opérateurs booléens entre gardes — && (et), || (ou) — et des parenthèses () pour grouper et fixer la priorité.

 

[a] && [b]                      // et explicite (équivalent à la garde multiple actuelle)
[a] || [b]                      // ou : actif si l'une au moins des conditions est vraie
[a] && ([b] || [c])             // groupement par parenthèses

 

À l’étude : cette syntaxe n’est pas encore implémentée. Pour l’instant, seul le et par juxtaposition ([a] [b]) est disponible en BP3 ; le ou passe par plusieurs règles distinctes.


Mutations — changer l’état pendant la dérivation

Les flags ne servent à rien s’ils ne changent jamais. Une mutation s’écrit avec [] en fin de règle, après le RHS. Elle est hors-temps : elle s’applique au déclenchement de la règle, pendant la dérivation — pas à un point de la séquence jouée. Sa position ne porte donc aucun sens temporel.

 

S -> A B [count-1]                  // applique count-1 quand cette règle est dérivée
S -> A B [phase=1] [count=2]        // plusieurs mutations, en fin de règle

 

Côté BP3, ces mutations deviennent des marqueurs /…/ placés en fin de règle :

BPscript BP3
S -> A B [count-1] S --> A B /count-1/
S -> A B [phase=1] [count=2] S --> A B /phase=1/ /count=2/

Pas de mutation « au milieu » : on ne peut pas insérer une mutation entre deux éléments pour qu’elle prenne effet « après le 3ᵉ son, avant le 4ᵉ ». Elle agit au niveau de la règle, au moment de sa dérivation — jamais à un instant précis du flux sonore.

Trois opérateurs de mutation :

  • = — assigner : [phase=2]
  • + — incrémenter : [count+1]
  • - — décrémenter : [tension-1]

Attention à ne pas confondre avec l’opérateur ! : ! est exclusivement temporel (triggers et simultanéité, voir S7 (à venir)). Une mutation de flag ne s’écrit jamais !phase=2 — toujours [phase=2] en fin de règle. La distinction est syntaxique et nette :

  • Sa!dha! suivi d’un symbole → trigger temporel
  • [phase=2] → crochets avec nom=valeur → mutation de flag (état moteur)

Les flags peuvent aussi se comparer entre eux :

  • [flag1==flag2] — tester l’égalité de deux flags
  • [flag1=flag2] — copier la valeur d’un flag dans un autre

Exemple 1 : un raga en trois phases

Une illustration : un raga indien structuré en trois phases (ālāp → jor → jhālā), avec transition automatique :

 

@alphabet.raga:supercollider
@tempo:60

// Gardes : chaque phase a ses propres règles
[phase==1] S -> alap S
[phase==2] S -> jor S
[phase==3] S -> jhala

// L'ālāp explore lentement ; dériver cette règle passe phase à 2
alap -> Sa _ Re _ Ga _ [phase=2]

// Le jor accélère ; dériver cette règle passe phase à 3
jor -> {Sa Re Ga Pa}[speed:2] [phase=3]

// Le jhālā conclut
jhala -> {Sa Re Ga Pa Dha Ni Sa}[speed:4]

 

Ce qui se passe :

  1. Au départ, phase vaut 1 (par défaut, les flags valent 0 — ici il faut initialiser à 1)
  2. La garde [phase==1] est active → S se dérive en alap S
  3. Dériver la règle alap produit Sa _ Re _ Ga _ et passe phase à 2 — la mutation agit au niveau de la règle, pas après les sons
  4. Au prochain cycle, [phase==2] est active → S se dérive en jor S
  5. Dériver jor produit la séquence plus rapide ([speed:2]) et passe phase à 3
  6. [phase==3]jhala conclut (pas de S récursif → la dérivation s’arrête)

Trois phases, trois ambiances, zéro logique impérative. La structure se parcourt.


Exemple 2 : un compteur cyclique

Les flags permettent de construire des séquences cycliques — chaque dérivation du même non-terminal produit un résultat différent :

 

[count==0] A -> Sa Re [count+1]
[count==1] A -> Ga Pa [count+1]
[count==2] A -> Dha Ni [count=0]       // reset → le cycle recommence

 

Trois dérivations successives de A donnent :

  1. Sa Re (count passe à 1)
  2. Ga Pa (count passe à 2)
  3. Dha Ni (count revient à 0)
  4. Sa Re (le cycle repart)

C’est plus puissant que le mode lin (dérivation leftmost aléatoire — voir S5) : avec les flags, on peut avoir des conditions complexes, des sauts, des réinitialisations conditionnelles. On construit de vrais parcours compositionnels, pas juste des variations positionnelles.


Gestion d’échec : on_fail

Quand une dérivation échoue (aucune règle ne s’applique pour un non-terminal donné), BP3 dispose de mécanismes de contrôle de flux bas niveau (_goto, _failed). BPscript prévoit de les exposer sous une forme plus lisible, on_fail :

 

// Directive globale — s'applique à toute la grammaire
@on_fail:skip                              // ignorer la règle qui échoue

// Surcharge locale sur une règle
[on_fail:retry(3)] S -> A B C              // réessayer 3 fois
[on_fail:fallback(B)] S -> A B C           // basculer vers la sous-grammaire B

 

Trois stratégies disponibles :

Stratégie Syntaxe Effet
skip @on_fail:skip Sauter la règle qui échoue, continuer
retry [on_fail:retry(3)] Réessayer N fois (utile avec @mode:random)
fallback [on_fail:fallback(X)] Basculer vers la sous-grammaire X

on_fail fait partie de la librairie @core et on_fail est une clé réservée du langage. L’objectif : un filet de sécurité, utile en live coding où les grammaires sont modifiées en temps réel et peuvent être temporairement incohérentes — la cible de compilation étant _goto/_failed.

État actuel : la syntaxe on_fail (skip / retry / fallback) est définie et reconnue par le parseur, mais son comportement n’est pas encore opérationnel (voir S12). Les contrôles BP3 sous-jacents _goto et _failed, eux, existent.


Flags vs backticks : qui gère la logique ?

Une question légitime : pourquoi utiliser des flags (limités à des entiers et des comparaisons simples) plutôt que des backticks Python avec de la vraie logique ?

La réponse est dans qui tient l’horloge :

Mécanisme Géré par Visible pour BP3 Temps
Flags BP3 (le moteur) Oui — BP3 sait quelles règles sont actives Synchrone avec la dérivation
Backticks Le runtime (SC, Python…) Non — BP3 ne voit pas l’état du runtime Asynchrone, évalué à l’exécution par le runtime

Les flags sont dans le moteur de dérivation. Quand [phase=2] s’exécute, BP3 le sait immédiatement et active les nouvelles règles au prochain pas de dérivation. Un backtick Python qui modifierait une variable Python ne changerait rien à la dérivation — BP3 ne voit pas les variables Python.

Règle simple : si la condition affecte la structure (quelles règles s’appliquent), c’est un flag. Si la condition affecte le son (quel synthétiseur jouer, à quel volume), c’est un backtick.


Ce qu’il faut retenir

  1. Les gardes [] sont déclaratives : la règle existe ou non — pas de if/else
  2. Les mutations s’écrivent en fin de règle avec [] (hors-temps, au niveau de la règle) : assignation ([flag=N]), incrément ([flag+1]), décrément ([flag-1]) — jamais avec !
  3. Les flags sont des entiers globaux gérés par BP3, pas par les runtimes
  4. On déclare des mondes possibles, pas des branchements — chaque valeur de flag définit un ensemble de règles actives
  5. on_fail gère les échecs de dérivation : skip, retry(N), fallback(X)
  6. Flags = structure (synchrone avec la dérivation), backticks = comportement (asynchrone, évalué par le runtime)

Glossaire

  • Flag : Variable entière globale dans BP3, testable par [flag_expr] et modifiable par [flag=valeur] — contrôle quelles règles de grammaire sont actives
  • Garde : Condition [flag_expr] avant le LHS qui détermine l’existence d’une règle — la règle est active si la garde est vraie
  • Mutation : modification d’un flag via [flag=valeur], [flag+1] ou [flag-1] en fin de règle — appliquée au déclenchement de la règle (hors-temps), pas à un point du flux joué
  • on_fail : Directive de gestion d’échec — que faire quand aucune règle ne s’applique (skip, retry, fallback)
  • Parcours compositionnel : Séquence d’états de flags qui définit une trajectoire à travers la grammaire — chaque état active un ensemble de règles différent
  • Ālāp : Phase d’ouverture d’un raga indien, non mesurée, exploratoire — paradigme du temps lisse
  • Jor : Phase intermédiaire d’un raga, progressivement rythmée
  • Jhālā : Phase conclusive d’un raga, rapide et virtuose

Liens dans la série

  • S2 — Les gardes [] et les opérateurs de flags
  • S5 — Modes de dérivation — l’interaction entre mode et flags
  • S7 — L’opérateur ! — simultanéité temporelle (distinct des mutations de flag)
  • B4 — Flags et poids dans BP3 — les fondements

Prérequis : S2, B4
Temps de lecture : 12 min
Tags : #BPscript #flags #gardes #composition-conditionnelle #BP3


Prochain article : S7 — L’instant partagé : ! et <!


Retour à l’index