# 5 - Clock ### 5.1. Utiliser le Tempoclock Le TempoClock, appelé Clock dans FoxDot, gère toute la programmation des événements musicaux (et non musicaux). Voici quelques méthodes utiles pour tirer le meilleur parti du TempoClock pendant que vous codez en direct. **Mesures de retour** **`Clock.now()`** Renvoie la mesure de battement actuelle détenue par l'horloge. **`Clock.bar_length()`** Renvoie la longueur de la mesure en battements **`Clock.bars(n=1)`** Renvoie le nombre de battements dans n mesures. Equivalent à Clock.bar_lenth() * n **`Clock.beat_dur(n=1)`** Renvoie la durée de n battements en secondes **`Clock.beats_to_seconds(beats)`** Equivalent à Clock.beat_dur(beats). **`Clock.seconds_to_beats(seconds)`** Renvoie le nombre de battements en secondes **`Clock.get_time_at_beat(beat)`** Renvoie l'heure de la machine locale au moment spécifié. **`Clock.next_bar()`** Renvoie le numéro du temps pour le début de la mesure à venir **`Clock.mod(beat, t=0)`** Renvoie le prochain moment où Clock.now() % beat sera égal à t. Utile pour obtenir le temps de départ d'un cycle. ``` # Renvoie le temps au début du prochain cycle de 16 temps Clock.mod(16) ``` ``` # Renvoie le rythme au début du prochain cycle de 4 mesures Clock.mod(Clock.bars(4)) ``` **Mise à jour de l'horloge** **`Clock.set_time(beat)`** Définit le rythme de l'horloge actuelle et met à jour tous les objets Player dans l'horloge. **`Clock.set_cpu_usage(value)`** Doit être une valeur comprise entre 0 et 2. Une valeur de 0 utilisera une plus petite quantité de puissance de l'unité centrale, mais une instabilité peut se produire lors de l'utilisation de durées plus petites. Une valeur de 2 utilisera une plus grande quantité de CPU mais aura de meilleures performances. **`Clock.set_latency(value)`** Doit être une valeur comprise entre 0 et 2. Une valeur de 0 utilisera une valeur de latence plus petite, ce qui signifie que les mises à jour du code seront entendues plus immédiatement, et une valeur de 2 utilisera une valeur de latence plus grande, ce qui permettra d'éviter les messages "late" dans SuperCollider. **`Clock.update_tempo(bpm)`** Fixe le tempo au début de la mesure suivante. Peut (et doit) être réglé en utilisant `Clock.bpm = bpm`. **`Clock.update_tempo_now(bpm)`** Met à jour le tempo de l'horloge immédiatement (c'est-à-dire pas au début de la mesure suivante). **`Clock.bpm = new_tempo`** Définit le tempo au début de la mesure suivante. Doit être un entier, un nombre à virgule flottante ou un TimeVar. **`Clock.nudge = new_nudge`** Décale le temps de descente de l'horloge de new_nudge secondes. Utile pour se synchroniser manuellement avec une source sonore externe. Par exemple, FoxDot et l'autre source sonore jouent tous deux à 120 bpm mais FoxDot est légèrement en retard sur le downbeat, vous pouvez utiliser Clock.nudge = 0.2 pour retarder le down-beat de 0.2 secondes. **`Clock.meter = (num_beats, beat_length)`** Met essentiellement à jour la valeur retournée par Clock.bar_length(). Par exemple, pour régler l'horloge sur l'heure 3/4, utilisez Clock.meter = (3, 4). **Programmation d'événements** **`Clock.schedule(callable, beat=None, args=(), kwargs={})`** Planifie un objet callable, tel qu'une fonction ou un objet avec une méthode __call__() valide, au rythme donné. Si aucun temps n'est donné, l'objet callable sera programmé au début de la mesure suivante. Les arguments et les mots-clés de l'objet callable peuvent être fournis respectivement dans une liste et un dictionnaire : ``` def update(key, bpm=None): Root.default = int(key) if bpm is not None: Clock.bpm = bpm return # Schedule for 2 beats in the future Clock.schedule(update, Clock.now() + 2, args=[4], kwargs={"bpm": 120}) ``` **`Clock.future(dur, callable, args=(), kwargs={})`** Similaire à Clock.schedule, mais le premier argument spécifie la date à laquelle l'objet doit être programmé, et non un rythme spécifique. **`Clock.clear()`** Efface tous les événements programmés dans l'horloge, en arrêtant tout son. Dans l'éditeur FoxDot, Ctrl+. est un raccourci pour exécuter cette commande. **`nextBar(func)`** Planifie une fonction qui sera appelée au début de la mesure suivante. Peut également être utilisé comme décorateur pour définir une nouvelle fonction qui est appelée au début de la mesure suivante, c'est-à-dire qu'elle ne doit pas être invoquée directement : ``` # Arrêter tous les événements au début de la mesure suivante nextBar(Clock.clear) # Le décorateur appelle `key_change()` au début de la mesure suivante. @nextBar def key_change(): Scale.default = "minor" Root.default = 4 ``` **Synchronisation** **`Clock.sync_to_espgrid(host="localhost", port=5510)`** Utilise le logiciel EspGrid pour synchroniser FoxDot avec d'autres environnements de codage en direct, tels que TidalCycles ou SuperCollider. Assurez-vous que le logiciel EspGrid est en cours d'exécution sur votre machine avant d'exécuter cette commande. **`Clock.sync_to_midi(sync=True)`** EXPÉRIMENTALE : Synchronisation avec l'horloge d'un appareil MIDI externe. **`Clock.connect(address)`** Permet de synchroniser les instances de FoxDot sur un réseau. Un utilisateur (l'horloge maître) doit aller dans le menu "Language" et sélectionner "Listen for connections". L'adresse IP de l'horloge maître est alors affichée. Tous les autres utilisateurs qui souhaitent se synchroniser utilisent `Clock.connect` et l'adresse IP donnée. ### 5.2. Temporal Recursion Introduction Sometimes we want to perform some actions several times on repeat. This is generally what we do when we create any computer program, but when we are live coding it can be quite tricky to perform repeated actions and continue coding while these actions are performed. A useful solution to this is temporal recursion; creating a function that calls itself in the future. Let’s look at an real life example problem that I was asked about recently and how temporal recursion provided a solution. I was asked why the following FoxDot code did not work: ```python for n in range(21): if 0 > n > 4: d1 >> play("x ") elif 4 > n > 20: d1 >> play("x-") else: d1.stop() ``` The reason is that the code has no concept of time – either seconds or beats – and so all the code is run immediately, in less that 0.1 seconds. That means that after 0.1 seconds, the code calls d1.stop() and, seemingly, nothing happens. If this was a normal Python program, we might tell the program to “sleep” for a small period of time at every step of the loop like so: ```python import time for n in range(21): if 0 > n > 4: d1 >> play("x ") elif 4 > n > 20: d1 >> play("x-") else: d1.stop() time.sleep(1) ``` This would work; for the first 4 seconds of the program we would here the result of d1 >> play("x ") and then for the next 16 seconds we would hear d1 >> play("x-") then it would stop. However, there is no way to stop or change this code while it is running – which is something we like to do when we live code. An alternative approach is to use temporal recursion and define a function that calls itself in the future like so: ```python def update(n=0): if 0 > n > 4: d1 >> play("x ") elif 4 > n > 20: d1 >> play("x-") else: d1.stop() return Clock.future(1, update, args=(n + 1,)) # Start the process by calling the function update() ``` The function uses Clock.future to schedule itself in the future with a new value of n until it has the value of 21, then it will stop. The cool thing about temporal recursion is that if we change the contents of the update function, it will still be executed with the correct value for n. We can also stop the repeated call simply by removing the Clock.future call from the end of the function.