# 4 - TimeVars
### 4.1. Timevar Basics
Introduction
La musique est quelque chose qui se produit dans le temps - changements de hauteurs, de durées, d'amplitudes et de sons - et une FoxDot TimeVar (Time-dependent Variable) vous permet de créer des valeurs qui changent dans le temps. Voici comment cela fonctionne : vous créez un objet var et vous lui donnez deux entrées : une liste de valeurs que vous voulez qu'il contienne, et une liste de durées (en battements) pour lesquelles ces valeurs doivent être conservées. Par exemple, prenons cet objet TimeVar ;
```
var([0, 1, 2, 3], [8, 4, 2, 2])
```
La valeur 0 sera maintenue pendant 8 temps, puis la valeur 1 pendant 4 temps, puis les valeurs 2 et 3 pendant 2 temps chacune. Ensuite, il reviendra au début du cycle et maintiendra à nouveau la valeur 0 pendant 8 temps. Les principes de base sont également expliqués dans la section Mise en route.
Vous pouvez effectuer n'importe quelle opération mathématique sur une TimeVar et sa valeur changera toujours au fil du temps. Ainsi, si vous doublez la TimeVar définie ci-dessus de la manière suivante :
```
>>> a = var([0, 1, 2, 3], [8, 4, 2, 2])
>>> b = a * 2
>>> print(a, b)
(1, 2)
```
Ensuite, lorsque 8 temps se seront écoulés et que a aura une valeur de 1, b aura une valeur de 2. Après 4 temps supplémentaires, a aura une valeur de 2 et b aura une valeur de 4. Vous pouvez ajouter une liste ou un tuple à une TimeVar pour obtenir un motif de valeurs de TimeVar ; cela peut être utile pour transformer des mélodies ou des rythmes entiers pour des périodes de temps :
```
p1 >> pluck([0, 1, 4] + var([0, 4]), dur=PDur(3,8) * var([1, 2], 8))
```
Vous remarquerez peut-être certaines choses à propos de la durée des TimeVars ci-dessus. Tout d'abord, aucune durée n'a été fournie à la première TimeVar - par défaut, la durée est égale à la longueur d'une mesure (généralement 4 battements) par valeur. Deuxièmement, une seule valeur a été fournie à la TimeVar, var([1, 2], 8), au lieu d'une liste. Dans ce cas, cette durée est utilisée pour toutes les valeurs du TimeVar (c'est-à-dire 8 battements chacune).
Autres caractéristiques
**TimeVars imbriquées**
Une caractéristique utile des TimeVars est qu'il est possible de les imbriquer les unes dans les autres pour subdiviser la valeur. Cette fonction est utile pour créer des TimeVars plus complexes et s'explique mieux par une démonstration triviale. Supposons que vous ayez créé le TimeVar suivant au cours d'une session FoxDot :
```
var([0, 1, 2, 3], [8, 4, 2, 2])
```
Je souhaite maintenant modifier la dernière valeur sur le dernier temps du deuxième cycle du TimeVar. Voici la façon manuelle de procéder :
```
var([0, 1, 2, 3, 0, 1, 2, 3, 5], [8, 4, 2, 2, 8, 4, 2, 1, 1])
```
C'est trop de saisie et cela commence à faire désordre. Nous pourrions également ajouter un autre TimeVar à notre original :
```
var([0, 1, 2, 3], [8, 4, 2, 2]) + var([0, 2], [15, 1])
```
Ce n'est pas mal, mais il faut connaître la longueur du cycle et le nombre qu'il faut ajouter à notre valeur originale pour obtenir la valeur désirée. Voici comment procéder en utilisant des TimeVars imbriquées. Tout d'abord, utilisez une liste imbriquée pour alterner la dernière valeur du cycle, puis utilisez une TimeVar avec cette liste imbriquée comme suit :
```
var([0, 1, 2, [3, var([3, 5], 1)]), [8, 4, 2, 2])
```
Il s'agit d'un moyen simple de créer des TimeVars plus complexes sans avoir à tenir compte des durées pour chaque valeur utilisée. Il n'est pas toujours nécessaire d'utiliser la liste imbriquée, mais elle s'est avérée utile dans le cadre de cet exemple. Des exemples plus pratiques de cette technique seront présentés dans d'autres parties de la documentation.
**Named TimeVars**
Il peut parfois être utile d'utiliser une TimeVar à plusieurs endroits dans votre code. Il peut s'agir, par exemple, de partager une séquence d'accords entre des Player Objects. Voici comment procéder :
```
chords = var([0, 4, 5, 3])
p1 >> blip(chords + (0, 2, 4), dur=4)
p2 >> pasha(chords + [0, 2, 3, 4, 7, 9], dur=1/4)
```
Que se passe-t-il lorsque vous souhaitez modifier la séquence d'accords ? Essayez d'exécuter ce code dans FoxDot. Si vous changez la valeur des accords, la musique ne change pas tant que vous n'avez pas ré-exécuté les lignes de code correspondant à p1 et p2. Il existe un moyen de contourner ce problème, qui consiste à utiliser des TimeVars nommés. Pour ce faire, tout ce que vous avez à faire est de commencer le nom de votre variable par var. comme suit :
```
var.chords = var([0, 4, 5, 3])
```
Ensuite, faites-y référence en utilisant le même nom de manière à ce que votre code ressemble à ceci :
```
p1 >> blip(var.chords + (0, 2, 4), dur=4, oct=5)
p2 >> pasha(var.chords + [0, 2, 3, 4, 7, 9], dur=1/4)
```
Maintenant, FoxDot garde la trace de ce nom de variable et met à jour tous les objets du lecteur qui l'utilisent lorsqu'il est mis à jour. Ainsi, si vous exécutez votre nouveau code et que vous changez la valeur de var.chords en quelque chose d'autre :
```
var.chords = var([0, 2, 3, 4])
```
La musique change instantanément. Superbe !
**Transformations**
Vous avez déjà vu que vous pouvez effectuer des transformations simples d'un TimeVar en effectuant des opérations arithmétiques sur eux, par exemple l'addition et la multiplication. Vous pouvez également transformer des valeurs en utilisant vos propres fonctions prédéfinies ou des fonctions lambda en ligne (si vous êtes un utilisateur avancé de Python). Cependant, vous ne pouvez pas vous contenter d'utiliser my_function(my_timevar), sauf si la fonction n'effectue que des opérations mathématiques de base. Si elle effectue quelque chose de plus complexe, comme l'utilisation d'instructions if-else, vous devez connaître la méthode de transformation TimeVar. Prenons un exemple :
Voici ma fonction arbitraire qui renvoie 5 si son entrée est impaire et 3 si son entrée est paire :
```
def odd_test(num):
return 5 if num % 2 == 1 else 3
And here is my TimeVar:
my_var = var([0, 1, 2, 3])
```
Si j'appelle `odd_test(my_var)` alors il retournera sa sortie (3 ou 5) basée sur la valeur actuelle de my_var et la valeur restera la même, même si `my_var` change. Essayez vous-même pour voir. Pour être sûr de toujours renvoyer la bonne valeur avec `my_var` en entrée, vous devez appeler la méthode transform, qui prend un objet appelable, généralement une fonction, en entrée. Notez que cette fonction appelable ne peut prendre qu'un seul argument en entrée.
`my_output = my_var.transform(odd_test)`
Maintenant my_output sera toujours 5 lorsque my_var est impaire et 3 lorsqu'elle est paire. Si vous êtes un utilisateur avancé de Python, vous pouvez même créer une classe avec une méthode `__call__()` pour créer un objet appelable et l'utiliser comme entrée pour la méthode transform pour conserver une sorte d'état entre les transformations, par exemple compter le nombre de fois qu'elle a été appelée et changer la valeur toutes les 10 fois.
### 4.2. Types de Timevar
Introduction
Cette section présente les différentes incarnations des TimeVars disponibles dans FoxDot. Jusqu'à présent, nous avons vu le TimeVar de base, qui conserve une valeur pendant un certain nombre de temps, puis passe à la valeur suivante. Il existe également un certain nombre de TimeVars qui changent progressivement entre ces valeurs, ainsi que des TimeVars représentant des objets Pattern entiers. Voyons maintenant ce qu'il en est :
**Valeurs temporelles à changement progressif**
Il existe trois types de TimeVars à changement progressif qui (sans surprise) passent progressivement d'une valeur à l'autre au fil du temps au lieu de changer instantanément une fois que la durée assignée s'est écoulée. Il s'agit de linvar, sinvar et expvar, et leurs noms sont liés à la manière dont les valeurs changent au fil du temps, comme décrit ci-dessous :
**`linvar(values, dur)`**
Ce TimeVar passe d'une valeur à l'autre sur une échelle linéaire, d'où son nom linvar. Comme tous les TimeVars, il prend en entrée une série de valeurs et de durées. Si dur est omis, il utilisera la valeur du mètre courant.
```
>>> my_linvar = linvar([0, 1], 4)
>>> print(Clock.now(), my_linvar)
0, 0
>>> print(Clock.now(), my_linvar)
1, 0.25
>>> print(Clock.now(), my_linvar)
2, 0.5
>>> print(Clock.now(), my_linvar)
3, 0.75
```
Au fur et à mesure que le temps passe, la valeur change de telle sorte qu'après la durée donnée (4 temps dans cet exemple), elle sera exactement de 1. Une fois cette durée écoulée, la linvar commence à changer linéairement sa valeur dans la direction de la valeur d'entrée suivante, qui est 0. Au cours des 4 temps suivants, la valeur diminue à un rythme linéaire vers 0.
```
>>> print(Clock.now(), my_linvar)
4, 1
>>> print(Clock.now(), my_linvar)
5, 0.75
>>> print(Clock.now(), my_linvar)
6, 0.5
>>> print(Clock.now(), my_linvar)
7, 0.25
```
**`sinvar(values, dur)`**
Au lieu de passer d'une valeur à l'autre à une vitesse linéaire, une sinvar passe à une vitesse dérivée d'une onde sinusoïdale. Vous pouvez voir ci-dessous que le taux est apparemment beaucoup plus rapide, mais qu'il atteindra toujours la valeur finale de 1 en même temps que la linvar.
```
>>> my_sinvar = sinvar([0, 1], 4)
>>> print(Clock.now(), my_linvar, my_sinvar)
1, 0.25, 0.38
>>> print(Clock.now(), my_linvar, my_sinvar)
2, 0.50, 0.71
>>> print(Clock.now(), my_linvar, my_sinvar)
3, 0.75, 0.92
>>> print(Clock.now(), my_linvar, my_sinvar)
4, 1, 1
```
**`expvar(values, dur)`**
Le taux de variation de l'expvar est exponentiel, c'est-à-dire qu'il commence par de petites valeurs mais se termine par de grandes étapes. Vous pouvez constater que c'est également le cas lorsque les valeurs diminuent :
```
>>> my_expvar([0, 1], 4)
>>> print(Clock.now(), my_linvar, my_sinvar, my_expvar)
1, 0.25, 0.38, 0.06
>>> print(Clock.now(), my_linvar, my_sinvar, my_expvar)
2, 0.50, 0.71, 0.25
>>> print(Clock.now(), my_linvar, my_sinvar, my_expvar)
3, 0.76, 0.93, 0.57
>>> print(Clock.now(), my_linvar, my_sinvar, my_expvar)
4, 1, 1, 1
>>> print(Clock.now(), my_linvar, my_sinvar, my_expvar)
5, 0.74, 0.92, 0.93
>>> print(Clock.now(), my_linvar, my_sinvar, my_expvar)
6, 0.50, 0.71, 0.75
>>> print(Clock.now(), my_linvar, my_sinvar, my_expvar)
7, 0.25, 0.38, 0.43
```
**Notes et examples**
Il convient de noter que lorsqu'un lecteur utilise une TimeVar à changement progressif, il utilise la valeur stockée dans celle-ci au moment où la note est déclenchée. Cela signifie qu'une fois qu'une note est jouée, vous n'entendrez pas de changement de valeur au fil du temps dans la note elle-même. Essayez ces lignes de code vous-même :
```
# Pas de changement progressif de la fréquence du high pass
p1 >> dirt(dur=4, hpf=linvar([0, 4000], 4))
# Changement progressif apparent de la fréquence du high pass
p2 >> dirt(dur=1/4, hpf=linvar([0, 4000], 4))
```
Vous pouvez également utiliser une durée de 0 pour ignorer immédiatement le changement progressif et passer à la valeur suivante ; ceci est utile pour "réinitialiser" les valeurs et créer des chutes :
```
# Montée en puissance jusqu'à 4000Hz puis remise à 0
p1 >> dirt(dur=1/4, hpf=expvar([0, 4000], [8, 0]))
```
Tout comme les TimeVars ordinaires, les TimeVars à changement progressif peuvent être imbriquées dans d'autres TimeVars afin de mieux gérer la manière dont les valeurs sont appliquées. Par exemple, nous pouvons augmenter la fréquence du filtre passe-haut uniquement sur les 4 derniers temps d'un cycle de 32 temps :
```
# Utiliser un TimeVar normal pour fixer la valeur à 0 pour 28 battements
p1 >> dirt(dur=1/4, hpf=var([0, expvar([0, 4000], [4, 0])], [28, 4]))
```
**Pattern TimeVars**
**`Pvar(patterns, dur)`**
Jusqu'à présent, nous n'avons stocké que des valeurs individuelles dans un TimeVar, mais il est parfois utile de stocker un objet Pattern entier. Il n'est pas possible d'utiliser une TimeVar classique, car tout Pattern figurant dans la liste des valeurs saisies est traité comme une liste imbriquée de valeurs individuelles. Pour éviter ce comportement, vous devez utiliser une Pvar, abréviation de Pattern-TimeVar. Il est créé exactement comme n'importe quel autre TimeVar, mais les valeurs peuvent être des listes/Patterns entiers :
```
>>> a = Pvar([[0, 1, 2, 3], [4, 5, 6]], 4)
>>> print(Clock.now(), a)
0, P[0, 1, 2, 3]
>>> print(Clock.now(), a)
4, P[4, 5, 6]
```
Les opérations et transformations mathématiques fonctionnent également de la même manière que les TimeVars ordinaires :
```
>>> a = Pvar([[0, 1, 2, 3], [4, 5, 6]], 4)
>>> b = (a * 2) + 1
>>> print(Clock.now(), a, b)
0, P[0, 1, 2, 3], P[1, 3, 5, 7]
>>> print(Clock.now(), a, b)
4, P[4, 5, 6], P[9, 11, 13]
>>> def odd_test(num):
... """ Convertir un nombre pair en 3 et un nombre impair en 5 """
... return 5 if num % 2 == 1 else 3
>>> c = c.transform(odd_test)
>>> print(a, b, c)
P[0, 1, 2, 3], P[1, 3, 5, 7], P[3, 5, 3, 5]
>>> print(a, b, c)
P[4, 5, 6], P[9, 11, 13], P[3, 5, 3]
```
Vous pouvez même imbriquer une Pvar dans un Pattern comme vous le feriez pour un Pattern normal afin d'alterner les valeurs jouées... et de les modifier en fonction du temps !
```
# Alterner les notes alternées tous les 8 temps
p1 >> pluck([0, 1, 2, Pvar([[4, 5, 6, 7], [11, 9]], 8)], dur=1/4, sus=1)
```
### 4.3. Timevar avancé
**TimeVar Singletons**
Ce sont des instances de TimeVar qui existent déjà au démarrage de FoxDot et qui représentent des valeurs changeantes, comme le rythme de l'horloge, et qui peuvent être utilisées comme n'importe quel autre TimeVar :
| Variable name | Description |
| -------- | -------- |
| **`now`** | Représente le rythme d'horloge actuel |
| **`nextbar`** | Représente le temps au début de la mesure suivante |
**Advanced Features**
**Offsetting the start time**
Imaginons que vous souhaitiez créer une séquence d'accords qui change non pas au début d'une mesure, mais après le premier temps de la mesure. Comment faire cela avec une TimeVar ? Voyons cela :
```
# Séquence d'accords originale
var([0, 4, 5, 3], 4)
# Mise à jour de la valeur après 1 temps de la mesure
var([3, 0, 4, 5, 3], [1, 4, 4, 4, 3])
```
Ce n'est pas une solution très élégante, mais elle fonctionnerait. Il serait plus simple de décaler le point de départ des durées d'un seul temps, n'est-ce pas ? Cela peut être réalisé en utilisant le mot-clé start comme suit :
```
var([0, 4, 5, 3], start=1)
```
Ceci peut être combiné avec le singleton now pour "démarrer" le cycle d'une var immédiatement. Par exemple, nous pouvons faire passer l'amplitude d'un Player de 0 à 1 sur 8 temps, en commençant maintenant :
`d1 >> play("x-o-", amp=linvar([0, 1], 8, start=now))`
**Durée infinie**
Il est parfois préférable qu'une TimeVar atteigne une valeur et la conserve. Pour ce faire, nous utilisons une variable spéciale appelée inf. C'est un singleton, ce qui signifie qu'il s'agit d'une instance d'une classe mais qu'il n'y en a qu'une seule et qu'elle peut être utilisée pour indiquer à une TimeVar de ne pas changer sa valeur une fois qu'elle l'a atteinte. Voici un exemple d'une linvar qui augmente son amplitude jusqu'à 1 sur 8 temps et qui s'y maintient :
`d1 >> play("x-o-", amp = linvar([0, 1], [8, inf]))`
Elle peut également être combinée avec les singletons now et nextbar pour une montée en puissance immédiate :
`d1 >> play("x-o-", amp = linvar([0, 1], [8, inf], start=now)`)
Remarque : lors du calcul de la longueur totale du cycle à partir du TimeVar, inf est compté comme 0 jusqu'à ce qu'il soit atteint une fois que le TimeVar est mis en œuvre. Cela signifie que la linvar utilisée ci-dessus démarre/redémarre son cycle à chaque huitième temps.