# 3 - Patterns
### 3.1. Méthodes de Patterns
Voici une liste détaillée des méthodes avec des exemples qui peuvent être utilisés avec des objets Pattern. Ils sont utiles pour transformer des séquences de valeurs et peuvent être combinés avec des objets Player en utilisant chaque méthode (plus d’informations ici).
**`.shuffle(n=1)`**
Renvoie le Pattern avec son contenu dans un ordre aléatoire et n est le nombre de permutations :
```
# Permutation unique de la lecture aléatoire
>>> print(P[0, 1, 2, 3].shuffle())
P[0, 3, 2, 1]
```
```
# Deux permutations
>>> print(P[0, 1, 2, 3].shuffle(2))
P[3, 1, 2, 0, 2, 1, 3, 0]
```
**`.reverse()`**
Retourne le Pattern avec son contenu dans l’ordre inverse. Les Pattern / groupes imbriqués ne sont pas inversés. Utilisez Pattern.mirror pour obtenir ce comportement :
```
# Inverse l'ordre
>>> P[0, 1, 2, 3].reverse()
P[3, 2, 1, 0]
```
```
# Le motif imbriqué n’est pas inversé lors de l’utilisation de reverse
>>> P[[0, 4], 3, 2, 1].reverse()
P[1, 2, 3, P[0, 4]]
```
```
# L’utilisation de mirror() inverse le motif imbriqué
>>> P[[0, 4], 3, 2, 1].mirror()
P[1, 2, 3, P[4, 0]]
```
**`.mirror()`**
Retourne un Pattern avec son contenu dans l’ordre inverse, y compris les motifs et les groupes imbriqués :
```
# Inverse l'ordre
>>> P[0, 1, 2, 3].mirror()
P[3, 2, 1, 0]
```
```
# Inverser les Patterns et les groupes imbriqués
>>> P[[0, 1], 2, 3, (4, 5)].mirror()
P[P(5, 4), 3, 2, P[1, 0]]
```
**`.sort(*args, kwargs)`**
Retourne un Pattern avec les valeurs triées dans l’ordre. Les *args et **kwargsare ceux qui sont fournis à la fonction intégrée triée de Python mais ceci renvoie un Pattern par opposition à une liste
```
# Trier par ordre croissant
>>> print(P[1, 3, 2, 0].sort())
P[0, 1, 2, 3]
```
```
# Trier par longueur d’accords
>>> print(P[(1, 2), (3,), (4, 5, 6, 7), (8, 9, 10)].sort(key=lambda x: len(x)))
P[P(3), P(1, 2), P(8, 9, 10), P(4, 5, 6, 7)]
```
**`.stutter(n=2)`**
Retourne un nouveau Pattern avec chaque valeur répétée n nombre de fois. Si n est un Pattern lui-même, alors chaque valeur est répétée par le nombre au même indice dans le Pattern donné.
```
# Bégaier chaque valeur 3 fois
>>> print(P[0, 1, 2, 3].stutter(3))
P[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]
```
```
# Bégayer toutes les autres valeurs de 2
>>> print(P[0, 1, 2, 3].stutter([1, 2]))
P[0, 1, 1, 2, 3, 3]
```
**`.arp(seq)`**
Retourne un nouveau Pattern avec chaque élément répété len(seq) fois et incrémenté par les valeurs de seq. Utile pour l’arpégiation.
```
# Bégayer de 2 et ajouter 4 à chaque seconde valeur.
>>> print(P[0, 1, 2, 3].arp([0, 4]))
P[0, 4, 1, 5, 2, 6, 3, 7]
```
**`.splice(seq, *seqs)`**
Prend un ou plusieurs Patterns pour le réunir avec le Pattern original. Le nouveau Pattern retourné est composé des valeurs des séquences originales et des données "splice" de manière alternée.
```
# Alternance entre deux séquences
>>> print(P[0, 1, 2, 3].splice([4, 5, 6, 7]))
P[0, 4, 1, 5, 2, 6, 3, 7]
```
```
# Alternance entre trois séquences
>>> print(P[0, 2, 4].splice([1, 3, 5], [6, 7, 8]))
P[0, 1, 6, 2, 3, 7, 4, 5, 8]
```
**`.invert()`**
Crée une version inversée du Pattern en soustrayant ses valeurs de la plus grande valeur du Pattern de sorte que la plus grande valeur du Pattern devient la plus petite (et vice versa) et que la différence entre les autres valeurs et le min/max est inversée :
```
>>> print(P[2, 5, 1, 11].invert())
P[10, 7, 11, 1]
```
```
# L’inversion d’une séquence linéaire
>>> print(P[0, 1, 2, 3].invert())
P[3, 2, 1, 0]
```
**`.shufflets(n = 4)`**
Retourne un nouveau Pattern qui contient le Pattern d’origine en tant que PGroup dans un ordre aléatoire de longueur n.
```
>>> print(P[0, 1, 2, 3].shufflets(3))
P[P(2, 3, 1, 0), P(3, 2, 0, 1), P(2, 1, 0, 3)]
```
**`.pivot(i)`**
Renvoie un nouveau Pattern qui est une version inversée de l’original, mais qui est ensuite tournée de sorte que l’élément à l’index i est toujours au même endroit.
```
# Le pivot à 2 maintiendra l’élément du milieu au même endroit
>>> print(P[5, 1, 6, 2, 3].pivot(2))
P[3, 2, 6, 1, 5]
```
**`.accum(n=None)`**
Retourne un Pattern qui est équivalent à la liste des sommes de ce Pattern jusqu’à cet indice (la première valeur est toujours 0). L’argument n spécifie la longueur du nouveau Pattern. Lorsque n est None, le nouveau Pattern prend la longueur de l’original.
```
# Accumulation d’une série de valeurs
>>> print(P[1, 2, 3, 4].accum())
P[0, 1, 3, 6]
```
```
# La nouvelle valeur peut être plus longue que l’original
>>> print(P[1, 2, 3, 4].accum(8))
P[0, 1, 3, 6, 10, 11, 13, 16]
```
**`.stretch(size)`**
Renvoie un Pattern qui est répété jusqu'à ce que la longueur soit égale à la taille.
```
# Etirer le Pattern
>>> print(P[1, 2, 3, 4].stretch(6))
P[1, 2, 3, 4, 1, 2]
```
```
# La taille peut être inférieure à la longueur de l'original
>>> print(P[1, 2, 3, 4].stretch(3))
P[1, 2, 3]
```
**`.trim(size)`**
Comme le stretch, mais la longueur ne peut pas dépasser la longueur du Pattern original.
```
# Taillez le patron à la longueur 3
>>> print(P[1, 2, 3, 4, 5].trim(3))
P[1, 2, 3]
```
```
# Les valeurs de taille supérieures à la longueur sont ignorées.
>>> print(P[1, 2, 3, 4, 5].trim(10))
P[1, 2, 3, 4, 5]
```
**`.ltrim(size)`**
Comme pour le trim, mais en enlevant les éléments du début du Pattern, et non de la fin.
```
>>> print(P[1, 2, 3, 4, 5].ltrim(3))
P[3, 4, 5]
```
**`.loop(n, func=None)`**
Répète le Pattern n fois. Utile pour enchaîner plusieurs Patterns. Les Patterns imbriqués sont pris en compte lors du bouclage. Une fonction peut être fournie en tant qu'argument secondaire facultatif, qui sera appliquée aux valeurs dans les boucles suivantes.
```
# Répéter le Pattern deux fois
>>> print(P[0, 1, 2, 3].loop(2))
P[0, 1, 2, 3, 0, 1, 2, 3]
```
```
# Répéter deux fois et enchaîner avec un autre Pattern
>>> print(P[0, 1, 2].loop(2) | P[7, 6])
P[0, 1, 2, 0, 1, 2, 7, 6]
```
```
# Le bouclage avec des Patterns imbriqués étend les Patterns imbriqués.
>>> print(P[0, [1, 2]].loop(2))
P[0, 1, 0, 2, 0, 1, 0, 2]
```
```
# Répétez le Pattern trois fois et ajoutez 7 aux boucles.
>>> print(P[0, 1, 2].loop(3, lambda x: x + 7))
P[0, 1, 2, 7, 8, 9, 14, 15, 16]
```
**`.duplicate(n)`**
Comme loop mais conserve les Patterns imbriqués de telle sorte que la première valeur de l'imbrication est utilisée lors de la première itération de la séquence dupliquée, etc.
```
# Conserver les valeurs imbriquées lors de la duplication
>>> print(P[0, [1, 2]].duplicate(2))
P[0, P[1, 2], 0, P[1, 2]]
```
**`.iter(n)`**
Comme loop mais ne prend pas en compte les Patterns imbriqués dans le calcul de la longueur.
```
>>> print(P[0, [1, 2]].iter(2))
P[0, 1, 0, 2]
```
**`.swap(n)`**
Intervertit la place des valeurs dans le Pattern. Lorsque n est égal à 2, les valeurs voisines sont permutées, lorsque n est égal à 3, les valeurs voisines de 1 sont permutées, et ainsi de suite.
```
# Échanger les valeurs les plus proches les unes des autres
>>> print(P[0, 1, 2, 3].swap(2))
P[1, 0, 3, 2]
```
```
# Permuter les valeurs séparées d'un pas
>>> print(P[0, 1, 2, 3, 4, 5].swap(3))
P[2, 1, 0, 5, 4, 3]
```
**`.rotate(n)`**
Renvoie un Pattern avec les valeurs du Pattern original décalées vers la gauche dans l'ordre par n nombre de places. Les nombres négatifs décalent les valeurs vers la droite.
```
# Rotation à gauche de 1
>>> print(P[0, 1, 2, 3].rotate(1))
P[1, 2, 3, 0]
```
```
# Rotation à droite de 1
>>> print(P[0, 1, 2, 3].rotate(-1))
P[3, 0, 1, 2]
```
**`.sample(n)`**
Renvoie un Pattern de longueur n avec des valeurs sélectionnées de manière aléatoire dans le Pattern original.
```
>>> print(P[0, 1, 2, 3].sample(3))
P[3, 2, 0]
```
**`.palindrome()`**
Ajoute l'inverse d'un Pattern à lui-même de manière à créer un palindrome de nombres.
```
>>> print(P[0, 1, 2, 3].palindrome())
P[0, 1, 2, 3, 3, 2, 1, 0]
```
**`.alt(seq)`**
Remplace le Pattern par celui de seq. Utile si vous souhaitez utiliser un autre Pattern et l'attribuer à l'aide de la méthode every.
```
# Remplacer le Pattern
>>> print(P[0, 1, 2, 3].alt([4, 5]))
P[4, 5]
```
```
# Utile lorsqu'il est utilisé avec un Player
>>> p1 >> pads([0, 1, 2, 3]).every(6, "alt", P[4, 5, 6, 7])
```
**`.norm()`**
Renvoie un Pattern dont toutes les valeurs ont été normalisées de sorte que chaque valeur du nouveau Pattern soit comprise entre 0 et 1.
```
>>> print(P[0, 1, 2, 3].norm())
P[0.0, 0.3333333333333333, 0.6666666666666666, 1.0]
```
**`.undup()`**
Supprime toutes les valeurs dupliquées consécutives afin qu'il n'y ait pas de valeurs répétées dans le Pattern.
```
>>> print(P[0, 1, 1, 1, 2, 2, 3].undup())
P[0, 1, 2, 3]
```
**`.limit(func, value)`**
Renvoie un nouveau Pattern généré en ajoutant des valeurs à l'original jusqu'à ce que func(Pattern) dépasse la valeur. L'argument func doit être une fonction valide, comme len ou sum.
```
# Créer un Pattern dont la somme n'est pas supérieure à 7
>>> print(P[0, 1, 2, 3].limit(sum, 7))
P[0, 1, 2, 3, 0, 1]
```
**`.replace(sub, repl)`**
Renvoie un nouveau Pattern dont les valeurs sont égales à sub et remplacées par repl.
```
# Replacer la valeur 0 par 4
>>> print(P[0, 1, 2, 3].replace(0, 4))
P[4, 1, 2, 3]
```
```
# Remplace également les valeurs dans les groupes et les Patterns imbriqués
>>> print(P[0, (1, 0), 2, [3, 0]].replace(0, 4))
P[4, P(1, 4), 2, P[3, 4]]
```
**`.submap(mapping)`**
Similaire à replace mais prend un dictionnaire de valeurs sub to repl pour remplacer plusieurs éléments.
```
# Remplacer 0 par 4 et 1 par 5
>>> print(P[0, 1, 2, 3].submap({0: 4, 1: 5}))
P[4, 5, 2, 3]
```
```
# Fonctionne également avec les valeurs imbriquées, etc.
>>> print(P[0, (1, 0), 2, [3, 0]].submap({0: 4, 1: 5}))
P[4, P(5, 4), 2, P[3, 4]]
```
**`.layer(method, *args, kwargs)`**
Zippe le Pattern original avec lui-même mais avec une méthode appelée à lui-même, qui peut être un nom de chaîne d'une méthode de Pattern valide (similaire à Player.every) ou une fonction valide (voir l'exemple ci-dessous).
```
# Pattern zippé avec son envers
>>> print(P[0, 1, 2, 3].layer("reverse"))
P[P(0, 3), P(1, 2), P(2, 1), P(3, 0)]
# Pattern zippé avec swap(2) appelé sur lui
>>> print(P[0, 1, 2, 3].layer("swap", 2))
P[P(0, 1), P(1, 0), P(2, 3), P(3, 2)]
# Zippé avec une fonction multipliant les valeurs par 2
>>> print(P[0, 1, 2, 3].layer(lambda x: x + 2 ))
P[P(0, 2), P(1, 3), P(2, 4), P(3, 5)]
# Ceci est équivalent à ce qui suit :
>>> print(P[0, 1, 2, 3].layer("__add__", 2))
P[P(0, 2), P(1, 3), P(2, 4), P(3, 5)]
```
**`.every(n, method, *args, kwargs)`**
Répète le Pattern original n fois et applique la méthode Pattern (spécifiée sous forme de chaîne) à la dernière répétition avec les args et kwargs fournis.
```
# Inverser le motif à la troisième répétition
>>> print(P[0, 1, 2, 3].every(3, "mirror"))
P[0, 1, 2, 3, 0, 1, 2, 3, 3, 2, 1, 0]
```
**`.map(callable)`**
Renvoie un nouveau Pattern avec l'argument callable appelé sur chaque élément du Pattern.
```
# Mettre tous les nombres pairs à 0
>>> print(P[0, 1, 2, 3].map(lambda x: 0 if x % 2 == 0 else x))
P[0, 1, 0, 3]
```
**`.extend(seq)`**
Étend le Pattern avec seq en place, c'est-à-dire qu'il renvoie None au lieu d'un nouveau Pattern. Cette méthode est plus efficace que la méthode concat pour combiner plusieurs séquences en un Pattern.
```
>>> pat = P[0, 1, 2, 3]
>>> pat.extend([4, (5, 6), 7])
>>> print(pat)
P[0, 1, 2, 3, 4, P(5, 6), 7]
```
**`.concat(seq)`**
Renvoie un nouveau Pattern avec le contenu de seq ajouté à la fin du Pattern original. La méthode spéciale __or__ (qui utilise la syntaxe |) fait également appel à cette méthode.
```
# Ces deux lignes sont equivalentes
>>> print(P[0, 1, 2, 3].concat([4, 5, 6]))
P[0, 1, 2, 3, 4, 5, 6]
>>> print(P[0, 1, 2, 3] | [4, 5, 6])
P[0, 1, 2, 3, 4, 5, 6]
```
Cette méthode est utile pour combiner plusieurs Patterns à l'intérieur d'un objet Player, mais elle n'est pas efficace pour créer de grands Patterns à l'aide d'une boucle. Essayez d'exécuter les deux blocs de code et voyez la différence.
```
# Ce n'est pas efficace
pat = P[0, 1, 2]
for n in range(20000):
pat = pat | [0, 1, 2]
print(pat)
```
```
# C'est efficace
pat = P[0, 1, 2]
for n in range(20000):
pat.extend([0,1,2])
print(pat)
```
**`.zip(seq)`**
Le "zippage" est le processus qui consiste à combiner deux séquences en une seule où chaque élément est un groupe qui contient les éléments de chaque séquence au même indice. Si les séquences sont de longueurs différentes, elles sont zippées jusqu'à la longueur du plus petit multiple commun des deux longueurs.
```
>>> print(P[0, 1, 2, 3].zip([4, 5]))
P[P(0, 4), P(1, 5), P(2, 4), P(3, 5)]
```
**`.offadd(value, dur=0.5)`**
Ajoute une valeur au Pattern, le zippe avec l'original et retarde la valeur zippée de dur à l'aide de la classe PGroupPrime.
```
>>> print(P[0, 1, 2, 3].offadd(2, 0.5))
P[P^(0, 2), P^(1, 3), P^(2, 4), P^(3, 5)]
```
**`.offmul(value, dur=0.5)`**
Similaire à offadd mais multiplie les valeurs au lieu de les additionner.
```
>>> print(P[0, 1, 2, 3].offmul(2, 0.5))
P[P^(0, 0), P^(1, 2), P^(2, 4), P^(3, 6)]
```
**`.offlayer(method, dur=0.5, *args, kwargs)`**
Similaire à offadd et offmul mais utilise une méthode ou une fonction spécifiée par l'utilisateur au lieu de l'addition/multiplication. L'argument méthode doit être un nom valide d'une méthode Pattern sous forme de chaîne de caractères ou d'un objet appelable tel qu'une fonction. Tout argument supplémentaire ou mot-clé est fourni après la durée pour retarder le calque, c'est pourquoi une durée doit être fournie si des arguments sont fournis dans le cadre de *args.
```
# Calque avec la méthode "rotate" avec les valeurs par défaut
>>> print(P[0, 1, 2, 3].offlayer("rotate"))
P[P^(0, 1), P^(1, 2), P^(2, 3), P^(3, 0)]
# Le calque suivant n'est pas réalisé avec .rotate(2) mais avec dur = 2
>>> print(P[0, 1, 2, 3].offlayer("rotate", 2))
P[P^(0, 1), P^(1, 2), P^(2, 3), P^(3, 0)]
# La durée doit être spécifiée pour fournir 2 à la rotation
>>> print(P[0, 1, 2, 3].offlayer("rotate", 0.5, 2))
P[P^(0, 2), P^(1, 3), P^(2, 0), P^(3, 1)
# On peut également utiliser des fonctions à la place des noms de méthodes.
>>> print(P[0, 1, 2, 3].offlayer(lambda x: (x * 2) + 1))
P[P^(0, 1), P^(1, 3), P^(2, 5), P^(3, 7)]
```
**`.amen()`**
Reproduit le rythme et l'ordre du célèbre amen break sur la base d'une séquence kick-drum, hi-hat, snare-drum, hi-hat. Écoutez l'exemple ci-dessous :
```
>>> p1 >> play("x-o-").every(8, "amen")
```
### 3.2. Pattern Functions
Introduction
Il y a plusieurs fonctions qui génèrent un Pattern de valeurs pour nous permettre de réaliser des choses utiles dans FoxDot, comme des rythmes et des mélodies. Cette page est une liste de fonctions de Patterns accompagnées de descriptions et d'exemples.
Utilisées comme arguments d'entrée pour les Players FoxDot, elles peuvent être traitées comme des patterns eux-mêmes et leurs méthodes peuvent être appliquées directement, par exemple PDur(3, 8).reverse(). Vous pouvez également remplacer n'importe quel argument d'entrée par un pattern ou une TimeVar pour créer un pattern étendu ou une Pvar. Voyons quelques exemples :
```
# Fonction de base du Pattern
>>> PDur(3, 8)
P[0.75, 0.75, 0.5]
```
```
# Utiliser une liste/Pattern pour réunir deux patterns utilisant la même fonction
>>> PDur([3, 2], [8, 4])
P[0.75, 0.75, 0.5, 0.5, 0.5]
```
```
# Utiliser une TimeVar pour créer une Pvar
>>> PDur(var([3, 5]), 8)
P[0.75, 0.75, 0.5] # Equal to PDur(3,8)
P[0.5, 0.25, 0.5, 0.25, 0.5] # Equal to PDur(5,8) after 4 beats
```
**Liste des Fonctions de Pattern**
**`PStep(n, value, default=0)`**
Crée un Pattern de longueur n dont le dernier élément est une valeur. Toutes les autres valeurs sont définies par défaut.
```
>>> PStep(5, 4)
P[0, 0, 0, 0, 4]
>>> PStep(4, "o", "x")
P["x", "x", "x", "o"]
```
**`PSum(n, total, lim=0.125)`**
Renvoie un Pattern de longueur n dont la somme est égale au total et dont chaque valeur est à peu près égale. Toutes les valeurs sont divisibles par lim, qui est également la plus petite valeur possible pour chaque élément.
```
>>> PSum(5, 4)
P[1.0, 0.75, 0.75, 0.75, 0.75]
>>> PSum(3, 2)
P[0.75, 0.75, 0.5]
```
**`PRange(start, stop=None, step=None)`**
Renvoie un Pattern rempli de la série du début à la fin (non inclus) avec des incréments de pas (1 par défaut). Si stop est omis, la série commence à 0 et se termine à start.
```
>>> PRange(5)
P[0, 1, 2, 3, 4]
>>> PRange(2, 7)
P[2, 3, 4, 5, 6]
>>> PRange(4, 14, 2)
P[4, 6, 8, 10, 12]
```
**`PTri(start, stop=None, step=None)`**
Renvoie un Pattern rempli de la série du début à la fin et inversement (non inclus) avec des incréments de pas (1 par défaut). Si stop est omis, la série commence à 0 et culmine à start.
```
>>> PTri(5)
P[0, 1, 2, 3, 4, 3, 2, 1]
>>> PTri(2, 7)
P[2, 3, 4, 5, 6, 5, 4, 3]
>>> PTri(4, 14, 2)
P[4, 6, 8, 10, 12, 10, 8, 6]
```
**`PSine(n=16)`**
Renvoie un Pattern avec n valeurs tirées d'une fonction d'onde sinusoïdale de base à des distances égales.
```
>>> PSine(16)
P[0.0, 0.3826834323650898, 0.7071067811865476, 0.9238795325112867, 1.0, 0.9238795325112867, 0.7071067811865476, 0.3826834323650899, 1.2246467991473532e-16, -0.38268343236508967, -0.7071067811865475, -0.9238795325112865, -1.0, -0.9238795325112866, -0.7071067811865477, -0.3826834323650904]
```
**`PEuclid(n, k)`**
Utilise l'algorithme euclidien pour créer un Pattern de longueur k avec n nombre d'impulsions réparties aussi équitablement que possible dans l'ensemble. Les impulsions sont représentées par un 1.
```
>>> PEuclid(3, 8)
P[1, 0, 0, 1, 0, 0, 1, 0]
```
**`PEuclid2(n, k, lo, hi)`**
Même fonction que PEuclid mais remplace les 1 par hi et les 0 par lo.
```
>>> PEuclid2(3, 8, "x", "o")
P["o", "x", "x", "o", "x", "x", "o", "x"]
```
**`PDur(n, k, start=0, dur=0.25)`**
Renvoie la sortie de PEuclid sous la forme d'une série de durées dont chaque élément est un pas de durée dur. Le mot-clé start spécifie l'indice de départ du motif.
```
>>> PDur(3, 8)
P[0.75, 0.75, 0.5]
>>> PDur(3, 8, 1)
P[0.75, 0.5, 0.75]
>>> PDur(3, 8, 1, 0.5)
P[1.5, 1.0, 1.5]
```
**`PBern(size=16, ratio=0.5)`**
Renvoie un Pattern de longueur variable rempli d'une sélection aléatoire de 1 et de 0 basée sur la valeur du ratio, connue sous le nom de séquence de Bernoulli.
```
>>> PBern(6)
P[1, 0, 0, 1, 1, 0]
>>> PBern(6, 0.75)
P[0, 1, 1, 1, 1, 0]
```
**`PBeat(string, start=0, dur=0.5)`**
Renvoie un Pattern de durées basé sur une chaîne d'entrée où les espaces non-blancs indiquent une impulsion.
```
>>> PBeat("x xxx x")
P[1, 0.5, 0.5, 1, 0.5]
>>> PBeat("x xxx x", start=1, dur=1/4)
P[0.25, 0.25, 0.5, 0.25, 0.5]
```
**`PSq(a=1, b=2, c=3)`**
Renvoie un motif de nombres carrés compris entre a et (a + c) - 1.
```
>>> PSq(1, 2, 3)
P[1, 4, 9]
>>> PSq(2, 2, 5)
P[4, 9, 16, 25, 36]
```
### 3.3. Generateurs de Pattern
Introduction
Nous savons que les patterns sont de longueur fixe et qu'ils peuvent être générés à partir d'une fonction. Cependant, il est parfois utile de disposer de patterns de longueur infinie, par exemple lors de la génération de nombres aléatoires. C'est là qu'interviennent les générateurs de patterns. Similaires aux générateurs Python où toutes les valeurs ne sont pas gardées en mémoire en même temps, sauf que les générateurs Python ont généralement une fin - les générateurs de patterns de FoxDot n'en ont pas ! Examinons d'abord le générateur de patterns PRand, puis vous trouverez ci-dessous une liste de tous les générateurs de patterns disponibles avec quelques exemples de code.
Le générateur PRand nous fournit une quantité infinie de nombres aléatoires entre une valeur basse et une valeur haute données ou à partir d'un ensemble de valeurs données dans une liste. Nous ne pouvons pas simplement imprimer l'objet comme nous le faisons avec les Patterns, parce que nous ne pouvons pas imprimer une liste infinie de valeurs qui n'ont même pas encore été calculées :
```
>>> my_gen = PRand(0, 10)
>>> print(my_gen)
PRand(0, 10)
```
Pour connaître les valeurs contenues dans my_gen, nous devons y accéder à l'aide de l'indexation ou du découpage.
```
>>> my_gen[0]
4
```
```
>>> my_gen[1]
6
```
```
>>> my_gen[:10]
P[4, 6, 1, 8, 3, 8, 8, 8, 0, 1]
```
L'indexation du PRand nous donne des valeurs individuelles et le découpage renvoie un Pattern de valeurs. Remarquez que les deux premières valeurs sont 4 et 6. Un générateur de pattern renverra (presque) toujours la même valeur pour un index donné. Vous pouvez appliquer n'importe quelle méthode de pattern à l'objet Pattern que vous obtenez en retour du découpage.
Vous pouvez également effectuer des opérations mathématiques de base sur les générateurs de patterns :
```
>>> my_gen2 = my_gen * 2
>>> print(my_gen2)
PRand(Mul 2)
>>> my_gen2[:10]
P[8, 12, 2, 16, 6, 16, 16, 16, 0, 2]
```
L'impression du nouveau PRand nous en apprend un peu plus, mais pas grand-chose - juste qu'il s'agit d'un objet PRand existant et qu'il a été multiplié par 2. Vous pouvez également utiliser vos propres fonctions personnalisées et les appliquer aux générateurs de Patterns de la même manière que vous pouvez les appliquer aux Patterns, par l'intermédiaire de la méthode transform. Créons une fonction qui renvoie 5 lorsque l'entrée est impaire et 3 lorsque l'entrée est paire :
```python
>>> def odd_test(num):
... return 5 if num % 2 == 1 else 3
>>> my_odd_gen = odd_test(PRand(0, 10))
>>> print(my_odd_gen)
```
Le num % 2 renvoie un nouveau PRand et non un nombre, de sorte que lorsqu'on lui demande "est-ce égal à 1 ?", il renvoie toujours False et, par conséquent, 3. Pour appliquer réellement la fonction aux valeurs de notre générateur de Patterns, nous devons utiliser la méthode transform et lui fournir la fonction.
```python
>>> my_odd_gen = PRand(0, 10).transform(odd_test)
>>> print(my_odd_gen)
PRand(lambda None)
>>> print(my_odd_gen[:10])
P[3, 3, 3, 5, 3, 3, 5, 3, 5, 3]
```
L'impression du nouveau générateur de Pattern montre que nous avons un nouveau PRand qui a été transformé (le lambda) sans valeur d'entrée. En le découpant, on obtient un Pattern de 3 et de 5, comme on s'y attendait.
Que se passe-t-il si nous ne stockons pas nos générateurs de Patterns dans une variable et que nous les imprimons ?
```
>>> print(PRand(0, 10)[:10])
P[8, 5, 6, 0, 2, 2, 7, 3, 4, 4]
>>> print(PRand(0, 10)[:10])
P[1, 0, 0, 8, 0, 3, 4, 4, 2, 9]
```
Nous obtenons une liste de valeurs différente. C'est parce que nous initialisons un nouveau PRand à chaque fois que nous exécutons le code. Cela devient un peu ennuyeux, en particulier lorsque vous utilisez FoxDot, car vous fournissez souvent des objets Pattern aux Générateurs de Patterns de cette manière et ne les stockez pas dans une variable d'abord. Si vous voulez être sûr d'obtenir les mêmes valeurs de votre générateur de patterns, vous pouvez spécifier le seed, qui - lorsqu'il est définie sur le même nombre - force le générateur de nombres aléatoires d'un ordinateur à générer le même ensemble de nombres.
```
>>> print(PRand(0, 10, seed=1)[:10])
P[2, 9, 1, 4, 1, 7, 7, 7, 10, 6]
>>> print(PRand(0, 10, seed=1)[:10])
P[2, 9, 1, 4, 1, 7, 7, 7, 10, 6]
```
**Types of Pattern Generators**
**`PRand(lo, hi, seed=None) / PRand([values])`**
Renvoie une série d'entiers aléatoires compris entre lo et hi inclus. Si hi est omis, la plage est comprise entre 0 et lo. Une liste de valeurs peut être fournie à la place de l'intervalle et PRand renverra une série de valeurs choisies au hasard dans la liste.
```
>>> PRand(5, 10)[:10]
P[9, 10, 7, 10, 5, 10, 6, 5, 5, 7]
>>> PRand(5)[:10]
P[5, 4, 5, 5, 4, 1, 5, 5, 2, 0]
>>> PRand([1, 2, 3])[:10]
P[3, 2, 2, 2, 1, 3, 3, 2, 2, 1]
```
**`PxRand(lo, hi) / PxRand([values])`**
Identique à PRand mais les éléments ne seront pas répétés.
```
>>> PxRand(5, 10)[:10]
P[6, 10, 8, 6, 9, 6, 9, 6, 10, 7]
>>> PxRand(5)[:10]
P[4, 3, 2, 1, 4, 1, 0, 5, 2, 3]
>>> PxRand([1, 2, 3])[:10]
P[2, 3, 2, 1, 3, 2, 3, 1, 3, 2]
```
**`PwRand([values], [weights])`**
Utilise une liste de weights pour indiquer la fréquence de sélection des éléments ayant le même indice dans la liste des valeurs. Un weights de 2 signifie que l'élément a deux fois plus de chances d'être choisi qu'un élément ayant un weights de 1.
```
>>> PwRand([0, 1], [1, 2])[:10]
P[1, 0, 0, 1, 1, 1, 1, 0, 1, 1]
>>> PwRand([0, 1, 2], [1, 2, 3])[:10]
P[1, 2, 2, 2, 2, 2, 0, 2, 2, 1]
```
**`PWhite(lo, hi)`**
Renvoie des nombres aléatoires en virgule flottante compris entre lo et hi.
```
>>> PWhite(0,5)[:10]
P[4.19058560392058, 2.776022501076935, 1.0643923909231003, 1.638195673989835, 0.8591848958936567, 2.461472231869311, 3.3546237751457335, 1.1854918335554943, 1.8310777165408907, 1.1767145868610336]
```
**`PChain(mapping_dictionary)`**
Basé sur une simple chaîne de Markov utilisant des probabilités égales. Prend un dictionnaire d'éléments ; des états et des futurs états possibles. Chaque état futur a une probabilité égale d'être choisi. Si un état futur possible n'est pas valide, une KeyError sera levée.
```
>>> markov_chain = {
... 0: [3, 4],
... 3: [0, 4, 5],
... 4: [3, 0],
... 5: [3, 4],
... }
>>> PChain(markov_chain)[:10]
P[3, 0, 4, 0, 4, 3, 5, 4, 3, 5]
```
**`PWalk(max = 7, step = 1, start = 0)`**
Renvoie une série d'entiers dont chaque élément est éloigné d'une taille de pas et dont les valeurs sont comprises dans l'intervalle +/- max. Le premier élément peut être choisi en utilisant start.
```
>>> PWalk()[:10]
P[0, 1, 2, 1, 2, 3, 2, 3, 4, 5]
>>> PWalk(20, 5, 5)[:10]
P[5, 10, 15, 20, 15, 20, 15, 20, 15, 20]
```
**`PDelta(deltas, start = 0)`**
Prend une liste de deltas (petits incréments/décréments) et renvoie la série de nombres générée par l'addition de ces valeurs en séquence. L'utilisation d'un seul nombre positif créera une série infiniment croissante. Définissez la valeur de départ à l'aide de start.
```
>>> PDelta([0.1, 0.5, -0.3])[:10]
P[0, 0.1, 0.6, 0.3, 0.4, 0.9, 0.6, 0.7, 1.2, 0.9]
>>> PDelta([0.5])[:10]
P[0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]
>>> PDelta([0.5, -0.5], start=1)[:10]
P[1, 1.5, 1.0, 1.5, 1.0, 1.5, 1.0, 1.5, 1.0, 1.5]
```
**`PSquare()`**
Renvoie la série de nombres carrés.
```
>>> PSquare()[:10]
P[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> PSquare()[10:20] % 7
P[2, 2, 4, 1, 0, 1, 4, 2, 2, 4]
```
**`PIndex()`**
Renvoie la série de nombres entiers.
```
>>> PIndex()[:10]
P[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```
**`PFibMod()`**
Renvoie la séquence de Fibonacci.
```
>>> PFibMod()[:10]
P[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
```
**`PZ12(tokens = [0, 1], p = [1, 0.5])`**
Implémentation simple de l'algorithme Z12 pour des nombres aléatoires prédéterminés. L'utilisation d'une valeur irrationnelle pour p entraîne toutefois un ordre de valeurs non déterminé. Expérimental - ne fonctionne qu'avec 2 valeurs.
```
>>> PZ12([0, 1], [1, 0.5])[:15]
P[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]
>>> PZ12([0, 1], [1, math.sqrt(2)])[:15]
P[1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1]
```
### 3.4. Pgroups
**Basics**
Alors que les patterns sont une liste de valeurs qui sont jouées en séquence, les PGroups sont des groupes de valeurs qui sont jouées en même temps. Chaque fois que vous utilisez un tuple dans un lecteur ou un pattern, il est automatiquement converti en un PGroup, qui, comme les patterns, est désigné par le P majuscule au début du tuple :
```
>>> print(P[0, 1, 2, (3, 4, 5)])
P[0, 1, 2, P(3, 4, 5)]
```
De nombreuses méthodes de Patterns peuvent également être appliquées aux groupes de PG, telles que le miroir et le mélange. Cette relation s'étend également au "laçage" des groupes de PG ; l'utilisation d'une liste ou d'un Pattern comme valeur dans un groupe de PG créera un Pattern de groupes de PG utilisant la séquence de valeurs à son tour, de la manière suivante :
```
# Laçage d'un groupe de PG
>>> print(P(0, 1, [2, 3]))
P[P(0, 1, 2), P(0, 1, 3)]
# Laçage d'un groupe de PG à l'intérieur d'un Pattern
>>> print(P[0, 1, P(2, 3, [4, 5])])
P[0, 1, P[P(2, 3, 4), P(2, 3, 5)]]
# La mise en place d'un groupe de PG avec plusieurs listes
>>> print(P(0, [1, 2], [3, 4, 5]))
P[P(0, 1, 3), P(0, 2, 4), P(0, 1, 5), P(0, 2, 3), P(0, 1, 4), P(0, 2, 5)]
```
**PGroups étendus**
Un groupe PG étendu, également connu sous le nom de " PGroup Prime ", contient des informations supplémentaires sur la manière dont les valeurs du groupe PG sont utilisées par un objet Player. L'insertion d'un opérateur mathématique, tel que "+", activera un type de comportement lorsqu'il sera utilisé dans un player. Ces opérateurs modifient généralement les notes, qui ne sont plus jouées simultanément, mais réparties sur une certaine période de temps. Voici une liste des PGroups étendus et quelques exemples de code :
**PGroup-Star**
**`P*(x, y, z, ...)`**
Joue le contenu du PGroup réparti de manière égale sur la valeur courante du Player.
```
# Joue 3 notes sur 2 temps d'une durée de 2/3 temps chacune
p1 >> pluck(P*(0, 2, 4), dur=2)
# Joue les notes dans le canal stéréo opposé toutes les 4 notes
p1 >> pluck(dur=1/2, pan=PStep(4, P*(-1, 1)))
```
**PGroup-Plus**
**`P+(x, y, z, ...)`**
Joue le contenu du PGroup réparti de manière égale sur la valeur actuelle du sus du joueur, quel que soit le dur.
```
# Jouer 4 notes sur un sustain à 1 temps
p1 >> pluck(P+(0, 2, 4, 6), dur=PDur(3,8), sus=1)
# Varier le sustain pour entendre la différence
p1 >> pluck([0, 2, P+(0, 2, 4)], dur=PDur(3,8)*2, sus=var([0.75, 1.5, 3]))
```
**PGroup-DoubleStar**
**`P(x, y, z, ...)`**
Identique à PGroup-Star mais randomise l'ordre dans lequel les notes sont jouées.
```
# Arpège l'accord dans un ordre aléatoire
p1 >> pluck(P[0, 1, 2, P**(3, 5, 7, 9)])
```
**PGroup-Div**
**`P/(x, y, z, ...)`**
Identique à PGroup-Start mais ne retarde les notes que toutes les deux fois que le groupe est joué.
```
# Arpège l'accord toutes les deux boucles
p1 >> pluck(P[0, 1, 2, P/(3, 5, 7, 9)])
```
**PGroup-Pow**
**`P^(x, y, z, ..., dur)`**
Utilise la dernière valeur du groupe de PG pour définir le délai entre chaque note au lieu d'utiliser la valeur dur ou sus d'un lecteur.
```
# force de délai de 0,5 temps par note
p1 >> pluck([0, 2, P^(3, 5, 7, 0.5)], dur=1, sus=4)
```