Ecrire un programme de contrôle Cscore

Le format général d'un programme de contrôle Cscore est :

#include "cscore.h" 
void cscore(CSOUND *cs) 
{ 
 /*  DECLARATIONS DES VARIABLES  */ 
 /*  CORPS DU PROGRAMME  */ 
}

L'instruction include définira les structures d'évènement et de liste et toutes les fonctions de l'API Cscore pour le programme. Il faut que le nom de la fonction de l'utilisateur soit cscore si elle doit être liée avec le programme main standard dans cscormai.c ou liée comme routine Cscore interne pour un exécutable de Csound personnalisé. Cette fonction cscore() reçoit un argument de cscormai.c ou de Csound -- CSOUND *cs -- qui est un pointeur sur un objet Csound. Le pointeur cs doit être passé en premier paramètre à toutes les fonctions de l'API Cscore que le programme appelle.

Le programme C suivant lira depuis une partition numérique standard, jusqu'à (mais sans l'inclure) la première instruction s ou e, puis il écrira ces données (inchangées) en sortie.

#include "cscore.h" 
void cscore(CSOUND *cs) 
{ 
  EVLIST *a;                    /* a est autorisé à pointer sur une liste d'évènements */ 
  a = cscoreListGetSection(cs); /* lit les évènements, retourne le pointeur de liste */ 
  cscoreListPut(cs, a);         /* écrit ces évènements en sortie (inchangés) */ 
  cscorePutString(cs, "e");     /* écrit la chaîne e sur la sortie */ 
}

Aprés l'exécution de cscoreListGetSection(), la variable a pointe sur une liste d'adresses d'évènements, qui pointent chacune sur un évènement stocké. Nous avons utilisé ce même pointeur pour permettre à une autre fonction de liste -- cscoreListPut() -- d'accéder à tous les évènements qui ont été lus et de les écrire en sortie. Si nous définissons maintenant un autre symbole e comme pointeur d'évènement, alors l'instruction

e = a->e[4];

lui affectera le contenu du 4ème emplacement de la structure EVLIST, a. Ce contenu est un pointeur sur un évènement, qui comprend lui-même un tableau de valeurs de champs de paramètre. Ainsi le terme e->p[5] signifiera la valeur du champ de paramètre 5 du 4ème évènement dans la EVLIST dénotée par a. Le programme ci-dessous multipliera la valeur de ce p-champ par 2 avant de l'écrire en sortie.

#include "cscore.h" 
void cscore(CSOUND *cs) 
{
  EVENT  *e;                    /* un pointeur sur un évènement */ 
  EVLIST *a; 
  a = cscoreListGetSection(cs); /* lit une partition comme une liste d'évènements */ 
  e = a->e[4];                  /* pointe sur l'évènement 4 dans la liste a */ 
  e->p[5] *= 2;                 /* trouve le p-champ 5, multiplie sa valeur par 2 */ 
  cscoreListPut(cs, a);         /* écrit en sortie la liste d'évènements */ 
  cscorePutString(cs, "e");     /* ajoute une instruction de "fin de partition" */ 
}

Considérez maintenant la partition suivante, dans laquelle p[5] contient la fréquence en Hz.

f 1 0 257 10 1 
f 2 0 257 7 0 300 1 212 .8 
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
e

Si cette partition est donnée au programme principal précédent, la sortie résultante ressemblera à ceci :

f 1 0 257 10 1 
f 2 0 257 7 0 300 1 212 .8
i 1 1 3 0 440 10000
i 1 4 3 0 512 10000        ; p[5] est devenu 512 au lieu de 256.
i 1 7 3 0 880 10000 
e

Notez que le 4ème évènement est en fait la seconde note de la partition. Jusqu'ici nous n'avons pas fait de distinction entre les notes et les tables de fonction mises en place dans une partition numérique. Les deux peuvent être classées comme évènement. Notez aussi que notre 4ème évènement a été stocké dans le champ e[4] de la structure. Pour être compatible avec la notation des p-champs de Csound, nous ignorerons p[0] et e[0] dans les structures d'évènement et de liste, en stockant p1 dans p[1], l'évènement 1 dans e[1], etc. Les fonctions de Cscore adoptent toutes cette convention.

Pour étendre l'exemple ci-dessus, nous pourrions décider d'utiliser les mêmes pointeurs a et e pour examiner chacun des évènements dans la liste. Noter que e n'a pas été fixé au nombre 4, mais au contenu du 4ème emplacement de la liste. Pour inspecter le p5 de l'évènement précédent dans la liste, nous n'avons qu'à redéfinir e avec l'affectation

e = a->e[3];

et référencer le 5ème emplacement du tableau de p-champs avec l'expression

e->p[5]

Plus généralement, nous pouvons utiliser une variable entière comme indice du tableau e[], et accéder séquentiellement à chaque évènement en utilisant une boucle et en incrémentant l'indice. Le nombre d'évènements stockés dans une EVLIST est contenu dans le membre nevents de la structure.

int index;    /* démarre avec e[1] car e[0] n'est pas utilisé */
for (index = 1; index <= a->nevents; index++)
{
  e = a->e[index];
  /* faire quelque chose avec e */
}

L'exemple ci-dessus démarre avec e[1] et augmente l'indice à chaque passage dans la boucle (index++) jusqu'à ce qu'il soit plus grand que a->nevents, l'indice du dernier évènement dans la liste. Les instructions à l'intérieur de la boucle for sont exécutées une dernière fois quand index égale a->nevents.

Dans le programme suivant nous utiliserons la même partition en entrée. Cette fois nous séparerons les instructions de ftable des instructions de note. Nous écrirons ensuite en sortie les trois évènements de note stockés dans la liste a, puis nous créerons une seconde section de partition constituée de l'ensemble de hauteurs original et d'une version transposée de celui-ci. Cela apportera un doublement à l'octave.

Ici, notre indice dans le tableau est n et il est incrémenté dans un bloc for qui boucle nevents fois, ce qui permet d'appliquer une instruction au même p-champ des évènements successifs.

#include  "cscore.h"
void cscore(CSOUND *cs)
{
  EVENT  *e, *f;
  EVLIST *a, *b;
  int n;

  a = cscoreListGetSection(cs);            /* lit la partition dans la liste d'évènements "a" */ 
  b = cscoreListSeparateF(cs, a);          /* sépare les instructions f */ 
  cscoreListPut(cs, b);                    /* écrit les instructions f dans la partition en sortie */
  e = cscoreDefineEvent(cs, "t 0 120");    /* définit un évènement pour l'instruction de tempo */ 
  cscorePutEvent(cs, e);                   /* écrit l'instruction de tempo dans la partition */ 
  cscoreListPut(cs, a);                    /* écrit les notes */ 
  cscorePutString(cs, "s");                /* fin de section */ 
  cscorePutEvent(cs, e);                   /* écrit l'instruction de tempo encore une fois */ 
  b = cscoreListCopyEvents(cs, a);         /* fait une copie des notes dans "a" */ 
  for (n = 1; n <= b->nevents; n++)        /* répète les lignes suivantes nevents fois : */
  { 
    f = b->e[n]; 
    f->p[5] *= 0.5;                        /* transpose la hauteur d'une octave vers le bas */
  }
  a = cscoreListAppendList(cs, a, b);      /* ajoute ces notes aux hauteurs originales */ 
  cscoreListPut(cs, a); 
  cscorePutString(cs, "e");
}

La sortie de ce programme est :

f 1 0 257 10 1
f 2 0 257 7 0 300 1 212 .8
t 0 120
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
s
t 0 120
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
i 1 1 3 0 220 10000
i 1 4 3 0 128 10000
i 1 7 3 0 440 10000
e

Si la sortie est écrite dans un fichier, le fait que les évènements ne soient pas ordonnés n'est pas un problème. La sortie est écrite dans un fichier (ou sur la sortie standard) chaque fois que la fonction cscoreListPut() est utilisée. Cependant, si ce programme était appelé durant une exécution de Csound et que la fonction cscoreListPlay() était remplacée par cscoreListPut(), alors les évènements seraient envoyés à l'orchestre au lieu du fichier et il faudrait qu'ils soient préalablement triés en appelant la fonction cscoreListSort(). Les détails de la sortie de la partition et de son exécution quand on utilise Cscore depuis Csound sont décrits dans la section suivante.

Ensuite nous étendons le programme ci-dessus en utilisant la boucle for pour lire p[5] et p[6]. Dans la partition originale p[6] dénote l'amplitude. Pour créer un diminuendo sur l'octave inférieure ajoutée, qui soit indépendant de l'ensemble de notes original, une variable appelée dim sera utilisée.

#include "cscore.h" 
void cscore(CSOUND *cs)
{
  EVENT  *e, *f;
  EVLIST *a, *b;
  int n, dim;                              /* déclare deux variables entières */ 

  a = cscoreListGetSection(cs);
  b = cscoreListSeparateF(cs, a);
  cscoreListPut(cs, b);
  cscoreListFreeEvents(cs, b);
  e = cscoreDefineEvent(cs, "t 0 120");
  cscorePutEvent(cs, e);
  cscoreListPut(cs, a);
  cscorePutString(cs, "s");
  cscorePutEvent(cs, e);                   /* écrit une autre instruction de tempo */
  b = cscoreListCopyEvents(cs, a);
  dim = 0;                                 /* initialise dim à 0 */ 
  for (n = 1; n <= b->nevents; n++)
  {
    f = b->e[n]; 
    f->p[6] -= dim;                        /* soustrait la valeur courante de dim */ 
    f->p[5] *= 0.5;                        /* transpose la hauteur une octave plus bas */
    dim += 2000;                           /* augmente dim pour chaque note */ 
  }
  a = cscoreListAppendList(cs, a, b);      /* ajoute ces notes aux hauteurs originales */ 
  cscoreListPut(cs, a); 
  cscorePutString(cs, "e");
}

En utilisant à nouveau la même partition en entrée, la sortie de ce programme est :

f 1 0 257 10 1 
f 2 0 257 7 0 300 1 212 .8
t 0 120
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
s
t 0 120
i 1 1 3 0 440 10000     ; Trois notes originales aux pulsations
i 1 4 3 0 256 10000     ; 1, 4 et 7 sans diminuendo.
i 1 7 3 0 880 10000
i 1 1 3 0 220 10000     ; Trois notes transposées une octave plus bas
i 1 4 3 0 128 8000      ; également aux pulsations 1, 4 et 7
i 1 7 3 0 440 6000      ; avec diminuendo.
e

Dans le programme suivant la même séquence de trois notes sera répétée à divers intervalles de temps. La date de début de chaque groupe est déterminée par les valeurs du tableau cue. Cette fois le dim se produira sur chaque groupe de notes plutôt que sur chaque note. Remarquez la position de l'instruction qui incrémente la variable dim en dehors de la boucle for intérieure.

#include "cscore.h" 
int cue[3] = {0,10,17};                    /* déclare un tableau de 3 entiers */ 
void cscore(CSOUND *cs) 
{
  EVENT  *e, *f;
  EVLIST *a, *b;
  int n, dim, cuecount;                    /* déclare la nouvelle variable cuecount */

  a = cscoreListGetSection(cs);
  b = cscoreListSeparateF(cs, a);
  cscoreListPut(cs, b);
  cscoreListFreeEvents(cs, b);
  e = cscoreDefineEvent(cs, "t 0 120");
  cscorePutEvent(cs, e);
  dim = 0; 
  for (cuecount = 0; cuecount <= 2; cuecount++) /* les éléments de cue sont numérotés 0, 1, 2 */
  {
    for (n = 1; n <= a->nevents; n++)
    { 
      f = a->e[n]; 
      f->p[6] -= dim; 
      f->p[2] += cue[cuecount];             /* ajoute les valeurs de cue */ 
    } 
    printf("; diagnostic:  cue = %d\n", cue[cuecount]); 
    dim += 2000; 
    cscoreListPut(cs, a);
  } 
  cscorePutString(cs, "e");
}

Ici la boucle for intérieure lit les évènements de la liste a (les notes) et la boucle for extérieure lit chaque répétition des évènements de la liste a (les "répliques" du groupe de hauteurs). Ce programme démontre aussi un moyen utile de résolution de problème au moyen de la fonction printf. Le point-virgule commence la chaîne de caractères pour produire un commentaire dans le fichier de partition résultant. Dans ce cas, la valeur de cue est imprimée en sortie pour s'assurer que le programme prend le bon membre du tableau au bon moment. Losrque les données de sortie sont fausses ou que des messages d'erreur sont rencontrés, la fonction printf peut aider à identifier le problème.

A partir du même fichier d'entrée, le programme C ci-dessus générera la partition suivante. Pouvez-vous expliquer pourquoi le dernier ensemble de notes ne démarre pas au bon moment et comment corriger le problème ?

f 1 0 257 10 1
f 2 0 257 7 0 300 1 212 .8
t 0 120
; diagnostic:  cue = 0
i 1 1 3 0 440 10000
i 1 4 3 0 256 10000
i 1 7 3 0 880 10000
; diagnostic:  cue = 10
i 1 11 3 0 440 8000
i 1 14 3 0 256 8000
i 1 17 3 0 880 8000
; diagnostic:  cue = 17
i 1 28 3 0 440 4000
i 1 31 3 0 256 4000
i 1 34 3 0 880 4000
e