< Back to IRCAM Forum

Antescofo et sequenceur

Bonjour,
J’essaie d’explorer les possibilités d’Antescofo comme séquenceur. Donc sans utiliser de suivi de partitions.

L’objectif est de pouvoir contrôler des processus en temps réel, donc d’avoir la main sur le tempo (les tempos de plusieurs séquences), et divers paramètres de variations de ces séquences.

Première idée: une boucle.
Pour l’instant, c’est très simple, mais cela permet d’avoir plusieurs boucles qui tournent à des tempo différents, et avec un peu de combinatoire, de se modifier l’une et l’autres (if…)
Est-ce la bonne façon de faire ?

 ;START-STOP sequence 1
whenever ($ctrlSeq1)
{
 if ( $ctrlSeq1 = 1 ){
  abort ::seq1
 }else{
  abort ::seq1
  ::seq1()
 }
}
; SEQUENCE 1
; reglages exterieurs (MAX) :
; exttempo1: duree d'un step
; Offset1: depart de la lecture dans le table
; Modulo1: taille de la boucle
; sortie: TEST1 (juste pour valider le fonctionnement)
@proc_def ::seq1() 
{
 @local $positionTab := 0
 @local $loop := 0
 
 @local $TABLO := tab [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8]
 
 @local $period := $exttempo1
 Loop $period ms
 {
  $positionTab := $TABLO[$loop + $Offset1]
  $loop := ($loop + 1) % $Modulo1
  TEST1 $positionTab
  $period := $exttempo1
 }
}

Ensuite, pour aller plus loin: A chaque itération de la boucle, seul un élément de la table est lu. Ce serait plus intéressant si tout un “motif” pouvait l’être.
A priori, ce devrait être simple.
Mon idée première est d’avoir une série d’événements:
0 TEST1 10
1 TEST2 20

Mais il y a 2 problèmes:

  • le tempo pour le séquencement de ces événements est associé au tempo global et pas à la période de la loop. (je m’en doutais…)
  • la boucle a sa durée, qui peut être différent de la durée d’exécution de son contenu (j’ai bien lu la doc.).
    Donc:
  • comment faire pour avoir un tempo différent par process, et contrôler ce tempo. Et définir la durée d’un Loop par rapport à ce tempo “locale”.
  • comment faire pour que la Loop attende la fin de son exécution avant de recommencer une itération? (Un peu comme le @exclusive).
    Merci d’avance.

Bonjour.

Si votre programme répond à vos besoin, alors il est bon :slight_smile:

1

Pour lire une sous-séquence (un “motif”) dans la table, on peut utiliser @slice qui prend une tranche du tableau. Par exemple, dans le programme suivant, les variables $debut et $fin servent à délimiter la tranche qu’on extrait, et celle-ci varie avec les itérations de la boucle :

$t := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
$debut := 0
$fin := 3

loop 1
{
   @local $tt

   $debut := ($debut + 1 ) % $t.size();
   $fin := ($fin + 1 ) % $t.size();

   if ($debut > $fin) {
       @local $tmp
       $tmp := $debut
       $debut := $fin
       $fin := $tmp
    }

   $tt := $t.slice($debut, $fin)
   print $tt
} during [20#]

La clause during[20#] est là juste pour limiter le nombre d’itération de la boucle. . Le résultat est :

print 2 3 4
print 3 4 5
print 4 5 6
print 5 6 7
print 6 7 8
print 7 8 9
print 1 2 3 4 5 6 7
print 2 3 4 5 6 7 8
print 3 4 5 6 7 8 9
print 1 2 3
print 2 3 4
print 3 4 5
print 4 5 6
print 5 6 7
print 6 7 8
print 7 8 9
print 1 2 3 4 5 6 7
print 2 3 4 5 6 7 8
print 3 4 5 6 7 8 9
print 1 2 3

Le calcul des variables $debut et $fin n’est pas important, il est là juste pour illustrer qu’on calcule la tranche qu’on extraie. Implicitement, un tableau comme argument dans un message est “applatit” en autant d’argument que d’élément dans le tableau. Il y a beaucoup de fonctions prédéfinies qui permettent de travailler avec les tableaux, voir : http://antescofo-doc.ircam.fr/Library/Functions/00intro/#tab-related-functions

2

Pour le tempo : chaque groupe d’instruction peut avoir sa propre vision de l’avancement du temps (le temps avance parce qu’il y a des événements qui arrivent et/ou parce qu’on a un tempo ou bien qu’on est synchronisé par rapport à un autre processus). Voir le chapitre Notions of TimeS in Antescofo dans la documentation.

Pour la boucle, il suffit de lui fixer un tempo, qui peut être calculé si besoin. Par exemple :

$tempo := 60
$i := 0

loop 1 @tempo $tempo
{
     $i += 1
     print $NOW
     if ($i == 5) { $tempo := 120 }
     
} during [10#]

La période de la boucle est toujours de 1 en temps relatif, mais ce temps relatif est réglé par la déclaration @tempo et prend une valeur de 60 pulsation par minute (i.e., 1 BPM = 1 seconde) pendant les 5 premières itérations et puis prend la valeur de 120 pour les itérations suivantes (i.e., 1 BPM = 0.5 seconde). On a donc l’affichage

print 0.0
print 1.0
print 2.0
print 3.0
print 4.0
print 4.5
print 5.0
print 5.5
print 6.0
print 6.5

On peut associer le tempo non pas à la boucle mais au contexte qui lance le processus (le lancement du processus est lui-même instantané, il n’est pas un groupe et donc lui associer un @tempo ne fait pas le travail). Plus explicitement
Pour élaborer votre exemple,

@proc_def ::LOOP()
{
    Loop 1 {
       print LOOP $NOW
    } during [10#]
}
// ...
group G @tempo 120 {
     ::LOOP()
}

Va effectuer tous les calculs du groupe (et donc tous les clauls lancés par le processus ::LOOP) avec un tempo de 120.

3

“Faire que la loop attende la fin de son exécution avant de recommencer une itération” est une question un peu délicate.

Le premier point est que les calculs qui sont dans votre boucle prennent un temps de 0 : il n’y a aucun délai et Antescofo fait comem si les calculs indiqués (des affectations, des envois de messages et du calcul d’expression) ne prenait pas de temps. L’idée d’attendre “la fin du corps de boucle” n’a ici pas de sens.

Le corps de boucle suivant prend du temps à s’exécuter :

$i := 0
Loop 1
{
    @local $j := $i
    $i += 1
    print iteration $j premiere partie à $NOW
    2
    print iteration $j seconde partie à $NOW
} during [5#] 

Le corps de boucle prend 2 pulsation et comme la boucle est itéré tous les 1 pulsation, il y a recouvrement des corps de boucles. On peut le voir dans la trace :

print iteration 0 premiere partie à 32.0
print iteration 1 premiere partie à 33.0
print iteration 2 premiere partie à 34.0
print iteration 0 seconde partie à 34.0
print iteration 3 premiere partie à 35.0
print iteration 1 seconde partie à 35.0
print iteration 4 premiere partie à 36.0
print iteration 2 seconde partie à 36.0
print iteration 3 seconde partie à 37.0
print iteration 4 seconde partie à 38.0

(l’origine des temps est ici décalée et commence à 32, parce que j’ai lancé cette boucle pas au début de mon programme, mais après d’autres calculs).

Si on veut éviter le recouvrement, on peut ajuster la période et la fixer à 2 :

print iteration 0 premiere partie à 32.0
print iteration 1 premiere partie à 34.0
print iteration 0 seconde partie à 34.0
print iteration 2 premiere partie à 36.0
print iteration 1 seconde partie à 36.0
print iteration 3 premiere partie à 38.0
print iteration 2 seconde partie à 38.0
print iteration 4 premiere partie à 40.0
print iteration 3 seconde partie à 40.0
print iteration 4 seconde partie à 42.0

mais on peut s’apercevori que cela ne règle que partiellement le problème : la fin de l’itération n et le début de l’itération n+1 se produisent à la même date. Le mot clé @exclusive est intéressant : il tue les ancien corps de boucle qui sont en cours d’exécution, avant de lancer le 'itération suivante. on a donc :

print iteration 0 premiere partie à 32.0
print iteration 1 premiere partie à 34.0
print iteration 2 premiere partie à 36.0
print iteration 3 premiere partie à 38.0
print iteration 4 premiere partie à 40.0
print iteration 4 seconde partie à 42.0

Tout cela a un sens car tout est fait pour qu’on puisse raisonner idéalement en attribuant un temps de calcul de zero aux actions élémentaires: elles s’exécutent “dans l’instant”. Ce sont les délais (et les curves) qui donnent de l’épaisseur au temps et “prennent du temps” pour s’exécuter.

Si vous connaissez le temps pris par un corps de boucle, il suffit de l’utiliser comme période pour obtenir ce que vous souhaitez. Remarquez bien que la période d’une boucle est une expression qui peut changer de valeur au cours du temps :

$i := 0
$periode := 1

Loop $periode
{
   print loop periode $periode
   if ($i == 5) { $periode := 0.5 }
   $i += 1
} during [10#]

produit le résultat suivant :

print loop periode 1
print loop periode 1
print loop periode 1
print loop periode 1
print loop periode 1
print loop periode 1
print loop periode 0.5
print loop periode 0.5
print loop periode 0.5
print loop periode 0.5

Attention, quand le délai pour lancer le corps de boucle n+1 est calculé juste avant le lancement de l’itération n. On peut même remplacer la période par un tableau : ce sont les éléments du tableau qui sont pris comme période, tour à tour :

$t := [1, 2, 3, 2, 0.5]

Loop $t {
   print $NOW
} during [12#]

a pour trace

print 50.0
print 52.0
print 55.0
print 57.0
print 57.5
print 58.5
print 60.5
print 63.5
print 65.5
print 66.0
print 67.0
print 69.0

L’itération 0 est lancé de suite, l’itération 1 est lancé $t[1] plus tard, c’est à dire après un délai de 2, pour l’itération 3 utilise $t[3], etc. Au bout du tableau on recommence au début.

Il y a une autre manière de réaliser des itérations, qui repose sur une notion de récursion temporelle. L’idée est d’écrire un processus qui réalise le corp de boucle et de rappeller ce processus à la fin du travail :

@proc_def ::iter($n) 
{
   print debut $NOW $n
   $n
   print fin $NOW
   +=> 
   ::iter($n + 0.5)
}

Le processus ::iter prend un argument qui ici va servir de délai. il commence par envoyé un message début, puis attend ce délai avant d’envoyer le message fin. L’opérateur +=> est un opérateur de continuation qui lance un calcul à la fin d’un autre. Ici, après le message “fin”, le processus ::iter se relance lui-même à la manière d’une fonction récursive. Au passage, il augment le délai en argument.

L’'expression

::iter(0) 
10 abort ::iter

va lancer le processus ::iter et au bout de 10 pulsation, il va tuer tous les processus ::iter en cours. La trace correspondante est :

print debut 72.0 0
print fin 72.0
print debut 72.0 0.5
print fin 72.5
print debut 72.5 1.0
print fin 73.5
print debut 73.5 1.5
print fin 75.0
print debut 75.0 2.0
print fin 77.0
print debut 77.0 2.5
print fin 79.5
print debut 79.5 3.0

ATTENTION la récursion temporelel est complexe à maitriser. Il est facile de lancer des calculs qui enflent et l’ordinateur se retrouvent submergé de calcul et finit par planter.

L’exemple précédent ne peut pas tourner indéfiniment. En effet, chaque calcul engendre un contexte temporel dans lequel s’exécutent les sous-calculs (comem on a pu le voir plus haut avec l’effet de @tempo sur les sous-calculs). Ici, cela veut dire que à chaque lancement de ::iter on crée un nouveau contexte qui doit hériter du contexte temporel de ses parents. Au bout d’un moment il y en a trop. Ce “trop” correspond à la heuteur de pile dans les fonctioons récursives. Sur ma machine, il est aux environs de 3000 itération. Si on a une itération toutes les secondes, cela veut dire que le programe plantera au bout de 50 minutes. Il y a des solutions pour éviter cela, mais cela nous entrainerait sans doute trop loin.

Fantastique ! Merci pour tous ces détails très clair.

J’avais bien compris (il me semble…) le fonctionnement du temps dans Antescofo, mais je n’avais pas tout compris pour son application. Par exemple de lancer un process dans un groupe pour avoir accès à un tempo par “loop”.

Aussi, j’avais essayé l’itération, et effectivement, en laissant tourner, à un moment, il y a un message d’erreur. Mais dans un contexte de boucle simple, même avec un peu de combinatoire, cette façon de faire n’est pas nécessaire.

encore merci!

Au passage, je rappelle le lien vers l’excellente et exhaustive documentation réalisée par @giavitto, Merci Jean-Louis !

http://antescofo-doc.ircam.fr/