Belly

Je travaille en parallèle de ma thèse sur un environnement de live-coding que j’ai choisi de nommer Belly. Il s’agit d’un client Python pour SuperDirt. Il s’agit pour moi de mettre la main à la pâte et d’essayer de comprendre, par la programmation, les enjeux de la conception d’un tel système (gestion du temps, manipulation de la syntaxe, intégration avec SuperDirt, etc…). Le logiciel sera posté sur GitHub lorsque celui-ci atteindra la stabilité et la maturité suffisante pour être utilisé lors d’une performance en temps réel.

  • Belly est couplé au moteur SuperDirt : intégration avec SuperCollider, communication par MIDI ou OSC.
  • Belly emprunte et s’inspire beaucoup de langages tels que Tidal Cycles, ChucK ou ExTempore.
  • Belly ne possède pas d’interface. Il s’utilise principalement comme mode particulier de Doom Emacs.
  • Belly contourne / détourne nombre de “bonnes pratiques” habituelles en Python, afin de s’adapter au contexte très particulier du temps réel.
Python s’avère relativement limité en matière de méta-programmation. La syntaxe reste donc souvent plus lourde que d’autres environnements de live-coding basés sur des langages de programmation fonctionnels ou supportant les macros, etc…

Syntaxe basique

Le langage repose sur un système de boucles musicales récursives. Chaque boucle déclare un certain nombre d’évènements musicaux ou extra-musicaux (notes, signaux, etc..) d’une durée non-définie.

Un appel à Track.loop() remplace le retour habituel de la fonction, et permet de relancer la fonction dans un futur proche (entre 0 et … pulsations ou secondes). Une valeur nulle entraîne la répétition immédiate de la boucle. Si rien n’est fait pour stopper une récursion temporelle immédiate infinie, un mécanisme est prévu pour prévenir du désastre avant le crash.

Il est possible de lancer une fonction musicale sans grille métrique. Toutefois, un appel à Track.start() permet de programmer précisément l’exécution d’une fonction sur une grille rythmique. Track.start(piano, [1,4], ()) aura pour effet de :

  • Lancer la fonction piano sur le premier temps de la prochaine mesure à quatre temps (calculée depuis l’origine du temps, c’est-à-dire, le lancement du programme).
  • () indique l’absence d’arguments. Il est possible de traiter les arguments de manière récursive une fois la boucle lancée dans son cycle.

Émettre un son

L’objet Sound() permet de lancer un échantillon, d’émettre une note de synthétiseur, un signal MIDI ou OSC. L’interface est calquée sur celle du framework SuperDirt, et n’offrira aucune surprise pour les musiciens habitués à Tidal Cycles. Cet objet est d’une très grande simplicité.

Sound(0, “clap”, reverb=[0.8]*3, squiz=choice(2,4,8)).out() : un échantillon joué sur le premier canal avec deux effets audio : une réverbération et un effet plus exotique.

Sound(0, “midi”, midinote=60, midichan=1).out() : une note MIDI émise sur le second canal.

Chaque son doit obligatoirement passer par .out() pour être joué. Cette lourdeur est nécessaire afin de permettre la manipulation algorithmique d’un son :

 

bam = Sound(0, "bd")
if dice(4): 
    bam.delay(0.8,0.7,0.6)
else: 
    bam.speed(2)
bam.out()

Environnement

Je développe et j’utilise Belly à l’aide de Doom Emacs. Cela me permet de manipuler une fenêtre de texte et de n’envoyer que des extraits choisis à l’interpréteur Python. Les commandes et raccourcis claviers suivants sont disponibles :

  • (belly-start-belly) : prépare un éditeur de texte et un interpréteur pour jouer.
  • (belly-eval-region) : permet d’envoyer une région textuelle à l’interpréteur.
  • (belly-eval-buffer) : permet d’envoyer l’intégralité de la fenêtre à l’interpréteur.
  • (belly-global-stop) : permet de stopper immédiatement l’exécution du code musical.

Des fonctions sont prévues pour permettre de suire le curseur (effet de traînée lors de l’édition) ainsi que l’évaluation des différentes parties du code (flash).