# Tutorials FoxDot ## Tutoriel 1 : Jouer des notes Dans FoxDot, tous les noms de variables à deux caractères sont réservés aux objets Players, comme 'p1' La création d'un objet Player sans argument jouera une seule note sur le do central, par défaut, de façon répétée jusqu'à ce qu'on l'arrête. Utilisez >> pour donner l'une de ces variables à un objet Player comme suit ``` p1 >> pluck() ``` Pour arrêter un objet Player individuel, il suffit d'exécuter ``` p1.stop() ``` En plus des variables à 2 caractères qui sont pré-réservées, vous pouvez créer les vôtres avec vos propres noms. propres avec vos propres noms ``` foo = Player() foo >> pluck() ``` Le >> en Python est généralement réservé à un type d'opération, comme + ou -, mais ce n'est pas le cas dans FoxDot. Si un utilisateur ré-exécute le code, FoxDot mettra à jour p1 au lieu de créer un PlayerObject, ce qui signifie que vous pouvez apporter des modifications à votre musique en utilisant une seule ligne de code. Si vous donnez maintenant quelques arguments à votre objet player, vous pouvez changer les notes jouées. Le premier argument doit être le degré de la note à jouer (par défaut, la note la plus basse de l'octave 5 de la gamme majeure) et n'a pas besoin d'être spécifié par son nom. Python, comme la plupart des langages de programmation, utilise l'indexation zéro pour accéder aux valeurs d'un tableau, ce qui signifie que 0 fait référence à la première note de la gamme. Donnez à votre objet Player des instructions pour faire de la musique avec son Synth. Le premier argument est la note de la gamme à jouer. Le code suivant joue les trois premières notes de la gamme par défaut (majeure) en répétition. Pour une seule note ``` p1 >> pluck(0) ``` Ou une liste de notes ``` p1 >> pluck([0,1,2]) ``` Mais vous devrez spécifier tout ce que vous voulez changer... Comme la durée des notes, ou la longueur de chaque note ``` p1 >> pluck([0,0,0], dur=[1,2,3]) ``` Ou l'amplitude, le "volume" de chaque note ``` p1 >> pluck([0,0,0], amp=[1,2,3]) ``` Si la deuxième liste, l'ampli dans cet exemple, est trop longue, alors la première liste (le degré) boucle simplement et est mise en correspondance avec les éléments restants de la liste avec les éléments restants de la deuxième liste (l'amplitude). ``` p1 >> pluck([0,2,4], amp=[1,2,3,1,5]) ``` Plus généralement, toutes les listes sont parcourues quelle que soit leur longueur. ``` p1 >> pluck([0,2,4], dur=[1,2], amp=[1,2,3,1,5]) ``` Les arguments peuvent être des entiers, des virgules flottantes, des fractions, des listes, tuples, ou un mélange ``` p1 >> pluck([0,0,0], dur=2) p1 >> pluck([0,0,0], dur=1.743) p1 >> pluck([0,0,0], dur=[0.25,0.5,0.75]) p1 >> pluck([0,0,0], dur=[1/4,1/2,3/4]) p1 >> pluck([0,0,0], dur=[1/4,0.25,3]) ``` Les listes de valeurs sont itérées au fur et à mesure que le Player joue des notes. La durée suivante équivaut à : 1,2,3,1,4,3 Si vous ne comprenez pas encore cela, ne vous inquiétez pas, vous en saurez plus sur les Patterns dans le tutoriel sur les Patterns. ``` p1 >> pluck([0,0,0], dur=[1,[2,4],3]) ``` Les valeurs des tuples sont utilisées simultanément, c'est-à-dire que p1 jouera 3 notes individuelles, puis un accord de 3 notes au même moment. ``` p1 >> pluck([0,2,4,(0,2,4)]) ``` Vous pouvez également attribuer des valeurs aux attributs des objets Players directement ``` p1.oct = 5 ``` Pour voir tous les noms des attributs du Player, il suffit d'exécuter ``` print(Player.get_attributes()) ``` Plus d'informations à ce sujet plus tard dans le tutoriel sur les attributs du Player Vous pouvez stocker plusieurs instances de Players et les assigner à des moments différents ``` proxy_1 = pads([0,1,2,3], dur=1/2) proxy_2 = pads([4,5,6,7], dur=1) p1 >> proxy_1 # Assigne le premier à p1 p1 >> proxy_2 # Cela remplace les instructions suivies par p1 ``` Pour jouer plusieurs séquences à la fois, il suffit de faire la même chose avec une autre séquence. ``` p1 >> pluck([0, 2, 3, 4], dur=1/2) p2 >> pads([(0, 2, 4), (3, 5, 7)], dur=8) ``` Joue uniquement ce Player, en mettant les autres en sourdine ``` p1.solo() # la valeur par défaut est 1 (solo activé) ``` Et désactiver le solo ``` p1.solo(0) ``` Arrêter (et pas seulement mettre en sourdine) les autres joueurs ``` p1.only() ``` Utiliser Ctrl+. pour tout effacer pour l'horloge de programmation ou l'exécution ``` Clock.clear() ``` ## Tutoriel 2 : Manipulation algorithmique Le code ci-dessous joue les quatre premières notes de la gamme par défaut en répétition : ``` p1 >> pads([0,1,2,3]) ``` Il est possible de manipuler cela en ajoutant un tableau de nombres à l'objet Player Ceci augmente la 4e note jouée de 2 degrés ``` p1 >> pads([0,1,2,3]) + [0,0,0,2] ``` Et ceci augmente chaque troisième note de 2 ``` p1 >> pads([0,1,2,3]) + [0,0,2] ``` Ces valeurs peuvent être alignées et regroupées ``` p1 >> pads([0,1,2,3]) + [0,1,[0,(0,2)]] ``` Ce comportement est particulièrement utile lors de l'utilisation de la méthode follow(). ``` b1 >> bass([0,4,5,3], dur=2) p1 >> pads().follow(b1) + [2,4,7] ``` Vous pouvez programmer les Players pour qu'ils fassent des choses Ceci demandera à p1 d'inverser les notes tous les 4 temps ``` p1 >> pads([0,2,4,6]) p1.every(4, "reverse") ``` Vous pouvez "enchaîner" des méthodes en les ajoutant à la fin de la ligne originale. ``` p1 >> pads([0,2,4,6]).every(4, "reverse") ``` Pour ne plus appeler "reverse", utilisez "never" : ``` p1.never("reverse") ``` Voici quelques autres méthodes que vous pouvez utiliser : L'utilisation de "stutter" jouera la même note "n" fois avec différents attributs spécifiés ``` p1.every(4, "stutter", 4, oct=4, pan=[-1,1]) ``` Rotate déplace toutes les valeurs de 1 dans leur ordre ``` p1.every(4, "rotate") ``` Pour randomiser l'ordre des notes, utilisez "shuffle" ``` p1.every(4, "shuffle") ``` ## Tutoriel 3 : Jouer des samples intégrés FoxDot peut aussi être utilisé pour séquencer et manipuler des samples audio. Pour ce faire, tout ce que vous avez à faire est d'utiliser la fonction spéciale play SynthDef. Le premier argument de la SynthDef de lecture doit être une chaîne de caractères au lieu d'une liste de nombres comme vous le feriez pour n'importe quel autre SynthDef. Chaque caractère représente un fichier audio différent, qui est stocké dans un tampon dans SuperCollider. Pour voir quel caractère correspond à quel fichier audio, exécutez ``` print(Samples) ``` Vous pouvez jouer des samples audio dans les sous-répertoires FoxDot/snd/ en utilisant le synthétiseur 'play' et en utilisant une chaîne de caractères au lieu d'une liste de notes. ``` bd >> play("x") ``` Un caractère fait référence à un son et l'espace blanc est utilisé pour le silence. vous pouvez étaler les sons dans le temps : ``` bd >> play("x x x ") hh >> play(" ‐") ``` Vous pouvez denteler des motifs en utilisant des parenthèses Ce qui se joue comme : "x o xo" ``` d1 >> play("(x )( x)o ") ``` Ce qui suit est identique à "-------=" ``` hh >> play("---(-=)") ``` Mettre des caractères entre crochets les jouera tous dans l'espace d'un temps. Et seront joués comme un seul caractère, non pas simultanément, mais en succession rapide. ``` d1 >> play("x-o[-o]") d1 >> play("x-o[---]") d1 >> play("x-o[-----]") d1 >> play("x-o[--------------]") ``` et peuvent être mis entre parenthèses comme s'ils étaient eux-mêmes un caractère. ``` d1 >> play("x[--]o(=[-o])") ``` Vous pouvez combiner les parenthèses comme vous le souhaitez : les modèles suivants sont identiques ``` d1 >> play("x-o(-[-o])") d1 >> play("x-o[-(o )]") ``` Les accolades { } sélectionnent un sample sonore au hasard si vous voulez plus de variété. ``` d1 >> play("x-o{-=[--][-o]}") ``` Les touches > et < combinent des motifs à jouer simultanément ``` d1 >> play("<X ><‐ ><# ><V >") d1 >> play("<X >< ‐ >< # >< V>") ``` Chaque caractère est associé à un dossier de fichiers sonores et vous pouvez sélectionner différents samples en utilisant le mot-clé "sample" comme argument ``` d1 >> play("(x[--])xu[--]") d1 >> play("(x[--])xu[--]", sample=1) d1 >> play("(x[--])xu[--]", sample=2) ``` Change le sample pour chaque temps ``` d1 >> play("(x[--])xu[--]", sample=[1,2,3]) ``` Vous pouvez superposer deux motifs ensemble (notez le "P", consultez le tutoriel 4 pour plus d'informations). ``` d1 >> play(P["x-o-"] & P[" **"]) ``` Et changer les effets appliqués à tous les motifs superposés en même temps ``` d1 >> play(P["x-o-"] & P[" **"], room=0.5) ``` Exemple tiré du tutoriel sur le lecteur, mais avec des samples à la place Conditions... ``` d1 >> play("x[--]xu[--]x", sample=(d1.degree=="x")) ``` Ou passer à la banque de samples 2 en multipliant ``` d1 >> play("x[--]xu[--]x", sample=(d1.degree=="x")*2) ``` Enchaîner des conditions multiples ``` d1 >> play("x[--]xu[--]x", sample=(d1.degree=="x")*2 + (d1.degree=="-")*5) ``` Ce qui est la même chose que ``` d1 >> play("x[‐‐]xu[‐‐]x", sample=d1.degree.map({"x":2, "‐":5})) ``` ## Tutoriel 4 : Utiliser des Patterns Les Player Objects utilisent des listes Python, plus connues sous le nom de tableaux dans d'autres langages, pour se séquencer. Vous avez déjà utilisé ces listes auparavant, mais elles ne sont pas exactement flexibles pour la manipulation. Par exemple, essayez de multiplier une liste par deux comme suit : ``` print([1, 2, 3] * 2) ``` Le résultat correspond-il à ce que vous attendiez ? FoxDot utilise un type de conteneur appelé 'Pattern' pour résoudre ce problème. Ils agissent comme des listes normales, mais toute opération mathématique effectuée sur ces listes est effectuée sur chaque élément de la liste. Dans la liste, et par paire si l'on utilise une seconde Pattern. Une Pattern de base est créé comme vous le feriez avec une liste normale ou un tuple, mais en le faisant précéder d'un 'P'. ``` print(P[1,2,3] * 2) print(P[1,2,3] + 100) ``` Dans cette opération, la sortie est constituée de toutes les combinaisons des deux Patterns, c'est-à-dire ``` [1+3, 2+4, 3+3, 1+4, 2+3, 3+4] print(P[1,2,3] + [3,4]) ``` Vous pouvez utiliser la syntaxe de découpage de Python pour générer une série de nombres. ``` print(P[:8]) print(P[0,1,2,3:20]) print(P[2:15:3]) ``` Essayez d'autres opérateurs mathématiques et voyez les résultats que vous obtenez. ``` print(P[1,2,3] * (1,2)) ``` Les Patterns entrelacent aussi automatiquement toute liste imbriquée. Comparez Liste normale : ```python for n in [0,1,2,[3,4],5] print(n) ``` avec Pattern ```python for n in P[0,1,2,[3,4],5] print(n) ``` Utilisez PGroups si vous voulez éviter ce comportement. Ceux-ci peuvent être implicitement spécifiés en tant que tuples dans Patterns : ```python for n in P[0,1,2,(3,4)] print(n) ``` Il s'agit d'un PGroup : ``` print(P(0,2,4) + 2) print(type(P(0,2,4) + 2)) ``` En Python, vous pouvez générer une plage d'entiers avec la syntaxe range(start, stop, step). Par défaut, start est 0 et step est 1. ``` print(list(range(10))) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` Vous pouvez utiliser PRange(start, stop, step) pour créer un objet Pattern avec les valeurs équivalentes : ``` print(PRange(10))` P[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] P[0, 2, 2, 6, 4, 10, 6, 14, 8, 18] P[0*1, 1*2, 2*1, 3*2, 4*1, 5*2, 6*1, 7*2, 8*1...] print(PRange(10) * [1, 2]) ``` Comportement de la classe Pattern L'ajout d'une liste (ou d'une Pattern) à une Pattern ajoutera les valeurs des éléments à l'autre là où les listes Python seraient concaténées. ``` print(PRange(10) + [0,10]) ``` Pour concaténer des Patterns, utilisez l'opérateur pipe comme suit : ``` print(PRange(10) | [0,10]) ``` FoxDot convertit automatiquement tout objet qui est canalisé vers une Pattern en classe de Pattern de base. Pour que vous n'ayez pas à vous soucier de vérifier que tout est du bon type. Ça joue toutes les valeurs ensemble ``` p1 >> pluck(P(4,6,8)) p1 >> pluck(P[0,1,2,P(4,6,8),7,8]) ``` Répartit les valeurs sur la durée actuelle, par exemple si la durée est de 2 temps, chaque valeur sera jouée à 2/3 temps d'intervalle. ``` p1 >> pluck(P*(0,2,4), dur=1/2) p1 >> pluck(P*(0,2,4), dur=1) p1 >> pluck(P*(0,2,4), dur=2) p1 >> pluck(P[0,1,2,P*(4,6,8),7,8], dur=1) ``` C'est la même chose que P* mais toutes les autres fois que les notes sont jouées, elles sont réparties sur la valeur dur. ``` p1 >> pluck(P/(0,2,4), dur=1/2) p1 >> pluck(P/(0,2,4), dur=1) p1 >> pluck(P/(0,2,4), dur=2) p1 >> pluck(P[0,1,2,P/(4,6,8),7,8], dur=1) ``` Répartit les valeurs sur le "sus" actuel, par exemple si le dur est de 2 temps et le sus de 3 temps, chaque valeur sera jouée à 1 temps d'intervalle. ``` p1 >> pluck(P+(0,2,4), dur=2, sus=3) p1 >> pluck(P+(0,2,4), dur=2, sus=1) p1 >> pluck(P[0,1,2,P+(4,6,8),7,8], dur=1, sus=3) ``` Répartit les premières valeurs avec un écart de la dernière valeur entre chacune d'entre elles Joue 0,2,4 avec un écart de 0,5 : ``` p1 >> pluck(P^(0,2,4,0.5), dur=1/2) ``` Les Patterns sont accompagnés de plusieurs méthodes pour manipuler leur contenu ``` help(pattern) ``` Modèle standard ``` print(P[:8]) ``` Mélanger le motif en le rendant aléatoire ``` print(P[:8].shuffle()) ``` Ajouter un motif inversé au motif ``` print(P[:8].palindrome()) ``` Décaler le motif de n (par défaut 1) ``` print(P[:8].rotate()) print(P[:8].rotate(3)) print(P[:8].rotate(-3)) ``` Prend le motif et l'ajoute autant de fois que nécessaire pour atteindre n nombre d'éléments dans le motif ``` print(P[:8].stretch(12)) print(P[:8].stretch(20)) ``` Inverse un motif ``` print(P[:8].reverse()) ``` Boucle un motif n nombre de fois ``` print(P[:8].loop(2)) ``` Ajoute un décalage ``` print(P[:8].offadd(5)) ``` Ajoute un décalage multiplié ``` print(P[:8].offmul(5)) ``` Bégaiement - Répéter chaque élément n fois ``` print(P[:8].stutter(5)) ``` Amen Fusionne et relie les deux premiers et derniers éléments de telle sorte qu'un motif de batterie "x-o-" deviendrait "(x[xo])-o([-o]-)" et imite le rythme du célèbre "amen break" ``` d1 >> play(P["x-o-"].amen()) print(P[:8].amen()) ``` Bulle Fusionne et relie les deux premiers et derniers éléments de telle sorte qu'un motif de batterie "x-o-" deviendrait "(x[xo])-o([-o]-) ``` d1 >> play(P["x‐o‐"].bubble()) print(P[:8].bubble()) ``` ## Tutorial 5: Les attributs des Players Vous pouvez définir des variables en dehors d'un player ``` pitches = P[0,1,2,3,4] harmony = pitches + 2 print(pitches) print(harmony) p1 >> pluck(pitches) p2 >> star(harmony) ``` Si vous fixez la durée de la seconde, cela pourrait ne pas avoir l'effet désiré ``` p1 >> pluck(pitches) p2 >> star(harmony, dur=1/2) ``` Il est possible pour un objet Player de jouer exactement ce que joue un autre Player. Pour qu'un Player en suive un autre, il suffit d'utiliser la méthode follow : ``` p1 >> pluck(pitches) p2 >> star(dur=1/2).follow(p1) + 2 ``` Vous pouvez aussi référencer explicitement des attributs tels que la hauteur ou la durée : ``` p2 >> star(p1.pitch) + 2 ``` c'est la même chose que .follow(p1) Fonctionne aussi pour d'autres attributs ``` p1 >> pluck(pitches) p2 >> star(dur=p1.dur).follow(p1) + 2 ``` Vous pouvez référencer et tester la valeur actuelle Le == renvoie un 1 si c'est vrai et un 0 si c'est faux ``` print(p1.degree) print(p1.degree == 2) ``` Cela vous permet de faire des conditionnelles comme ``` p1 >> pluck([0,1,2,3], amp=(p1.degree==1)) p1 >> pluck([0,1,2,3], amp=(p1.degree>1)) ``` Ou le changer pour une autre amplitude en le multipliant par 4 ``` p1 >> pluck([0,1,2,3], amp=(p1.degree==1)*4) ``` Enchaîner plusieurs conditionnels ``` p1 >> pluck([0,1,2,3], amp=(p1.degree==1)*4 + (p1.degree==2)*1) ``` Ce qui est la même chose que ``` p1 >> pluck([0,1,2,3], amp=p1.degree.map({1:4, 2:1})) ``` ## Tutoriel 6 : Les repos Les silences peuvent être ajoutés en utilisant un objet silencieux dans le tableau dur Le repos fait taire la note qui aurait été jouée. Sans repos, 5 notes ``` p1 >> pads([0,1,2,3,4], dur=[1,1,1,1,1]) ``` Avec un repos c'est à dire 4 notes et un repos, la note "4" est réduite au silence pendant 4 temps ``` p1 >> pads([0,1,2,3,4], dur=[1,1,1,1,rest(4)]) ``` ## Tutoriel 7 : Les bases de l'horloge Pour arrêter tous les Players, vous pouvez appuyer sur Ctrl+. (Maintenir Ctrl et appuyer sur le point) Ce qui est un raccourci pour la commande : ``` Clock.clear() ``` Changer le tempo (cela prend effet à la prochaine mesure) La valeur par défaut est 120. ``` Clock.bpm = 144 ``` Pour voir ce qui est programmé pour être joué. ``` print(Clock) ``` Pour voir quelle est la latence ``` print(Clock.latency) ``` Parfois, vous voulez savoir quand commence le prochain cycle de X temps. Pour faire cela, nous utilisons la méthode 'mod'. Par exemple, si nous voulons savoir quand le début du prochain cycle de 32 battements, nous pouvons faire ``` print(Clock.mod(32)) ``` ## Tutoriel 8 : Les gammes Par défaut, les Players utilisent la gamme C Major. Celles-ci peuvent être modifiées en utilisant les mots-clés 'scale' et 'root'. Les gammes peuvent être définies comme un tableau de demi-tons, tel que la gamme majeure est [0,2,4,5,7,9,11] ou l'une des gammes prédéfinies du module 'Scale', par exemple Scale.minor. Le 'root' se réfère à la tonique de la gamme ; 0 étant C, 1 est C#, 2 est D et ainsi de suite. La gamme par défaut peut être modifiée de telle sorte que tout Player n'utilisant pas une gamme spécifique sera mis à jour. Cela se fait en utilisant la syntaxe ci-dessous (chaque ligne est techniquement équivalente) : ``` Scale.default.set("major") Scale.default.set(Scale.major) Scale.default.set([0,2,4,5,7,9,11]) ``` Ou la même chose, mais en mineur : ``` Scale.default.set("minor") Scale.default.set(Scale.minor) Scale.default.set([0,2,3,5,7,10]) ``` Pour gagner du temps, vous pouvez également faire ``` Scale.default = "minor" ``` C'est la même chose pour la racine : ``` Root.default.set(1) Root.default.set("C#") ``` Ou : ``` Racine.default.set(2) Racine.default.set("D") ``` Pour voir une liste de toutes les gammes, utilisez ``` print(Scale.names()) ``` Vous pouvez changer la gamme utilisée par un Player en utilisant le mot-clé 'scale'. ``` p1 >> pads([0,1,2], scale=Scale.minor) ``` De la même manière, vous pouvez changer les Players de note fondamentale en utilisant le mot-clé root et l'objet Root.default ``` p1 >> pads([0,1,2], scale=Scale.minor, root=2) ``` ## Tutoriel 9 : Les groupes Les attributs des Players, tels que le degré ou la gamme, peuvent également être modifiés en leur attribuant directement des valeurs, comme suit ``` p1 >> pads([0,2,4,2], scale=Scale.majorPentatonic) ``` C'est équivalent à ``` p1 >> pads() p1.degree = [0,2,4,2] p1.scale = Scale.majorPentatonic ``` C'est utile si vous voulez assigner les mêmes valeurs à plusieurs Players simultanément, comme ceci : ``` p1 >> pads([0,2,4,2]) p2 >> pads([2,1,0,4]) p3 >> pads([2,3]) p1.dur=p2.dur=p3.dur=[1,1/2,1/4,1/4] p1.stop() p2.stop() p3.stop() ``` Vous pouvez référencer tous les membres avec des noms similaires ``` p_all.dur = [1/2,1/4] ``` Exécutez ceci pendant que p1, p2, etc. jouent ! ou ``` p_all.amplify = 1 ``` ou... ``` p_all.stop() ``` ou... ``` p_all.solo() ``` Pour réduire la quantité de saisie, les Players peuvent être regroupés et leurs attributs modifiés d'une manière plus simple ``` p1 >> pads([0,2,4,2]) p2 >> pads([2,1,0,4]) p3 >> pads([2,3]) g1 = Group(p1, p2, p3) g1.dur=[1,1/2,1/4,1/4] ``` Vous pouvez regrouper les groupes _all ``` g1 = Group(p_all, d_all, b1, b2) ``` Active le volume pendant 4 temps, puis le désactive pendant 4 temps Cela remplace les amplitudes existantes définies dans les players ``` g1.amp=var([1,0],4) g1.stop() ``` Vous pouvez utiliser des fonctions pour regrouper des éléments. Pour exécuter, utilisez CTRL+Return, et non ALT+Return. ```python def tune() : b1 >> bass([0,3], dur=4) p1 >> pluck([0,4], dur=1/2) d1 >> play("x--x--x-") tune() ``` ou programmer l'horloge pour appeler d'autres fonctions groupées ```python def verse(): b1 >> bass([0,3], dur=4) p1 >> pluck([0,4], dur=1/2) d1 >> play("x‐‐x‐‐x‐") Clock.future(16, chorus) def chorus(): b1 >> bass([0,4,5,3], dur=4) p1 >> pluck([0,4,7,9], dur=1/4) d1 >> play("x‐o‐") Clock.future(16, verse) verse() ``` ## Tutoriel 10 : Utiliser les variables Une TimeVar est une abréviation de "Time Dependent Variable" (variable dépendante du temps) et est une caractéristique clé de FoxDot. Une TimeVar a une série de valeurs entre lesquelles elle change après un nombre prédéfini de battements Elle est créée à l'aide d'un objet var avec la syntaxe ``` var([list_of_values],[list_of_durations]) ``` Génère les valeurs : 0,0,0,0,3,3,3,3... ``` a = var([0,3],4) ``` La durée peut être une valeur unique ``` print(int(Clock.now()), a) #'a' a initialement une valeur de 0 >>> 0, 0 ``` La première valeur peut être différente... ``` print(int(Clock.now()), a) ``` Après 4 temps, la valeur passe à 3 ``` print(int(Clock.now()), a) >>> 4, 3 ``` Après 4 autres battements, la valeur passe à 0 ``` print(int(Clock.now()), a) >>> 8, 0 ``` La durée peut également être une liste ``` a = var([0,3],[4,2]) print(int(Clock.now()), a) ``` Lorsqu'une TimeVar est utilisée dans une opération mathématique, les valeurs qu'elle affecte deviennent également des TimeVars qui changent d'état lorsque la TimeVar originale change d'état - ceci peut même être utilisé avec des motifs : ``` a = var([0,3], 4) print(int(Clock.now()), a + 5) ``` Quand le temps est 0, a est 5 ``` print(int(Clock.now()), a + 5) >>> 5 ``` Lorsque le temps est 4, a est 8 ``` print(int(Clock.now()), a + 5) >>> 8 ``` ``` b = PRange(4) + a` `print(int(Clock.now()), b) ``` Après 8 temps, la valeur passe à 0 ``` P[0, 1, 2, 3] print(int(Clock.now()), b) ``` Après 12 battements, la valeur passe à 3 ``` P[3, 4, 5, 6] ``` Utilisez 'var' avec vos objets Player pour créer des progressions d'accords. ``` a = var([0,4,5,3], 4) b1 >> bass(a, dur=PDur(3,8)) p1 >> pads(a + (0,2), dur=PDur(7,16)) ``` Vous pouvez ajouter un 'var' à un objet Player ou à une variable. ``` b1 >> bass(a, dur=PDur(3,8)) + var([0,1],[3,1]) b = a + var([0,10],8) print(int(Clock.now()), (a, b)) ``` La mise à jour des valeurs d'une 'var' se répercute partout ailleurs ``` a.update([1,4], 8)` print(int(Clock.now()), (a, b)) ``` Les variables peuvent être nommées... ``` var.chords = var([0,4,5,4],4) ``` Et utilisé plus tard ``` b1 >> pluck(var.chords) ``` Tous les lecteurs utilisant la variable nommée seront mis à jour ``` var.chords = var([0,1,5,3],4) ``` Vous pouvez également utiliser une 'linvar' qui change ses valeurs progressivement dans le temps Changez la valeur de 0 à 1 sur 16 temps ``` c = linvar([0,1],16) ``` Exécutez cette opération plusieurs fois pour voir les changements se produire ``` print(int(Clock.now()), c) ``` Changez l'ampli en fonction de cette linvar ``` p1 >> pads(a, amp=c) ``` une 'Pvar' est une 'var' qui peut stocker des motifs (par opposition à des entiers, par exemple) ``` d = Pvar([P[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], P[0, 1, 2, 3, 4, 5, 4, 3, 2, 1]], 8) print(int(Clock.now()), d) p1 >> pads(a, amp=c, dur=1/4) + d ``` Change l'échelle tous les 16 temps ``` Scale.default = Pvar([Scale.major, Scale.minor],16) ``` Vous pouvez même définir la valeur pour toujours une fois qu'elle est atteinte à l'aide d'une valeur spéciale appelée "inf" ``` x = var([0, 1, 2, 3], [4, 4, 4, inf]) print(x) # Continuez à appuyer - il s'arrêtera finalement à 3 ``` Autres types de "var" Il existe plusieurs sous-classes de "var" qui renvoient des valeurs comprises entre les nombres spécifiés. Par exemple, une "linvar" change progressivement les valeurs de façon linéaire : ``` print(linvar([0,1],8)) # continuer à courir pour voir la valeur changer entre 0 et 1 ``` Exemple : augmenter la coupure du filtre passe-haut sur 32 temps ``` p1 >> play("x-o-", hpf=linvar([0,4000],[32,0])) ``` D'autres types incluent "sinvar" et "expvar" ``` print("Linéaire :", linvar([0, 1], 8)) print("Sinusoïdal :", sinvar([0, 1], 8)) print("Exponentielle :", expvar([0, 1], 8)) ``` Modèle TimeVar Parfois, nous pourrions vouloir stocker des motifs entiers dans une var mais si nous essayons de le faire, ils sont automatiquement mis en parallèle : ``` motif1 = P[0, 1, 2, 3] motif2 = P[4, 5, 6, 7] print(var([motif1, motif2], 4)) ``` Pour stocker des motifs entiers, vous devez utiliser une "Pvar" qui ne lace pas les valeurs, mais stocke les motifs à la place ``` print(Pvar([motif1, motif2], 4)) p1 >> pluck(Pvar([pattern1, pattern2], 4), dur=1/4) ``` Décalage de l'heure de début Une autre astuce utile consiste à décaler l'heure de début de la var. Par défaut, c'est lorsque l'heure de l'horloge est 0, mais vous pouvez spécifier une valeur différente en utilisant le mot-clé "start ``` print(linvar([0, 1], 8)) print(linvar([0, 1], 8, start=2)) ``` Ceci peut être combiné avec Clock.mod() pour démarrer une rampe au début du prochain cycle de 32 temps : ``` d1 >> play("x‐o‐", hpf=linvar([0,4000],[32,inf], start=Clock.mod(32))) ``` ## Tutoriel 11 : Jouer des samples personnalisés Vous pouvez utiliser vos propres samples en déposant simplement des fichiers audio dans les répertoires de samples existants de FoxDot. Ceux-ci se trouvent dans le répertoire 'snd' à la racine de l'installation de FoxDot (par exemple, `C:\NPython27\NLib\Nsite-packages\NFoxDot\Nsnd'`). Vous avez vu précédemment comment travailler avec des samples en utilisant play(). Vous pouvez également lire des samples avec loop(). ``` s1 >> loop('foxdot') ``` Vous pouvez remarquer que ceci ne fait que jouer la première partie du sample encore et encore. Vous pouvez modifier le comportement avec la plupart des arguments que nous avons vus jusqu'à présent pour contrôler d'autres synthés. dur est un bon point de départ. ``` s1 >> loop('foxdot', dur=4) ``` Si vous avez un dossier plein de samples que vous aimeriez utiliser dans FoxDot, vous pouvez appeler loop() avec le chemin complet du sample. ``` s1 >> loop('/path/to/samples/quack.wav') ``` Si vous donnez à loop le chemin d'un dossier, il jouera le premier sample qu'il trouvera. Vous pouvez changer le sample joué avec sample=arg. Joue le premier sample de ma collection ``` s1 >> loop('/chemin/vers/samples') ``` Joue le deuxième samples de ma collection ``` s1 >> loop('/path/to/samples', sample=1) ``` Si vous avez l'intention d'utiliser beaucoup de samples d'un dossier, vous pouvez l'ajouter au chemin de recherche des samples. FoxDot va FoxDot recherchera dans tous ses chemins de recherche un samples correspondant lorsque vous lui donnerez un nom. ``` Samples.addPath('/path/to/samples') s1 >> loop('quack') ``` Une fois que vous avez un chemin de recherche, vous pouvez utiliser la recherche par motif pour rechercher des samples. Jouer le 3ème samples dans le répertoire 'snare'. ``` s1 >> loop('snare/*', sample=2) ``` Vous pouvez également utiliser * dans les noms de répertoire ``` s1 >> loop('*_120bpm/drum*/kick*') ``` ** signifie "tous les sous-répertoires récursifs". Ceci jouera le premier sample imbriqué dans 'percussion' (par exemple 'percussion/kicks/classique/808.wav') ``` s1 >> loop('percussion/**/*') ``` ## Tutoral 12 : SynthDefs FoxDot crée de la musique en donnant aux Players un "instrument numérique" à jouer qui sont appelés 'SynthDefs'. Vous pouvez voir la liste des 'Synths' pré-installés en exécutant la commande ``` print(SynthDefs) ``` Chacun d'entre eux représente un *objet `SynthDef`. Ces objets sont alors donnés aux Players pour qu'ils en jouent - comme on donne un instrument à quelqu'un dans votre orchestre. Écrire vos propres définitions de synthétiseurs, c'est un peu plus avancé, mais si vous avez déjà écrit des SynthDefs dans Supercollider alors vous vous sentirez peut-être à l'aise. Si ce n'est pas le cas, revenez à cette section plus tard. FoxDot peut accéder à n'importe quel SynthDef stocké sur le serveur SuperCollider, mais il doit savoir qu'il est là. Si vous avez déjà écrit un SynthDef dans SuperCollider et que vous l'avez nommé \NMySynth, il vous suffit de créer une instance de SynthDef en utilisant FoxDot comme ceci : ``` mySynth = SynthDef("mySynth") ``` Utiliser le même nom de variable dans FoxDot que dans SuperCollider pour votre SynthDef est une bonne idée pour éviter toute confusion. Si vous voulez écrire (ou éditer) votre propre SynthDef pendant l'exécution dans FoxDot, vous pouvez utiliser une API de SuperCollider en important le module SCLang. Tous les objets SynthDef de FoxDot héritent du comportement de la classe de base, comme les filtres passe-bas et passe-haut et le vibrato, mais ils peuvent être remplacés ou mis à jour facilement. Si vous voulez en savoir plus sur le traitement numérique du son et la création de SynthDef, consultez la documentation de SuperCollider. Vous trouverez ci-dessous un exemple de création dans FoxDot : Module d'importation pour écrire du code SCLang à partir de Python ``` from SCLang import * ``` Créer un SynthDef nommé 'exemple' (utiliser le même nom de variable que le nom du SynthDef est une bonne idée) ``` exemple = SynthDef("exemple") ``` Créer l'oscillateur (osc) en utilisant une onde sinusoïdale ``` exemple.osc = SinOsc.ar(ex.freq) ``` Et lui donner une enveloppe de son percussif (env) ``` exemple.env = Env.perc() ``` Enfin, stockez-le ! ``` exemple.add() ``` Comment créer un SynthDef avec SynthDef("pads") comme pads : ``` pads.osc = SinOsc.ar(pads.freq) pads.env = Env.perc() ``` Équivalent à ``` pads = SynthDef("pads") pads.osc = SinOsc.ar(pads.freq) pads.env = Env.perc() pads.add() ``` ## Tutoriel 13 : Horloge avancée Pour voir ce qui est programmé pour être joué. ``` print(Clock) ``` Pour voir quelle est la latence ``` print(Clock.latency) ``` L'horloge peut programmer n'importe quoi avec une méthode `__call__` en utilisant la méthode `__call__`. Il faut un indice de temps absolu pour programmer une fonction Clock.schedule a besoin de connaître le temps sur lequel appeler quelque chose ``` Clock.schedule() ``` Programmation d'un événement après une certaine durée Clock.future a besoin de savoir combien de temps avant il faut appeler quelque chose ``` Clock.future() ``` Ces méthodes sont équivalentes ``` Clock.schedule(lambda : print("hello"), Clock.now() + 4) Clock.future(4, lambda : print("hello")) ``` Pour programmer autre chose ``` Clock.schedule(lambda : print("hello")) ``` Nous pouvons appeler quelque chose tous les n battements ``` Clock.every(4, lambda : print("hello")) ``` On récupère l'horloge actuelle et on ajoute 2. ``` print(Clock.now() + 2) ``` Donner une commande à la prochaine mesure ``` nextBar(Clock.clear) ``` Avec un décorateur ``` @nextBar def change() : Root.default=4 Scale.default="minor" etc etc ``` Vous pouvez créer votre propre fonction, et la décorer, pour pouvoir l'utiliser dans un .every sur un objet Player ``` @PlayerMethod def test(self): print(self.degree) p1 >> pluck([0,4]).every(3, "test") #And cancel it with p1.never("test") ``` ## Tutoriel 14 : Référence des attributs du Player Pour voir tous les attributs : ``` print(Player.get_attributes()) ``` Vous pouvez voir quels effets sont disponibles en évaluant ``` print(FxList) ``` Utilisons le filtre passe-haut pour un exemple. Vous pouvez voir qu'il est décrit comme suit : ``` "<Fx 'highPassFilter' -- args : hpr, hpf>" ``` Chaque effet a un argument "maître" et des arguments enfants. Ici, l'argument maître est "hpf" (abréviation de high pass filter) et l'argument enfant est "hpr" (abréviation de high pass resonance). L'effet n'est ajouté que lorsque l'argument maître est différent de zéro : ``` d1 >> dirt([0,4,2,1], dur=1/2, hpf=4000) ``` Ceci règle le filtre passe-haut à 4000 Hz de sorte que seules les fréquences du signal audio *supérieur* à cette fréquence soient entendues. Modifions la valeur de la résonance. La valeur par défaut est de 1 donc réduisons-la ``` d1 >> dirt([0,4,2,1], dur=1/2, hpf=4000, hpr=0.3) ``` Vous remarquez une différence ? Nous pouvons utiliser des patterns / vars dans nos effets pour les faire changer avec le temps : ``` d1 >> dirt([0,4,2,1], dur=1/2, hpf=linvar([0,4000],8),hpr=P[1,1,0.3].stretch(8)) ``` :::danger ### [ Pour aller plus loin, voir la page Players >>>](https://hackmd.io/3QIKqhB2RAm0MsOXW619zQ#2---Players) ::: ## Tutoriel 15 : Générateurs de Patterns Référence Il y a plusieurs autres classes de Pattern dans FoxDot qui vous aident à générer des tableaux de nombres mais qui se comportent également de la même manière que la Pattern de base. Pour voir quels sont les motifs existants et essayer de les utiliser, exécutez ``` print(classes(Patterns.Sequences)) ``` **PEuclid** PEuclid(n, k) Retourne le rythme euclidien qui répartit 'n' impulsions sur 'k' pas aussi uniformément que possible. 3 impulsions sur 8 pas ``` print(PEuclid(3, 8)) ``` **PDur** PDur(n, k, start=0, dur=0.25) renvoie les durées réelles basées sur les rythmes euclidiens (voir PEuclid) où dur est la longueur de chaque pas. Répartit 'n' impulsions sur 'k' étapes aussi uniformément que possible. ``` print(PDur(3,8)) # P[0.75, 0.75, 0.5] print(PDur(5,8)) # Donne une liste de 3 dur, jointe à une liste de 5 dur print(PDur([3,5],8)) d1 >> play("x", dur=PDur(5,8)) ``` **PIndex** Retourne l'index auquel on accède ``` print(PIndex()) print(PIndex()*4) ``` **PSine** PSine(n=16) Renvoie les valeurs d'un cycle de l'onde sinusoïdale divisée en 'n' parties ``` # Divisée en 5 parties print(PSine(5)) # Divisée en 10 print(PSine(10)) ``` **PTri** PTri(start, stop=None, step=None) Retourne un motif équivalent à `Pattern(range(start, stop, step))` avec sa forme inversée ajoutée. Pensez-y comme à un angle "Tri". ``` # Jusqu'à 5 puis jusqu'à 1 print(PTri(5)) # Jusqu'à 8 puis jusqu'à 1 print(PTri(8)) # De 3 à 10, puis jusqu'à 4 print(PTri(3,10)) # De 3 à 30, par 2, puis jusqu'à 4 print(PTri(3,20,2)) # Jusqu'à 4, puis jusqu'à 1, puis jusqu'à 8, puis jusqu'à 1 print(PTri([4,8])) p1 >> pluck(PTri(5), scale=Scale.default.pentatonic) # Identique à p1 >> pluck(PRange(5) | PRange(5,0,‐1), scale=Scale.default.pentatonic) ``` **PRand** PRand(start, stop=None) Renvoie un nombre entier aléatoire entre le début et la fin. Retourne un entier aléatoire entre 0 et start. ``` print(PRand(8)[:5]) # Renvoie un entier aléatoire entre le début et la fin. print(PRand(8,16)[:5]) # Si start est un type de conteneur, il renvoie un élément aléatoire pour ce conteneur. print(PRand([1,2,3])[:5]) # Vous pouvez fournir un seed print(PRand([1,2,3], seed=5)[:5]) Continue à générer des mélodies aléatoires p1 >> pluck(PRand(8)) # Crée une liste aléatoire, et itère sur cette même liste p1 >> pluck(PRand(8)[:3]) ``` **PRhythm** PRhythm prend une liste de durées simples et de tuples qui contiennent des valeurs qui peuvent être fournies à `PDur` Ce qui suit joue du hi hat avec un rythme euclidien de 3 pulsations en 8 étapes ``` d1 >> play("x‐o‐", dur=PRhythm([2,(3,8)]))` `print(PRhythm([2,(3,8)])) ``` **PSum** PSum(n, total) Renvoie un motif de longueur 'n' dont la somme est égale à 'total' ``` # Retourne un Pattern de longueur 2, dont la somme des éléments est égale à 8 print(PSum(3,8)) # Retourne un motif de longueur 5, dont la somme des éléments est égale à 4 print(PSum(5,4)) ``` **PStep** PStep(n, value, default=0) Retourne un motif dont chaque terme n est 'valeur' sinon 'défaut' ``` # Tous les 4, la valeur est 1, sinon la valeur par défaut est 0 print(PStep(4,1)) # Tous les 8, 6, sinon 4 print(PStep(8,6,4)) # Tous les 5, mettre 2, sinon, 1 print(PStep(5,2,1)) ``` **PWalk** PWalk(max=7, step=1, start=0) Par défaut, renvoie un motif dont chaque élément est aléatoirement 1 plus haut ou plus bas que le précédent ``` print(PWalk()[:16]) # Changement de pas print(PWalk(step=2)[:16]) # Avec max print(PWalk(max=2)[:16]) #Commence à un nombre non nul print(PWalk(start=6)[:16]) ``` **PWhite** PWhite(lo=0, hi=1) Retourne des valeurs aléatoires en virgule flottante entre 'lo' et 'hi' Lo vaut par défaut 0, hi vaut par défaut 1 ``` print(PWhite()[:8]) # renvoie des nombres aléatoires entre 1 et 5 print(PWhite(1,5)[:8]) ``` :::danger ### [ Pour aller plus loin, voir la page Patterns >>>](https://hackmd.io/jFtm4KRLQCOmarXZxFirsg?view) ::: **Modèles de générateurs personnalisés** Les modèles de générateurs personnalisés peuvent être créés en sous-classant GeneratorPattern et en redéfinissant `GeneratorPattern.func` ```python classe CustomGeneratorPattern(GeneratorPattern) :` `def func(self, index) :` `return int(index / 4)` `print(CustomGeneratorPattern()[:10]) ``` Ceci peut être fait de manière plus consciencieuse en utilisant `GeneratorPattern.from_func`, en passant une fonction qui prend un index et renvoie un élément de la Pattern. ```python def some_func(index) :` `return int(index / 4)` `print(GeneratorPattern.from_func(some_func)[:10]) ``` Nous pouvons également utiliser des lambdas ``` print(GeneratorPattern.from_func(lambda index: int(index / 4))[:10]) ```