Suivez-nous sur X

|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : C - Génération de sons (deuxième partie)
(Article écrit par Pascal Amiable et extrait d'Amiga News Tech - mars 1992)
|
|
Suite à l'article du mois dernier concernant la programmation du composant
audio de l'Amiga en C, nous allons étudier ce mois-ci un minimum de théorie sur la génération d'ondes sonores,
puis terminer par un programme d'exemple qui va faire du bruit...
Afin d'appréhender la manière de créer des données audio pour l'Amiga, il est nécessaire de comprendre quelques notions
sur la génération de sons synthétiques. Ces notions découlent directement de la théorie des ondes. Pour l'Amiga,
produire un son n'est possible qu'après avoir défini trois choses : sa forme d'onde, sa période d'échantillonnage
(qui va déterminer sa tonalité) et son volume de sortie. Nous allons donc décrire chacune de ces données d'entrée
avant de les appliquer à un cas d'école, représenté par le programme ci-joint.
Définition de la forme d'onde
Toute onde, sonore ou non, est caractérisée par une forme, qui est une représentation de la variation
d'amplitude en fonction du temps. Cette représentation, qui peut être une équation mathématique exacte (comme
une sinusoïde pour le courant alternatif) ou plus complexe (dans notre cas, approximée par des séries de Fourier),
ne va pas être introduite telle quelle dans l'Amiga, mais sous forme d'un tableau de valeurs définies à un instant
donné. La forme d'onde triangulaire qui nous sert d'exemple (voir figure 1) peut être échantillonnée par le tableau
de valeurs suivant.
Pour générer la forme d'onde, le nombre d'échantillons doit être obligatoirement pair. Chaque valeur est représentée
sous la forme d'un octet signé, autorisant donc des valeurs comprises entre -128 et +127. Pour obtenir la meilleure
qualité possible, il est préférable d'utiliser toute l'échelle d'amplitude, le volume pouvant être réglé à posteriori
selon votre désir.
Cette forme d'onde échantillonnée sera ensuite convertie en forme d'onde analogique par les composants audio de
l'Amiga, puis transmise aux hauts parleurs afin de produire un son.
La période d'échantillonnage
Comme nous l'avons vu rapidement le mois dernier, un son est également défini par une fréquence. Plus la
fréquence est élevée, plus le son est aigu, et inversement : plus elle est faible, plus il est grave. Pour réaliser
ces variations, il est possible de piloter la fréquence d'échantillonnage de sortie du son. Plus la fréquence
d'échantillonnage est élevée, plus le taux de répétition du signal sera grand, ce qui entraînera une sortie à
haute fréquence du son. L'inverse est également vrai pour les sons graves, à basse fréquence.
Sur l'Amiga, on spécifiera non pas la fréquence, mais la période d'échantillonnage. Ce qui signifie que la valeur mise dans
le registre de période est inversement proportionnel à la fréquence de sortie du son. Ainsi, si vous désirez une fréquence de
sortie élevée, vous indiquerez une période faible et réciproquement. Le champ de variation de la période est compris entre 1 et
126.
Contrôle du volume
Après avoir défini l'onde sous la forme d'un tableau d'échantillons puis la fréquence de sortie sous la forme
d'une période d'échantillonnage, il ne reste plus qu'à déterminer le volume de sortie du son.
Chaque canal possède un registre de contrôle du volume. Ce registre peut contenir des valeurs comprises entre 0 et 64,
où 0 signifie le volume minimum et 64 le maximum. Ce registre est généralement utilisé pour réaliser des effets
sonores tels les crescendos et decrescendos.
De la théorie a la pratique
Lorsque vous utilisez la commande CMD_WRITE de l'audio.device, vous avez la possibilité de spécifier le nombre de
fois que vous souhaitez reproduire la même forme d'onde avant de passer à la suivante. Ce nombre est à spécifier
dans le champ ioa_Cycles de la structure IOAudio.
La transition entre deux formes d'ondes, qu'elles soient du même type ou de type différent, doit être la moins
sensible possible. Pour obtenir une transition en douceur, deux règles doivent être appliquées :
- L'amplitude de démarrage et de fin d'une forme d'onde doit être la plus proche possible de zéro.
- La "pente" de fin d'une onde doit être dans le même sens que la pente de début de la forme suivante
(négative ou positive toutes les deux).
La figure 2 illustre ces deux règles.
Pour que l'Amiga puisse reproduire le son désiré, il doit pouvoir lire ses données. Celles-ci doivent obligatoirement
être stockées dans la zone mémoire accessible au composant audio, c'est-à-dire en mémoire Chip.
Deux solutions s'offrent pour réaliser cela :
- Vous possédez un compilateur C gérant la directive "chip" (comme le SAS/Lattice, par exemple),
et dans ce cas, il suffit de déclarer vos données comme "chip UBYTE donnees_sonores[];".
Le compilateur se chargera alors de stocker le tableau donnees_sonores en mémoire Chip.
- Vous ne possédez pas cet ordre (ou bien vous voulez simplement programmer "portable",
le mot-clé "chip" n'étant absolument pas standard). Dans ce cas, vous devez allouer une zone
mémoire de type MEMF_CHIP (Cf. la fonction AllocMem() d'Exec) de la taille voulue et y
copier les données ou les charger depuis le disque.
De plus, les données audio doivent avoir une taille paire, car le transfert DMA vers le
composant audio s'effectue toujours par mots de 16 bits.
Enfin, dernier point, le premier octet doit obligatoirement se trouver à une adresse mémoire paire,
cette dernière condition étant automatiquement prise en compte par la fonction AllocMem().
Les quelques lignes de langage C qui réalisent cette allocation sont bien entendu présentes
dans le programme d'exemple.
Le programme d'exemple
Ce programme utilise les fonctions décrites dans le précédent article (le source étant rappelé
pour mémoire). Il représente le squelette essentiel de tout programme musical sur Amiga. Après
l'avoir tapé et fait fonctionner, je vous conseille de faire varier les différents paramètres
(forme d'onde, volume, nombre de répétitions, fréquence....) afin de mieux appréhender leur
utilisation.
Ainsi ce termine ce rapide survol de la programmation audio en C sur Amiga. Le mois prochain,
nous illustrerons ce que nous avons appris durant ces deux mois grâce à une application
originale de la théorie fractale pour la génération de sons.
/*---------------------------------------------------------------*/
/* Exemple d'utilisation des fonctions audio */
/* Amiga Newstech 1992 */
/* Par Herr Doktor Von GlutenStimmelImDorf */
/*---------------------------------------------------------------*/
/*---------------------------------------------------------------*/
/* Déclaration des includes utiles */
/*---------------------------------------------------------------*/
#include <stdio.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/audio.h>
#define NBECH 8 /* il s'agit du nombre d'échantillon qui doit être multiple de 2 */
/*---------------------------------------------------------------*/
/* Déclaration des fonctions et variables globales */
/*---------------------------------------------------------------*/
struct IOAudio *allocaudio();
void freeaudio(), verroucanal(), deverroucanal(),referme();
extern struct MsgPort *CreatePort();
UBYTE allocanaux();
BYTE forme_onde[] = { 0, 63, 126, 63, 0, -63, -126, -63 }; /* forme d'onde triangulaire */
UWORD periode[] = { 500, 400, 300, 200 }; /* 4 période d'échantillonnage différente */
struct Device *audio = 0;
BYTE *onde = 0;
struct IOAudio *ioaud[4]; /* on va travailler avec les quatre canaux */
struct IOAudio *lock[4];
struct IOAudio *free[4];
struct MsgPort *replyport, *lockport;
/*---------------------------------------------------------------*/
/* Programme principal */
/*---------------------------------------------------------------*/
int main()
{
int i,canal,erreur,longueur;
UBYTE canaux[4];
BYTE *source, *destination; /* utiliser pour le transfert ver la CHIP RAM */
struct Unit *audiounit;
for(i=0;i<4;i++)
{
ioaud[i] = allocaudio();
lock[i] = allocaudio();
free[i] = allocaudio();
if(!ioaud[i] || !lock[i] || !free[i])
referme("Allocation mémoire impossible");
}
onde = (BYTE *)AllocMem(NBECH,MEMF_CHIP); /* on s'alloue la zone CHIP */
if(!onde)
referme("Allocation mémoire impossible");
source = forme_onde;
destination = onde;
for (i=0; i< NBECH; i++) /* et on effectue la copie des données */
*destination++ = *source++;
erreur = OpenDevice("audio.device",0,ioaud[0],0); /* on ouvre le device audio */
if(erreur)
referme("Impossible d'ouvrir l'audio.device");
/* sauvegarde de l'adresse du device */
audio = ioaud[0]->ioa_Request.io_Device;
/* création de deux ports msg pour la gestion du device */
replyport = CreatePort(0,0);
lockport = CreatePort(0,0);
if((!replyport) || (!lockport))
referme("Impossible de créer un port");
/* Initialisation des champs "ports" et "device" de chaque */
/* bloc requete audio */
for (i=0; i<4; i++)
{
ioaud[i]->ioa_Request.io_Device = audio;
lock[i]->ioa_Request.io_Device = audio;
ioaud[i]->ioa_Request.io_Message.mn_ReplyPort = replyport;
lock[i]->ioa_Request.io_Message.mn_ReplyPort = lockport;
}
longueur = NBECH/2; /* longueur de la forme d'onde en mots de 16 bits. */
canaux[0] = 1;
canaux[1] = 2; /* table d'allocation pour les quatre canaux */
canaux[2] = 4;
canaux[3] = 8;
for(i=0;i<4;i++)
{
canal = allocanaux(ioaud[i],canaux,longueur);
printf("Je prends le canal %ld\n",canal);
/* chargement de la clef d'allocation */
lock[i]->ioa_AllocKey = ioaud[i]->ioa_AllocKey;
/* chargement du numéro d'unité */
audiounit = ioaud[i]->ioa_Request.io_Unit;
lock[i]->ioa_Request.io_Unit = audiounit;
verroucanal(ioaud[i],canal);
if(CheckIO(lock[i]))
referme("quelqu'un a fauché un canal");
/* cette routine renvoie TRUE si un canal a été déverouillé */
}
/* maintenant que tout est initialisé on va préparer la requète sonore */
for(i=0;i<4;i++)
{
ioaud[i]->ioa_Data = (UBYTE *)onde; /* donnée du signal */
ioaud[i]->ioa_Length = NBECH/2; /* longeur en WORD */
ioaud[i]->ioa_Period = periode[i]; /* cf table des periodes ci-dessus */
ioaud[i]->ioa_Volume = 64; /* Volume maximum */
ioaud[i]->ioa_Cycles = 10000; /* 10000 fois */
/* Maintenant on passe les commandes */
ioaud[i]->ioa_Request.io_Command = CMD_WRITE;
ioaud[i]->ioa_Request.io_Flags = ADIOF_PERVOL;
/* on sauvegarde le bloc audio pour le libérer plus tard */
*free[i] = *ioaud[i];
/* On envoie la requète */
printf("La requète est envoyée\n");
BeginIO(ioaud[i]);
}
for(i=0;i<4;i++)
{
WaitIO(ioaud[i]); /* on attend que ce soit fini. */
}
/* on déverrouille les canaux */
for(i=0;i<4;i++)
{
deverroucanal(ioaud[i]);
printf("Deverrouillage d'un canal\n");
}
referme("c'est fini\n");
}
/*---------------------------------------------------------------*/
/* Fonction d'allocation d'une structure IOAudio */
/*---------------------------------------------------------------*/
struct IOAudio *allocaudio()
{
struct IOAudio *ioaud;
ioaud = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),
MEMF_CHIP | MEMF_CLEAR);
return(ioaud); /* On renvoie 0 si plus de mémoire */
}
/*---------------------------------------------------------------*/
/* Fonction de libération d'une structure IOAudio */
/*---------------------------------------------------------------*/
void freeaudio(ioaud)
struct IOAudio *ioaud;
{
if(ioaud)
FreeMem(ioaud,sizeof(struct IOAudio));
}
/*---------------------------------------------------------------*/
/* Libération des ressources et gestion des erreurs */
/*---------------------------------------------------------------*/
void referme(erreur)
char *erreur;
{
int i;
if(audio)
CloseDevice(ioaud[0]); /* on referme le device audio */
if(onde);
FreeMem(onde,NBECH); /* on désalloue la zone CHIP */
for (i=0;i<4;i++)
{
freeaudio(ioaud[i]); /* on libère l'ensemble des structure IOAudio allouées */
freeaudio(lock[i]);
freeaudio(free[i]);
}
if(replyport)
DeletePort(replyport); /* on détruit les ports de transmission de messages */
if(lockport)
DeletePort(lockport);
printf("%s\n",erreur);
exit(1); /* c'est fini */
}
/*---------------------------------------------------------------*/
/* Verrouillage d'un canal audio */
/*---------------------------------------------------------------*/
void verroucanal(ioaud,canal)
struct IOAudio *ioaud;
UBYTE canal;
{
ioaud->ioa_Request.io_Command = ADCMD_LOCK;
ioaud->ioa_Request.io_Flags = 0;
BeginIO(ioaud);
}
/*---------------------------------------------------------------*/
/* Déverouillage d'un canal audio */
/*---------------------------------------------------------------*/
void deverroucanal(ioaud)
struct IOAudio *ioaud;
{
/* Attention, le numéro de Unit et la clef d'allocation doivent être */
/* correctes sous peine d'un krash spectaculaire */
ioaud->ioa_Request.io_Command = ADCMD_FREE;
ioaud->ioa_Request.io_Flags = IOF_QUICK;
BeginIO(ioaud);
WaitIO(ioaud);
}
/*---------------------------------------------------------------*/
/* Récupération d'un canal audio */
/*---------------------------------------------------------------*/
UBYTE allocanaux(ioaud,canaux,longueur)
struct IOAudio *ioaud;
UBYTE *canaux;
SHORT longueur;
{
int erreur;
UBYTE canal;
ioaud->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
ioaud->ioa_Request.io_Command = ADCMD_ALLOCATE;
ioaud->ioa_Data = (UBYTE *)canaux;
ioaud->ioa_Length = longueur;
ioaud->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
BeginIO(ioaud);
erreur = WaitIO(ioaud); /* différent de zéro si erreur */
if(!(ioaud->ioa_Request.io_Flags & IOF_QUICK))
GetMsg(ioaud->ioa_Request.io_Message.mn_ReplyPort);
if(erreur)
{
printf("coucou\n");
return(0);
}
canal = ((ULONG)(ioaud->ioa_Request.io_Unit)) & 0xFF;
return(canal);
}
|
Mise à jour de mai 2025 : une archive contenant le listing adapté à vbcc, et avec l'exécutable
compilé par vbcc, a été réalisée par Yann-Gaël Guéhéneuc et est disponible sur
obligement.free.fr/files/antsoundgeneration.lha.
|