Strudel : programmer la musique en direct sur le web

Il n'y a pas que le vibe coding dans la vie, il y a aussi le live coding. Et même en musique !

Strudel : programmer la musique en direct sur le web

Le live coding, ou programmation en direct, a gagné ses lettres de noblesse dans le domaine de la création musicale, notamment grâce à des systèmes tels que TidalCycles (ou 'Tidal'). Tidal, ancré dans le langage de programmation fonctionnel pur Haskell, a défini une approche puissante pour la création de motifs algorithmiques. Strudel se présente comme une réinterprétation fidèle de cette approche, mais portée au langage JavaScript natif et, par extension, au web.

Et ça ressemble à quoi ce machin ?!

L'objectif de Strudel est de démocratiser le live coding, en offrant un environnement qui s'exécute entièrement dans un navigateur. Il ouvre ainsi la porte des motifs algorithmiques à l'écosystème riche et moderne des technologies web audio et visuelles.

TL;DR:

J'ai écris un mini-guide pour que vous puissiez vous lancer sans stress dans la création musicale rapidement. C'est gratuit 😄


Comprendre le cœur : le motif abstrait

L'essence de Tidal, et donc de Strudel, réside dans la notion de motif (Pattern). Un motif n'est pas une simple structure de données stockant une séquence figée, mais une entité abstraite représentant un flux temporel. Ce concept est hérité d'une technique nommée programmation réactive fonctionnelle pure.

En pratique, un motif est une fonction pure qui prend un intervalle de temps en entrée et émet en sortie un ensemble d'événements qui se produisent dans cette période. Le positionnement temporel précis de ces événements dépend de la structure interne du motif. L'unité de temps est appelée le cycle.

Un utilisateur de l'interface de programmation en direct (REPL) n'a pas à gérer l'interrogation de ce motif ; elle est prise en charge par l'ordonnanceur (scheduler). Le rôle de l'ordonnanceur est d'interroger le motif de manière répétée pour générer des événements qui seront ensuite programmés pour la synthèse sonore ou d'autres déclencheurs.


Un autre exemple ?


Le flux de travail : du code à l'événement

Strudel propose une interface utilisateur de référence, le REPL (Read, Evaluate, Print/Play, Loop). Cet éditeur de code est dédié à la manipulation en direct des motifs musicaux.

Le flux de contrôle du REPL est structuré en trois étapes fondamentales :

  1. Code utilisateur et motif (Pattern) : Vous écrivez et mettez à jour le code. Cette action est transformée (transpilée) puis évaluée pour créer une instance de Motif.
  2. Ordonnancement et événements (Events) : L'ordonnanceur exécute une boucle à intervalle régulier, interrogeant le Motif actif pour générer les Événements (Haps) pour l'intervalle de temps suivant.
  3. Audio et déclenchement (Trigger) : Les Événements générés sont déclenchés en appelant leur méthode onTrigger, définie par le système de sortie audio choisi.

L'un des avantages fondamentaux est que le changement de motif est immédiatement pris en compte lors du prochain appel de l'ordonnanceur, sans interrompre l'horloge interne, permettant une véritable performance en direct.


La magie de la mini-notation et de la syntaxe ✨

Pour simplifier l'écriture, Strudel supporte une mini-notation compacte pour exprimer le rythme.

Le code JavaScript est rendu plus idiomatique pour le live coding via la transpilation. Cela permet de traduire des notations courtes en appels de fonctions complets. Par exemple, une chaîne de caractères entre guillemets doubles comme "c3 [e3 g3]*2" est automatiquement transformée en une fonction qui crée un motif.

Exemple d'application direct : rythme et mélodie

Utilisez la mini-notation pour créer un motif rythmique et mélodique.

// Crée un motif de notes séquencées
// "c3" dure la première moitié du cycle, "[e3 g3]" la seconde.
// Les crochets [] divisent le temps de leur parent (la seconde moitié) également.
note("c3 [e3 g3]")

Transformer le motif : Appliquez des fonctions pour manipuler la séquence.

// La séquence est jouée deux fois plus rapidement (*2) et inversée à l'intérieur de chaque groupe.
// .s("sawtooth") définit un son 'sawtooth' pour la lecture.
note("c3 [e3 g3]*2").rev().s("sawtooth")


Les sorties : événements sonores et au-delà 

Strudel s'intègre à un large éventail de systèmes pour générer des événements :

  • Web Audio API : La sortie par défaut, créant un nouveau graphe audio pour chaque événement (oscillateurs, samples, effets).
  • WebDirt : Une réimplémentation en JavaScript du moteur de samples de Tidal.
  • OSC (Open Sound Control) : Pour communiquer avec des systèmes externes comme SuperDirt.
  • Csound : Grâce à sa distribution WebAssembly, permettant d'intégrer des synthétiseurs orchestra complexes.
  • WebMIDI : Pour envoyer des messages MIDI à des instruments ou d'autres programmes.
  • WebSerial : Pour déclencher des événements via des microcontrôleurs (robotique, lumière).

Pour contrôler ces systèmes, Strudel utilise des paramètres de contrôle (control parameters) qui façonnent la valeur de chaque événement. Ces fonctions (comme note, cutoff, s) acceptent une valeur simple, une liste de valeurs, ou même un autre Motif.

Exemple d'application direct : paramètres de contrôle

Ici, trois paramètres de contrôle sont appliqués en parallèle sur le même motif rythmique : note, cutoff et s.

// Le pattern de notes est "c3" puis "e3"
// Le paramètre cutoff (fréquence de coupure du filtre) est fixé à 1000
// Le paramètre s (synthétiseur) est fixé à 'sawtooth'
note("c3 e3").cutoff(1000).s('sawtooth')


Alignement des motifs : la composition polyrythmique

L'une des caractéristiques les plus puissantes est la manière flexible dont les motifs peuvent être combinés. L'approche est déclarative : vous spécifiez ce qui doit être fait, et Strudel gère les détails de l'alignement temporel.

Le mécanisme par défaut pour la combinaison de deux motifs aligne les cycles et fait correspondre les événements.

Strudel offre des méthodes explicites pour varier cette combinaison :

  • in : Applique les valeurs du motif de droite dans celui de gauche.
  • out : Applique les valeurs du motif de gauche dans celui de droite.
  • squeeze : Les cycles du motif de droite sont compressés dans les événements du motif de gauche.
  • trig : Les cycles du motif de droite sont alignés avec les événements du motif de gauche et tronqués à leur durée.

Exemple d'application direct : combinaison de motifs

Combinez deux motifs en utilisant la méthode add avec un alignement spécifique.

Motif 1 (rythme lent) : n("0 1 2") -> trois événements de même durée.

Motif 2 (valeurs rapides) : n("10 20") -> deux événements, chacun durant la moitié du cycle.

Combinaison par défaut (in) :

// Le pattern de droite est ajouté au pattern de gauche,
// La structure rythmique est définie par "0 1 2"
n("0 1 2").add(n("10 20"))
// Résultat : les valeurs de "10 20" sont alignées sur "0 1 2"

Combinaison squeeze :

// Les deux événements "10 20" (cycle complet) sont "compressés" dans le premier événement "0".
// Le même processus se répète pour "1" et "2".
n("0 1 2").add.squeeze(n("10 20"))
// Le résultat est un pattern complexe qui alterne entre les valeurs des deux patterns
// Équivalent à : [10 20] [11 21] [12 22] (rythmiquement parlant)


La perspective du codeur : JavaScript contre Haskell

Le portage de Tidal de Haskell à JavaScript a été un exercice d'équilibrage. Haskell offre une syntaxe plus terse. Strudel, contraint par JavaScript, doit privilégier une approche plus explicite basée sur l'enchaînement de méthodes (.add.squeeze()), là où Tidal utiliserait des opérateurs infixes.

Le code Haskell peut être perçu comme plus « propre » en raison de sa concision, mais la syntaxe de Strudel, bien que plus lourde en parenthèses, est jugée plus facile à apprendre pour les débutants non familiers avec la programmation fonctionnelle pure. L'approche de Strudel, tout en étant contrainte par le langage, a réussi à créer un environnement de live coding très abordable et utilisable.