Si les générateurs unitaires existants de Csound ne répondent pas à vos besoins, il est relativement aisé d'étendre Csound en écrivant de nouveaux générateurs unitaires en C ou en C++. Le traducteur, le chargeur et le moniteur d'exécution traiteront votre module comme n'importe quel autre module, pourvu que vous suiviez certaines conventions.
Historiquement, on réalisait ceci avec des générateurs unitaires intégrés, c'est-à-dire dont le code est lié statiquement avec le reste de l'exécutable de Csound.
Aujourd'hui, on préfère créer des générateurs unitaires sous forme de plugin. Ce sont des
bibliothèques à liaison dynamique (DLL) sous Windows, et des modules chargeables (bibliothèques partagées
chargées par dlopen
) sur Linux. Csound recherche et charge ces plugins au
moment de l'exécution. L'avantage de cette méthode, naturellement, est que les plugins créés par
n'importe quel développeur, n'importe quand, peuvent être utilisés avec des versions de Csound déjà
existantes.
Vous avez besoin d'une structure définissant les entrées, les sorties et l'espace de travail, plus
du code d'initialisation et du code d'exécution. Mettons un exemple de tout cela dans deux nouveaux
fichiers, newgen.h
et newgen.c
. Les exemples donnés sont pour
Csound 5. Pour les versions antérieures, il faut omettre le premier paramètre
(CSOUND *csound
) dans toutes les fonctions d'opcode.
/* newgen.h - définit une structure */ /* Déclare les structures et les fonctions de Csound. */ #include "csoundCore.h" typedef struct { OPDS h; /* en-tête requis */ MYFLT *result, *istrt, *incr, *itime, *icontin; /* adr des arg de sortie et d'entrée */ MYFLT curval, vincr; /* espace de données privé */ long countdown; /* ditto */ } RMP; /* newgen.c - code d'initialisation et d'exécution */ /* Déclare les structures et les fonctions de Csound. */ #include "csoundCore.h" /* Déclare la structure RMP. */ #include "newgen.h" int rampset (CSOUND *csound, RMP * p) /* à l'initialisation de la note : */ { if (*p->icontin == FL(0.0)) p->curval = *p->istrt; /* reçoit si besoin la nouvelle valeur de début */ p->vincr = *p->incr / csound->esr; /* fixe l'increment au taux-s par sec. */ p->countdown = *p->itime * csound->esr; /* compteur pour iduree en secondes */ return OK; } int ramp (CSOUND *csound, RMP * p) /* pendant l'exécution de la note : */ { MYFLT *rsltp = p->result; /* initialise un pointeur sur le tableau de sortie */ int nn = csound->ksmps; /* taille du tableau donnée par l'orchestre */ do { *rsltp++ = p->curval; /* copie la valeur courante vers la sortie */ if (--p->countdown > 0) /* pour les premières iduree secondes, */ p->curval += p->vincr; /* incrémenter la valeur */ } while (--nn); return OK; }
Maintenant nous ajoutons ce module à la table du traducteur dans entry1.c
,
sous le nom d'opcode rampt
:
#include "newgen.h" int rampset(CSOUND *, RMP *), ramp(CSOUND *, RMP *); /* opname dsblksiz thread outypes intypes iopadr kopadr aopadr */ { "rampt", S(RMP), 5, "a", "iiio", (SUBR)rampset, (SUBR)NULL, (SUBR)ramp },
Finalement, il faut relier Csound avec le nouveau module. Ajoutez le nom du fichier C à la liste
libCsoundSources
dans le fichier SConstruct
:
libCsoundSources = Split(''' Engine/auxfd.c ... OOps/newgen.c ... Top/utility.c ''')
Lancez scons comme vous le feriez pour toute autre construction de Csound, et le nouveau module sera intégré dans votre Csound.
Les actions ci-dessus ont ajouté un nouveau générateur au langage Csound. C'est une fonction de rampe linéaire au taux audio qui modifie une valeur d'entrée selon une pente définie par l'utilisateur pour une durée donnée. Cette rampe peut éventuellement continuer depuis la dernière valeur de la note précédente. L'entrée correspondante du manuel de Csound ressemblerait à ceci :
ar rampt idebut, ipente, iduree [, icontin]
idebut -- valeur du début d'une rampe linéaire au taux audio. Eventuellement ignorée s'il y a un drapeau de continuité.
ipente -- pente de la rampe, exprimée comme le taux de changement des y par seconde.
iduree -- durée de la rampe en secondes, après laquelle la valeur est tenue jusqu'à la fin de la note.
icontin (facultatif) -- drapeau de continuité. S'il est à zéro, la rampe démarrera depuis l'entrée idebut. Sinon, la rampe démarrera depuis la dernière valeur de la note précédente. La valeur par défaut est zéro.
Le fichier newgen.h
comprend une liste de paramètres de sortie et d'entrée
définie sur une ligne. Ce sont les ports par lesquels le nouveau générateur communiquera avec les
autres générateurs dans un instrument. La communication se fait par adresse,
pas par valeur, et c'est une liste de pointeurs sur des valeurs de type
MYFLT (double si la macro USE_DOUBLE est définie, et
float autrement). Il n'y a aucune restriction sur les noms, mais les types
d'argument d'entrée-sortie sont définis plus loin par des chaînes de caractères dans
entry1.c
(intypes, outypes). Les types intypes sont habituellement
x, a, k, et i,
suivant les conventions normales du manuel de Csound ; on trouve aussi o
(facultatif, par défaut 0), p (facultatif, par défaut 1). Les types outypes
comprennent a, k, i et
s (asig ou ksig). Il est important que tous les noms d'argument de la liste
se voient attribuer un type d'argument correspondant dans entry1.c
. De plus,
les arguments de type-i ne sont valides qu'à l'initialisation, et les arguments des autres
types ne sont valables que pendant l'exécution. Les lignes suivantes de la structure RMP
déclarent l'espace de travail nécessaire pour que le code soit réentrant. Ceci permet d'utiliser
le module plusieurs fois dans plusieurs copies d'instrument tout en préservant toutes les
données.
Le fichier newgen.c
contient deux sous-programmes, appelés chacun avec un
pointeur sur l'instance de Csound et un pointeur sur la structure RMP allouée de façon unique et
ses données. Les sous-programmes peuvent être de trois sortes : initialisation de note, génération
de signal au taux-k
, génération de signal au taux-a
.
Normalement, un module requiert deux de ces sous-programmes : initialisation, et un sous-programme
soit de taux-k
, soit de taux-a
qui sera inséré dans
divers listes chaînées de tâches exécutables quand un instrument est activé. Les type de chaînage
apparaissent dans entry1.c
sous deux formes : noms isub,
ksub et asub ; et un index de chaînage qui est la somme
de isub=1, ksub=2, asub=4. Le code lui-même peut référencer (mais ça ne devrait être qu'en lecture)
les membres publiques de la structure CSOUND définie dans csoundCore.h
, dont
les plus utiles sont :
OPARMS *oparms MYFLT esr taux d'échantillonage défini par l'utilisateur MYFLT ekr taux de contrôle défini par l'utilisateur int ksmps ksmps défini par l'utilisateur int nchnls nchnls défini par l'utilisateur int oparms->odebug option -v de la ligne de commande int oparms->msglevel option -m de la ligne de commande MYFLT tpidsr 2 * PI / esr
pour accéder aux tables de fonction en mémoire, une aide spéciale est disponible. La nouvelle structure définie doit comprendre un pointeur
FUNC *ftp;
initialisé par l'instruction
ftp = csound->FTFind(csound, p->ifuncno);
où MYFLT *ifuncno est un argument d'entrée de type-i contenant le numéro de la ftable. La table
stockée est alors en ftp->ftable, et d'autres données comme sa longueur, les masques de phase,
les convertisseurs cps-incrément, sont aussi accessibles depuis ce pointeur. Voir la
structure FUNC dans csoundCore.h
, le code de csoundFTFind() dans
fgens.c
, et le code de oscset() et de koscil() dans
OOps/ugens2.c
.
Parfois les besoins en espace d'un module sont trop grands pour faire partie d'une structure (limite supérieure de 65279 octets, due au paramètre en entier court non-signé dsblksiz et aux codes réservés >= 0xFF00), ou ils dépendent d'une valeur d'argument-i qui n'est pas connue avant l'initialisation. De l'espace supplémentaire peut être alloué dynamiquement et géré proprement en incluant la ligne
AUXCH auxch;
dans la structure définié (*p), puis en utilisant ce type de code dans le module d'initialisation :
csound->AuxAlloc(csound, npoints * sizeof(MYFLT), &p->auxch);
L'adresse de l'espace auxiliaire est gardée dans une chaîne d'espaces similaires appartenant à cet intrument, et elle est gérée automatiquement lorsque l'instrument est dupliqué ou passé au ramasse-miettes durant l'exécution. L'assignation
void *auxp = p->auxch.auxp;
trouvera les espaces alloués pour une utilisation pendant l'initialisation et pendant l'exécution.
Voir la structure LINSEG dans ugens1.h
et le code de lsgset() and klnseg()
dans OOps/ugens1.c
.
Lorsque l'on accède souvent à un fichier externe, ou si on le fait depuis plusieurs endroits, il est souvent efficace de lire le fichier entier dans la mémoire. On accomplit ceci en incluant la ligne
MEMFIL *mfp;
dans la structure définie (*p), puis en utilisant le style de code suivant dans le module d'initialisation :
p->mfp = csound->ldmemfile(csound, nomfic);
où char *nomfic est une chaîne contenant le nom du fichier requis. Les données lues se trouveront entre
(char *)p->mfp->beginp; et (char *)p->mfp->endp;
Les fichiers chargés n'appartiennent pas à un instrument particulier, mais sont automatiquement
partagés pour des accès multiples. Voir la structure ADSYN dans ugens3.h
et le code de adset() et de adsyn() dans OOps/ugens3.c
.
Pour permettre un argument d'entrée de type chaîne (disons MYFLT *inomfic) dans votre structure
définie (*p), assignez-lui le type d'argument S dans
entry1.c
, et incluez le code suivant dans le module d'initialisation :
strcpy(nomfic, (char*)p->inomfic);
Voir le code pour adset() dans OOps/ugens3.c
, lprdset() dans
OOps/ugens5.c
, et pvset() dans OOps/ugens8.c
.
La procédure pour créer un généteur unitaire comme plugin ressemble beaucoup à celle qui est utilisée pour créer un générateur intégré. Le code du générateur unitaire sera le même à part les différences suivantes.
En supposant à nouveau que votre générateur s'appelle newgen
, effectuez les
étapes suivantes :
Ecrivez vos fichiers newgen.c
et newgen.h
comme
vous le feriez pour un générateur unitaire intégré. Mettez ces fichiers dans le répertoire
csound5/Opcodes
.
Mettez #include "csdl.h"
dans les sources de votre générateur
unitaire, au lieu de #include "csoundCore.h"
.
Ajoutez vos champs OENTRY
et les fonctions d'enregistrement du
générateur unitaire au bas de votre fichier C. Exemple (mais vous pouvez avoir autant de générateurs
unitaires que vous le voulez dans un plugin) :
#define S sizeof static OENTRY localops[] = { { { "rampt", S(RMP), 5, "a", "iiio", (SUBR)rampset, (SUBR)NULL, (SUBR)ramp }, }; /* * La macro suivante de csdl.h définit * la fonction d'enregistrement d'opcode "csound_opcode_init()" * pour la table des opcodes locaux. */ LINKAGE
Ajoutez votre plugin comme nouvelle cible dans la section des opcodes en plugin du
fichier de construction SConstruct
:
pluginEnvironment.SharedLibrary('newgen', Split('''Opcodes/newgen.c Opcodes/un_autre_fichier_utilise_par_newgen.c Opcodes/encore_un_autre_fichier_utilise_par_newgen.c'''))
Lancer la construction de Csound de la manière usuelle.
La structure OENTRY
(voir H/csoundCore.h
,
Engine/entry1.c
, et Engine/rdorch.c
) contient les champs publiques
suivants :
opname, dsblksiz, thread, outypes, intypes, iopadr, kopadr, aopadr
dsblksiz
Il y a deux types d'opcode,
polymorphe et non-polymorphe. Pour les opcodes non-polymorphes, le drapeau dsblksiz
spécifie la taille de la structure de l'opcode en octets, et les arguments sont toujours passés à
l'opcode au même taux. Les opcodes polymorphes peuvent accepter des arguments à des taux différents,
et la façon dont ces arguments sont réellement distribués aux autres opcodes est déterminée par le
drapeau dsblksiz
et les conventions de nommage suivantes (note : la liste
suivante est incomplète, voir Engine/entry1.c
pour tous les codes spéciaux
possibles pour dsblksiz) :
0xffff
Le type du premier argument en
sortie détermine quelle fonction de générateur unitaire est réellement appelée :
XXX
-> XXX.a
, XXX.i
, ou
XXX.k
.
0xfffe
Les types des deux premiers arguments
en entrée déterminent quelle fonction de générateur unitaire est réellement appelée :
XXX
-> XXX.aa
, XXX.ak
,
XXX.ka
, ou XXX.kk
, comme dans le générateur unitaire
oscil
.
0xfffd
Fait référence à un argument en
entrée de type a ou k, comme dans le générateur unitaire
peak
.
thread
Spécifie le(s) taux utilisé(s) pour appeler les fonctions de générateur unitaire, comme suit :
Tableau 18. Taux d'appel des ugens selon le paramètre thread
0
|
taux-i ou taux-k (sortie B seulement) |
1
|
taux-i |
2
|
taux-k |
3
|
taux-i et taux-k |
4
|
taux-a |
5
|
taux-i et taux-a |
7
|
taux-i et (taux-k ou taux-a ) |
outypes
Liste les valeurs de retour des
fonctions de générateur unitaire, s'il y en a. Les types permis sont (note : la liste suivante est
incomplète, voir Engine/entry1.c
pour tous les types possibles en sortie) :
Tableau 19. Liste des types de sortie des ugens
i
|
scalaire de taux-i |
k
|
scalaire de taux-k |
a
|
vecteur de taux-a |
x
|
scalaire de taux-k ou vecteur de taux-a |
f
|
type fsig de flux pvoc de taux-f |
m
|
arguments multiples en sortie de taux-a |
intypes
Liste les arguments, s'il y en a,
que prennent les fonctions de générateur unitaire. Les types permis sont (note : la liste suivante est
incomplète, voir Engine/entry1.c
pour tous les types possibles en entrée) :
Tableau 20. Liste des types d'entrée des ugens
i
|
scalaire de taux-i |
k
|
scalaire de taux-k |
a
|
vecteur de taux-a |
x
|
scalaire de taux-a ou vecteur de taux-a |
f
|
type fsig de flux pvoc de taux-f |
S
|
Chaîne |
B
|
|
l
|
|
m
|
Commence une liste indéfinie d'arguments de taux-i
(n'importe quel nombre) |
M
|
Commence une liste indéfinie d'arguments (n'importe quel taux, n'importe quel nombre) |
N
|
Commence une liste indéfinie d'arguments facultatifs (aux
taux-a , -k , -i , ou -S )
(n'importe quel nombre impair) |
n
|
Commence une liste indéfinie d'arguments au taux-i
(n'importe quel nombre impair) |
O
|
facultatif au taux-k , 0 par défaut |
o
|
facultatif au taux-i , 0 par défaut |
p
|
facultatif au taux-i , 1 par défaut |
q
|
facultatif au taux-i , 10 par défaut |
V
|
facultatif au taux-k , 0.5 par défaut |
v
|
facultatif au taux-i , 0.5 par défaut |
j
|
facultatif au taux-i ,-1 par défaut |
h
|
facultatif au taux-i , 127 par défaut |
y
|
Commence une liste indéfinie d'arguments au taux-a
(n'importe quel nombre) |
z
|
Commence une liste indéfinie d'arguments au taux-k
(n'importe quel nombre) |
Z
|
Commence une liste indéfinie d'argumenents alternant les
taux-k et -a (kaka...)
(n'importe quel nombre) |
iopadr
L'adresse de la fonction du
générateur unitaire (de type int (*SUBR)(CSOUND *, void *)
) qui est appelée
à l'initialisation, ou NULL s'il n'y a pas de fonction.
kopadr
L'adresse de la fonction du
générateur unitaire (de type int (*SUBR)(CSOUND *, void *)
) qui est appelée
au taux-k
, ou NULL s'il n'y a pas de fonction.
aopadr
L'adresse de la fonction du
générateur unitaire (de type int (*SUBR)(CSOUND *, void *)
) qui est appelée
au taux-a
, ou NULL s'il n'y a pas de fonction.