Anneaux / Listes / Fun

Anneaux

Il est temps pour nous d’en terminer avec le premier exemple que nous examinons depuis le début de ce guide. Observons l’élément de langage qui nous résiste encore :

play (ring :e2, :e2, :f2, :g2, :g2).look sleep (knit 0.5, 12, 0.75, 1, 0.25, 2, 1, 1).look

Bien qu’il soit intriguant et qu’il ait l’air complexe à première vue, sa fonction est rudimentaire. Il s’agit d’une liste indexée de données. En tant que tel, vous ne pourriez rien tirer de l’exemple ci-dessus. Pour cette raison, je n’ai pas inclus d’exemple audio. Vous auriez entendu l’équivalent sonore du code suivant. Imaginez-le. Lire du code et en imaginer le résultat est un des talents étonnants que vous développerez sans vous en rendre compte au cours du temps :


live_loop :nul do play :e3 ; sleep 0.5 end

Un anneau est un type particulier de liste. Il s’agit avant tout de bien faire la distinction entre ces deux types de données qui n’ont pas le même comportement. Un anneau est toujours déclaré entre parenthèses et précédé d’un mot-clef qui définit son rôle dans la famille des anneaux : (ring, x, x, x), (line x, x, x), (knit x, x, x) [0]. Les valeurs que nous plaçons dans l’anneau sont des données que nous pouvons convoquer à l’aide de leur numéro d’index, leur numérotation commençant à 0.

Figurez-vous un cercle ou un anneau. Lorsque vous en avez fait le tour, vous effectuez une boucle et vous revenez au début. Un anneau est un type de donnée qui fait exactement cela : lorsque vous atteignez la limite des index disponibles, celui-ci revient à 0. Aucun risque de dépassement !

Une liste basique en Ruby est très similaire. Elle se déclare à l’aides des crochets [] et permet également de stocker une donnée ordonnée par un index. Dans la liste [“raphael”, “maurice”, “forment”], “maurice” serait l’élément 1. Si vous indiquez à Sonic-Pi que vous souhaitez obtenir l’index 76, celui-ci vous renverra une erreur car celui-ci n’existe pas. Dans le feu de l’improvisation, je préfère personnellement utiliser des listes. Lorsque je suis certain de mes données, ou que celles-ci me servent uniquement à stocker une information précise, je préfère les listes.

Les anneaux sont un idiosyncratisme propre à Sonic-Pi : un type de donnée qui lui est propre. Vous remarquerez que souvent, les langages de live-coding sont fascinés par les boucles et la conception cyclique du temps [1]. Sonic-Pi n’y fait pas exception, et les anneaux appartiennent à ce paradigme qui conçoit une mélodie / une série d’accords / de rythmes comme une boucle.

Plutôt qu’une longue explication technique ou qu’une digression, écoutez l’exemple suivant. Il risque de vous donner quelques cauchemars. Personne ne devrait être autorisé à produire ce type de musique. Il s’agit d’un rockabillly synthétique faisant usage des anneaux. Pardonnez-moi, mais apprenez :


live_loop :metro do sleep 1 end live_loop :anneau, sync: :metro do use_synth :piano tick play (ring :c2, :c2, :f2, :f2, :g2, :g2).look sleep (ring 0.75, 0.25).look end live_loop :anneau2, sync: :metro do use_synth :dsaw tick a = (ring :c4, :c4, :f4, :f4, :g4, :g4).look b = (ring 1, 1, 2, 2, 2, 2).look play_chord (chord a, :major, invert: b) sleep (ring 0.75, 0.25).look end

Quelques explications concernant cet extrait s’imposent :

  • mon live_loop :metro agit ici comme un décompte. Une mesure d’une seconde passe avant que mes autres live_loop ne le rejoignent. Je fais usage de la commande sync que nous avons apprise dans l’article précédent. Cela me sert à définir un repère pour la synchronisation de mes autres boucles.
  • Deux anneaux sont stockés sous les noms a et b. Il est possible d’attribuer un nom à tout type de données. Consultez la partie consacrée à l’abstraction.

Simplifions une nouvelle fois ce code, et testons cet exemple :


live_loop :test do play (ring :c4, :e4) sleep 0.5 end

On entend une tierce majeure. Toutes les valeurs présentes dans l’anneau ont été jouées en même temps. Ce n’est pas nécessairement ce que vous espériez. En soit, écrire ceci à l’aide d’une liste aurait eu exactement le même résultat :


live_loop :test do play [:c4, :e4] sleep 0.5 end

Que nous faut-t-il changer si l’on souhaite entendre alternativement l’une puis l’autre de ces notes ? Il faut introduire deux nouveaux éléments de langage  : tick et .look.

Tool et Lick

Tick et Look sont deux méthodes/instructions extrêmement utiles. Tick est un compteur. En réalité, il ne compte pas comme vous et moi, de 1 à x ou de 0 à x. Imaginez que vous tenez dans votre main un collier de perles et que vous jouiez à faire défiler sous vos doigts chacune des perles, sans vous arrêter. Voici ce que fait tick et, tout comme vous, il n’a aucune raison de compter des perles. Il incrémente une valeur imaginaire à chaque itération de vos boucles et fait donc avancer un compteur imaginaire, sans début ni fin.

[Cette précision est nécessaire car j’ai souvent vu des gens être confus par le comportement de tick.]

 Tick existe sous deux formes :

  • tick (instruction)
  • .tick (méthode)

Tick peut recevoir un nom en attribut, et il est donc possible d’avoir de multiples instances de ce compteur dans votre code : un second tick(:deux) ou un troisième tick(:trois).

Une nouvelle fois, je pense qu’un exemple vous apporte plus que mes tentatives d’explication. Voici une utilisation simplissime de ce mécanisme :


live_loop :tick do play (ring :c3, :d3, :e3, :g3).tick play (ring :c2, :g2, :c2, :bb2).tick(:deux) sleep (ring 0.5, 0.25).tick(:trois) end

Look ou plutôt .look — c’est une méthode — l’observe. Il tient compte d’un changement de valeur dans votre tick. Je suis très friand de son utilisation, car il m’abstient de nommer plusieurs tick. Cette méthode, tout comme précédemment s’accole aux anneaux et observe le tick avec lequel elle correspond :

  • look observe tick.
  • look(:deux) observe tick(:deux).

Voici un nouvel exemple, dans lequel j’ai volontairement nommé mon tick et mon look, même si je n’avais pas à le faire !


live_loop :tick2 do tick(:deux) play (ring :d4, :f4).look(:deux) sleep 0.25 end

Ce mécanisme peut au départ sembler un peu confus. Une fois que vous l’aurez en tête, vous aurez fait de grand progrès dans votre capacité à utiliser Sonic-Pi pour produire de la musique véritablement intéressante. J’use et j’abuse de ce type de compteurs, car ce sont eux qui me permettent des variations rapides de mes notes, rythmes, paramètres, etc…

Voici encore un exemple rapide et explicite du mécanisme, retenez-le, et recopiez-le :


live_loop :test do tick play (ring :c4, :e4).look sleep 0.5 end

Pour imaginer le comportement conjoint de nos opérateurs/méthodes, nous pourrions représenter la chose ainsi :


live_loop :test do play (ring :c4, :e4)[0] sleep 0.5 play (ring :c4, :e4)[1] sleep 0.5 end

Ce code est valide et demande la valeur 0 puis 1, alternativement. Vous pouvez coller un (ring) sur à peu près toute valeur numérique ou même sur des noms précédés de : pour les contrôler et leur assigner des valeurs personnalisées à chaque itération.

Lord of the Rings

Un rapide survol de la syntaxe de Sonic-Pi permet de remarquer que la syntaxe propre aux anneaux est l’une de ses spécificités les plus marquantes. Si ring peut être vu comme un modèle pour étudier le fonctionnement de ce type de données, d’autres types d’anneaux plus spécifiques existent. Je vous encourage à les connaître et à les utiliser. Le même résultat peut être obtenu de plusieurs manières, mais certains auront pour avantage de rendre plus lisible ou plus élégant le code que vous écrirez. Voici une liste des types d’anneaux alternatifs, ainsi que des suggestions concernant leur utilisation :

Bools est un type d’anneau permettant de jouer avec un opérateur booléen. Les seules valeurs recevables sont 1 pour True, et 0 pour False. Il est utile dès lors qu’il se trouve couplé à une condition, permettant de créer une série de valeurs qui vont se vérifier ou non selon le déroulement de notre anneau. Dans l’eemple suivant, nous avons choisi de l’utiliser pour mettre en place une boîte à rythme simple. La série des 0 et des 1 reproduit ici un mécanisme de gate, classique des boîtes à rythme analogiques, et permet de visualiser instantanément le pattern assigné à un échantillon.


live_loop :perc do ; tick sample :bd_boom, amp: 1 if bools(1, 0, 0, 1, 0).look sample :perc_snap2, amp: 1 if bools(1, 0, 1, 0).look sample :drum_cowbell, amp: 1 if bools(0, 0, 0, 1).look sample :drum_snare_hard, amp: 0.7 if bools(0, 0, 1, 0).look sleep 0.25 end

Line sera souvent privilégié pour jouer avec des variations de paramètres et pour différents types d’automation. Trois arguments sont nécessaires : une valeur minimale, une valeur maximale et un nombre d’étapes intermédiaires à parcourir entre l’une et l’autre.  Attention, ce type d’anneau fait doublon avec un autre type d’anneau, range. L’anneau suivant (line 0, 10, steps: 10) retourna les valeurs 0, 1, 2, 3, 4, 5, 6, 7, 8 et 9 avant de se répéter. Le paramètre inclusive: true peut être utilisé pour inclure la valeur finale dans la ligne générée. Line vous permet un contrôle fin et graduel des paramètres, tel qu’illustré par l’exemple suivant :


live_loop :line do ; tick use_synth :noise play :c4, release: (line 0.001, 0.10, steps: 10).look sleep 0.125 end

Spread est un type d’anneau très original, puissant, mais quelque peu intimidant tant que l’on ne comprend pas le fonctionnement d’un séquenceur euclidien. Cet algorithme, l’un des favoris de la scène algorave, permet de répartir équitablement un certain nombre de battements sur un certain nombre de pulsations. Il permet — virtuellement — de reproduire toutes les combinaisons polyrythmiques que vous désirez, et fut initialement popularisé pour l’étude des musiques extra-européennes. Son fonctionnement est très simple à décrire dans le détail.

Spread reçoit trois arguments : deux sont nécessaires, le dernier est optionnel et aura pour valeur défaut 0 :

  • nombre de battements : un nombre de battements souhaités.
  • nombre de pulsations par mesure : divisez votre mesure en x pulsations égales.
  • rotation : à chaque itération, décalez les battements de x pulsations par rapport au nombre de pulsations par mesure.

Le voici utilisé au sein d’un live_loop. Ecoutez cette polyrythmie mélodique. Elle aurait été difficile à obtenir en utilisant un autre anneau :


live_loop :euclidien do ; tick play :c3 if spread(1, 4).look play :g3 if spread(4.5, 10).look play :g4 if spread(4.5, 11).look play :g5, amp: 0.1 if spread(15, 32).look sleep 0.25 end

Knit ressemble beaucoup à ring à ceci près qu’il permet de spécifier un certain nombre de répétitions pour chacun des index. Sa syntaxe est donc légèrement différente. Il y a peu à en dire. Il est toutefois très utile pour les progressions d’accord, pour générer un accompagnement et pour varier cycliquement un rythme :


live_loop :knit do ; tick play (knit :f4, 2, :g4, 2, :ab4, 2).look, release: 0.1 play (knit :c5, 2, :bb4, 2, :f4, 2).look, release: 0.1 sleep (knit 0.125, 16, 0.125/2, 2, 0.125, 16, 0.5, 1).look end

Enfin, d’autres anneaux existent bien qu’ils soient moins utiles. Ils sont en général trop spécifiques pour avoir une utilité immédiate, mais il est bon de connaître leur existence. Parcourez la documentation, vous en trouverez certainement quelques-uns que j’ai du ici ommettre de mentionner :

  • (octs, note, nombre d’octaves) génère une liste d’octaves.
  • (halves nombre, nombre de découpes) génère le nombre, sa moitié, la moitié de sa moitié, et caetera selon le nombre de découpes.
  • (doubles nombre, nombre de doubles) la même chose en doublant les nombres.

Toujours plus avec ring

Le plaisir du live-coding réside dans la manipulation virtuose des données. Tick et .look sont des fondamentaux mais il est possible d’altérer leur comportement pour obtenir plus de variété. En réalité, look et tick ne sont que deux méthodes parmi une liste beaucoup plus large. Il est possible de les combiner pour trier les données comme vous le souhaitez :

  • look : attend le tick, renvoie la prochaine valeur.
  • reverse : renverse l’anneau (de la fin de l’index vers le début).
  • shuffle : renvoie n’importe quelle valeur au hasard.
  • pick(x) : choisit x valeurs dans l’anneau (indexées).
  • take(x) : renvoie les x premiers éléments.
  • drop(x) : abandonne les x premières valeurs.
  • drop_last(x) : abandonne les x dernières valeurs.
  • take_last(x) : renvoie les x dernières valeurs.
  • stretch(x) : répète chaque valeur x fois.
  • repeat(x) : répète l’anneau entier x fois.
  • mirror(x) : ajoute à l’anneau sa version en miroir.
  • reflect : ajoute le reflet de l’anneau (il évite donc les valeurs en double à chaque extrémité).
  • scale(x) : renvoie le ring mais chaque valeur multiplié par x.

Bien que je n’illustre pas chacun de ces mécanismes par un exemple, ne négligez pas cette liste ! Certaines fonctions, comme mirror ou repeat sont cruciales pour gagner en précision dans votre expression musicale. Voici un exemple de ce qu’il vous est maintenant possible de faire en combinant ces méthodes :


live_loop :test do ; tick play (ring :c4, :e4, :g4, :b4, :d5, :f5, :a5).shuffle.reflect.take_last(5).look sleep 0.125 end

Il est déjà possible d’écrire beaucoup de musique en se limitant uniquement à la manipulation des anneaux sur les paramètres du synthétiseur, sur play, sur sleep, etc.. C’est lorsque j’ai appris les anneaux et leur utilisation que Sonic-Pi m’est soudainement apparu beaucoup plus puissant que je ne le pensais. Voici un exemple bruitiste d’utilisation du synthétiseur :fm :


live_loop :test do ; tick use_synth :fm use_synth_defaults divisor: (ring 1, 5, 10, 15).shuffle.look, depth: (ring 1, 2).look play :c3, pitch: (ring 0, 12, 4).look sleep (ring 0.25, 0.5).mirror.look end

Un autre exemple court mélangeant plusieurs types d’anneaux pour obtenir une boîte à rythme :

live_loop :woooh do ; tick sample :bd_tek if bools(1, 0, 1, 0).look sample :bd_klub if spread(1, 5).look sample :bd_boom if spread(1, 8).look sample :elec_cymbal, rate: 3 if bools(0, 1).look sleep 0.5 end

Une gamme / un accord, est un anneau

Sonic-Pi fait le choix de représenter les gammes et les accords sour la forme d’anneaux. Si cela n’est pas visible au premier abord, attendez de croiser la console d’erreur pour vous en convaincre. Prenons un exemple :

Do – Ré – Mi – Fa – Sol – La – Si Do  / C – D -E – F -G – A – B – C

(notation européenne) / (notation anglo-saxonne)

Do et Do, C et C se recoupent. Est-ce que Do reçoit l’index 0 ou l’index 7 ? En réalité, la question ne se pose pas grâce aux anneaux. Il n’y a que sept valeurs uniques au sein de notre liste. Aller au dernier index d’un anneau consiste à revenir au premier. Une gamme est un pattern cyclique si l’on omet la question déterminante de la hauteur. Observez l’exemple suivant :


live_loop :gammes do ; tick play (scale :c, :major).look play (scale :c, :major, num_octaves: 2).look sleep 0.125 end

(scale) est un anneau. Souvenez-vous de (chord), il s’agissait également d’un anneau. Notre première gamme comporte 7 index différents, mais la seconde, suite à l’adjonction de num_octaves: 2 en comporte le double. En introduisant la question de l’octave, nous avons introduit l’idée que deux paramètres déterminent une note, et non seul le nom. Cela a pu permettre à Sonic-Pi de déterminer qu’il fallait donc 14 index différents et les valeurs qui y sont associées.

Ce type d’anneau reçoit a minima deux paramètres : une note fondamentale et un type de gamme. Sonic-Pi propose d’emblée par auto-completion une liste de gammes pré-programmées. Celle-ci est assez vaste, et vous apprendra certainement l’existence de certaines échelles. Les modes à transposition limités de Messiaen sont présents, de même que nombre de gammes extra-européennes.

(chord) est une forme particulière d’anneau. Sonic-Pi ne considère qu’un nombre fini de notes dans accord, et ne prends pas en compte la répétition possible des notes. Il est donc loin de penser comme nous qu’une série de notes telle que :

Do – Ré – Mi- Sol – Do – Mi(8ve) – Do(8ve)

est un accord. Pour Sonic-Pi, Do majeur est constitué de Do – Mi – Sol, ni plus ni moins. En cela, il fait bonne oeuvre en tant que théoricien, mais mauvaise oeuvre en tant que musicien. Pour des questions d’orchestration, de voicings ou pour une série de bonnes raisons, la fonction (chord) n’est pas suffisante. Bien heureusement pour nous, nous connaissons d’autres méthodes pour créer des accords et toute superposition harmonique.

Cet anneau reçoit deux paramètres a minima : une note fondamentale et une qualité d’accord. Une nouvelle fois, Sonic-Pi en propose — par auto-complétion —  un nombre impressionnant.

L’enfant terrible : les listes

Nous en avons déjà brièvement parlé, Sonic-Pi hérite des listes, type de donnée natif en Ruby. Elles ont leur utilité,
et il ne faut pas les oublier. N’utilisez pas les anneaux pour tout ! Dès que vous devez gérer un nombre limité de données,
privilégiez les listes. Elles sont plus lisibles, et tout aussi puissantes. Voici par exemple la syntaxe typique que j’utilise
dès lors que je cherche à randomiser un paramètre entre trois valeurs différentes :
amp: [0.5, 0.7, 1].chosoe

Les listes excellent pour tout ce qui concerne les données fixes, mais sont perfectibles dsè qu’il s’agit de valeurs qui ont tendance à varier rapidement, ou à être suceptibles de profondes modifications. Si vous écrivez le code suivant, votre liste comportera déjà 7 membres :


(scale :a, :major)

Juste en ajoutant la commande suivante :


(scale :a, :major, num_octaves: 4)

Vous quadruplez le nombre d’éléments présents au sein de votre liste. Cela fait beaucoup de données ! Vous commencez donc à voir les avantages respectifs des anneaux et des listes. Un exemple comme le suivant est non seulement peu élégant, mais particulièrement laboreux à modifier :


live_loop :liste do play [:c4, :g4, :c5, :e5, :g5] ; sleep 0.25 play [:c4, :g4, :c5, :e5, :g5][0] ; sleep 0.25 play [:c4, :g4, :c5, :e5, :g5][1] ; sleep 0.25 play [:c4, :g4, :c5, :e5, :g5][2] ; sleep 0.25 play [:c4, :g4, :c5, :e5, :g5][3] ; sleep 0.25 bob = [“salut”, “bobby”] puts bob[1] sleep 1 end

Nous en savons assez sur les anneau et les listes pour écrire tout type d’enchaînement mélodique, harmonique ou rythmique. Nous reviendrons à ce sujet au cours d’articles plus avancés.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

[0] Mmmhh.. : les plus perspicaces d’entre vous se rappelleront d’avoir déjà croisé cette syntaxe. (chord) est un type d’anneau ! play_pattern_timed et ses compères utilisent des listes !