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 - Lecteur 8SVX
(Article écrit par Pascal Amiable et extrait d'Amiga News Tech - mai 1992)
|
|
Peut-on réellement parler de l'audio.device et du son Amiga en général sans aborder le format IFF 8SVX ? A notre avis, non.
Je vous propose donc une étude rapide du format 8SVX avec pour but la réalisation d'un programme permettant de faire
jouer des échantillons à ce format par votre machine favorite. Mais commençons par un petit peu de théorie.
Le composant audio de l'Amiga contient quatre convertisseurs de type DAC (Digital to Analogic Converter) qui lui
permettent de transformer un son numérique avec une résolution de 8 bits en son analogique. Un échantillonnage
sur 8 bits signifie que la courbe sonore est décomposée, suivant la fréquence de numérisation, en échantillons
pouvant prendre 256 valeurs possibles. C'est certes assez faible, mais néanmoins bien suffisant pour des
applications personnelles. A titre d'exemple, la résolution d'un bon synthétiseur est de 12 bits et
celle d'un CD est de 16 bits (65 536 niveaux possibles).
Généralités
Sur Amiga, les échantillons musicaux sont généralement stockés au format IFF 8SVX. Il s'agit du format
standard pour les fichiers 8 bits. Ce format IFF autorise le stockage d'échantillons (ou "samples" en
anglais) dont la taille peut atteindre 2 Mo de longueur.
Malheureusement, ces deux mégaoctets de données ne sont pas restituables directement par l'Amiga sans
un travail préalable. En effet, tous les registres du composant audio de l'Amiga sont de type 16
bits (y compris le registre contenant la taille de l'échantillon à convertir). Ce qui signifie que
la taille maxi de l'échantillon est limitée à 65 536 mots de 16 bits (taille du registre de données),
ce qui nous fait des échantillons de 128 ko au maximum.
Cette limitation matérielle n'était pas une aberration en soit lors de la création de l'Amiga : la
mémoire Chip étant alors limitée à 512 ko, une taille d'échantillon sonore de 128 ko semblait
raisonnable. Mais à l'heure actuelle, où sur certains modèles, la mémoire accessible par les composants
internes est de 2 Mo, cette limite devient gênante.
D'ailleurs, vous avez sans doute dû voir des programmes permettant de jouer des échantillons de taille plus
imposante. C'est tout à fait (Thierry) possible : il suffit de découper le gros échantillon en morceaux ne
dépassant pas 128 ko et de les faire jouer les uns à la suite des autres, en utilisant un mécanisme de
double tampon mémoire pour que la transition soit instantanée et donc indiscernable par l'oreille humaine.
Cette fonctionnalité représente d'ailleurs une amélioration du programme que je vous propose ici et dont
le mécanisme vous sera expliqué en détail le mois prochain.
Notions de bases sur le format 8SVX
Comme tout format IFF, le format 8SVX possède une certaine structure permettant de définir les données
contenues dans le fichier. Le but de ce premier article n'est pas de passer en revue l'ensemble de
ces structures (appelées "chunks" en anglais), mais d'étudier les trois qui sont indispensables à
la réalisation de ce petit lecteur.
- FORM : ce premier bloc de données a deux buts. D'abord, sa présence identifie un fichier de type IFF.
Ensuite, il contient la taille du fichier à charger.
- VHDR : correspond à l'en-tête du fichier. Cette en-tête contient des informations complémentaires
sur le fichier (sous la forme d'autres blocs de données que nous ignorerons dans cet exemple).
- BODY : indique le début de la zone des données du fichier.
Voilà, nous en savons assez pour écrire notre programme d'exemple qui pourra jouer n'importe quel
échantillon au format IFF 8SVX, pour peu que ce dernier ne dépasse pas 128 000 octets.
Le programme que je vous propose fait appel aux includes IFF de monsieur Commodore. Si vous ne les possédez
pas avec votre compilateur, vous pouvez vous les procurer dans le domaine public et plus particulièrement
sur la disquette Fish n°185.
Étude du programme
Cette partie va détailler, d'un point de vue structurel notre programme d'exemple. Il explique les
différents choix effectués sans s'attarder toutefois sur les aspects techniques qui ont été, pour
la plupart, abordés dans les deux numéros précédents.
Notre programme principal va comporter un argument d'entrée qui est le nom du fichier. On déclarera
donc main() avec les traditionnels arguments argc et argv, argc contenant le nombre d'arguments reçus
et argv un tableau de chaînes de caractères contenant les arguments proprement dits.
Le premier test consiste donc à vérifier la présence de cet argument.
Ensuite, on cale la base de temps de restitution sonore sur la fréquence de balayage de l'écran. Afin
d'être totalement rigoureux, il a fallu prendre en compte les deux modes de visualisation de l'Amiga - PAL
et NTSC - dont la fréquence est différente. Cette information est disponible dans le champ
GfxBase->DisplayFlags de la graphics.library. La base de temps est une constante comme indiquée dans
la fonction chargehorloge().
L'étape suivante est l'ouverture du fichier à l'aide de la fonction Open() et l'évaluation du type de
fichier (IFF ou non). Cette évaluation s'effectue en deux temps :
- Vérifier que la taille à une taille au moins égale à 8 octets.
- Vérifier que le premier bloc de données est bien FORM, identifiant un fichier IFF.
A partir de cet instant, on peut allouer une zone mémoire en mémoire Chip
(elle doit être accessible par le composant audio de l'Amiga) de la taille précisée dans le
champ p8chunk->ckSize.
Une fois cette zone allouée, on copie le fichier en mémoire en vérifiant que la longueur
lue correspond bien à la longueur attendue. Sinon, il y a une erreur, le fichier étant sûrement endommagé.
Le fichier IFF est désormais en mémoire mais nous ne savons toujours pas s'il s'agit bien
d'un fichier 8SVX. On va donc tester les quatre octets suivants pour savoir s'il s'agit bien
du bloc de données MY8S, révelateur de la présence d'un fichier 8SVX.
Enfin, il ne reste plus qu'à décoder le reste du fichier à la recherche des en-têtes (VHDR)
et des données proprement dites (BODY). Un fichier 8SVX peut contenir soit un seul échantillon,
soit une reproduction du même échantillon sur plusieurs octaves différentes. On trouve cette
information dans l'en-tête et plus précisément en faisant l'analyse des deux champs
header->oneShotHiSamples et header->repeatHiSamples. Le champs non nul indique la longueur
de l'échantillon et son type puisque seul un de ces deux champs peut être non nul.
La vitesse de transfert des informations sonores est ensuite calculée à partir d'une part, de
la base de temps vidéo, et d'autre part, de la fréquence d'échantillonnage de l'échantillon.
Cette fréquence peut être récupérée dans le champs header->samplesPerSec.
On a donc : vitesse=horloge/header->samplesPerSec.
Enfin, on détermine la priorité de la tâche et on s'alloue le périphérique logique
audio comme indiqué dans le numéro précédent. On prépare l'émission des données et une fois
celle-ci lancée, il ne reste plus qu'à attendre tranquillement la fin de l'émission en écoutant
la douce voix de votre Amiga.
Une fois que tout est fini, on n'oubliera pas de désallouer tout ce qui avait été alloué précédemment
et de libérer le périphérique logique audio pour que d'autres puissent l'utiliser. C'est la fonction
referme() qui se charge de cela.
/*----------------------------------------------------------*/
/* */
/* 8SVX Player Version 1.0 */
/* */
/* Par Herr Doktor Von G... pour l'ANT 1992 */
/* */
/*----------------------------------------------------------*/
/*----------------------------------------------------------*/
/* */
/* Declaration des #includes et #defines */
/* */
/*----------------------------------------------------------*/
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <graphics/gfxbase.h>
#include <iff/iff.h>
#include <iff/8svx.h>
#define VHDR MakeID('V','H','D','R')
#define BODY MakeID('B','O','D','Y')
#define MY8S MakeID('8','S','V','X')
/*----------------------------------------------------------*/
/* */
/* Declaration des variables globales et fonctions */
/* */
/*----------------------------------------------------------*/
struct IOAudio *ioaud1; /* pointeur sur structure IOAudio */
struct IOAudio *Aptr;
struct Message *msg; /* Structure message et port message */
struct MsgPort *port; /* pour la gestio du device audio */
ULONG device; /* le device */
UBYTE *pubfich; /* deux variables pour le stockage en */
ULONG pubtaille; /* PUBLIC RAM pour le traitement */
UBYTE *chipfich; /* deux variables pour le stockage en */
ULONG chiptaille; /* CHIP RAM de l'échantillon */
struct FileHandle *gestfich; /* la structure de gestion du fichier */
UBYTE canal1[] = {1}; /* La table de répartition des canaux */
UBYTE canal2[] = {2}; /* un seul est alloué sans préférence */
UBYTE canal3[] = {3};
UBYTE canal4[] = {4};
UBYTE *canal[] = {canal1,canal2,canal3,canal4};
APTR OpenLibrary();
APTR AllocMem();
struct FileHandle *Open();
struct Task *FindTask();
struct MsgPort *CreatePort();
struct Message *GetMsg();
ULONG chargehorloge();
void referme();
/*----------------------------------------------------------*/
/* */
/* Programme principal */
/* */
/*----------------------------------------------------------*/
LONG main(argc,argv)
LONG argc; /* nombre d'arguments d'entrées */
char *argv[]; /* tableau des arguments d'entrées */
{
ULONG horloge;
char *nomfich;
UBYTE *donnee;
BYTE iobuffer[8];
BYTE *echant[2];
ULONG length[2];
Chunk *p8Chunk;
Voice8Header *pVoice8Header;
ULONG y,compteur,vitesse;
ULONG wakebit;
BYTE ancpri,c;
struct Task *mt;
/* initialisation des variables globales */
nomfich = 0L;
device = 1L;
pubfich = 0L;
chipfich = 0L;
ioaud1 = 0L;
gestfich = 0L;
/* test des arguments d'entrée (le nom du fichier) */
if(argv[1] == NULL)
{
referme("aucun nom de fichier donné\n");
exit(1);
}
nomfich = argv[1]; /* On récupère le nom du fichier */
/* Initialisation de la base de temps en fonction de l'affichage */
horloge = chargehorloge();
/* Ouverture du fichier */
gestfich = Open(nomfich,MODE_OLDFILE);
if(gestfich == 0)
{
referme("Impossible d'ouvrir le fichier\n");
exit(1);
}
/* Lecture des 8 premiers octets et évaluation du type de fichier */
compteur = Read(gestfich,iobuffer,8); /* On lit les 8 premiers octets */
if(compteur == -1)
{
referme("Impossible de lire le fichier\n");
exit(1);
}
if(compteur < 8)
{
referme("Fichier trop court\n");
exit(1);
}
p8Chunk = (Chunk *)iobuffer;
/* on teste s'il s'agit bine d'un fichier IFF qui possède */
/* obligatoirement la directive FORM en tête de fichier */
if(p8Chunk->ckID != FORM)
{
referme("Ce n'est pas un fichier IFF\n");
exit(1);
}
/* allocation d'une zone mémoire pour stockage du fichier */
/* Cette zone doit obligatoirement être en CHIP RAM */
pubfich = (UBYTE * )AllocMem(pubtaille=p8Chunk->ckSize,MEMF_PUBLIC|MEMF_CLEAR);
if(!pubfich)
{
referme("Allocation mémoire impossible\n");
exit(1);
}
donnee = pubfich;
/* Lecture du fichier et stockage en RAM */
compteur = Read(gestfich,donnee,p8Chunk->ckSize);
if(compteur == -1)
{
referme("Impossible de lire le fichier\n");
exit(1);
}
/* On teste si le fichier est correct à son descripteur */
if(compteur < p8Chunk->ckSize)
{
referme("Fichier IFF trop court\n");
exit(1);
}
/* Evaluation du type IFF pour déterminer si il est ou non de type 8SVX */
if(MakeID(donnee[0],donnee[1],donnee[2],donnee[3]) != MY8S)
{
referme("Ce n'es pas un fichier IFF 8SVX\n");
exit(1);
}
/* Evaluation des chunk 8SVX */
donnee = donnee + 4;
while(donnee < pubfich+pubtaille)
{
p8Chunk = (Chunk *)donnee;
switch(p8Chunk->ckID)
{
case VHDR: /* On vient de trouver un Header */
pVoice8Header = (Voice8Header *)(donnee+8L);
break;
case BODY: /* On découvre le début d'un bloc de données */
echant[0] = (BYTE *)(donnee + 8L);
echant[1] = echant[0] + pVoice8Header->oneShotHiSamples;
length[0] = (ULONG)pVoice8Header->oneShotHiSamples;
length[1] = (ULONG)pVoice8Header->repeatHiSamples;
break;
default:
break;
}
donnee = donnee + 8L + p8Chunk->ckSize;
if(p8Chunk->ckSize&1L == 1)
donnee++;
}
/* si length[0] = 0 -> multioctave sinon monooctave */
if (length[0] == 0)
y = 1;
else
y = 0;
if(length[y] <= 128000)
{
chiptaille = length[y];
if(length[0] == 0 && length[1] == 0)
{
referme("Problème de décodage du fichier \n");
exit(1);
}
chipfich = (UBYTE *)AllocMem(chiptaille,MEMF_CHIP|MEMF_CLEAR);
if(chipfich ==0)
{
referme("Pas de memoire chip disponible\n");
exit(1);
}
CopyMem(echant[y],chipfich,chiptaille);
echant[y] += chiptaille;
}
else
{
referme("Sample trop gros\n");
exit(1);;
}
/* calcul de la fréquence de restitution du son gràce au balayage video */
/* et à la fréquence d'écanaltillonage indiquée dans le fichier */
vitesse = horloge / pVoice8Header->samplesPerSec;
/* Mise en place de la priorité */
mt = FindTask(NULL);
ancpri = SetTaskPri(mt,21);
/* Allocation du canal audio */
ioaud1 = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR);
if(!ioaud1)
{
referme("Impossible d'allouer le canal audio\n");
exit(1);
}
/* Création du port de données */
port = CreatePort(0,0);
if(!port)
{
referme("Pas de port disponible\n");
exit(1);
}
c = 0;
while(device)
{
ioaud1->ioa_Request.io_Message.mn_ReplyPort = port;
ioaud1->ioa_Request.io_Message.mn_Node.ln_Pri = 128;
ioaud1->ioa_AllocKey = 0;
ioaud1->ioa_Data = canal[c];
ioaud1->ioa_Length = 1;
/* Ouverture du device audio */
device = OpenDevice("audio.device",0,ioaud1,0);
c++;
}
/* Si device vaut zéro cela signifie qu'aucun canal n'est disponible */
if(device)
{
referme("Pas de canal disponible\n");
exit(1);
}
/* Initialisation des blocs IO Audio pour la restitution du sample */
ioaud1->ioa_Request.io_Command = CMD_WRITE;
ioaud1->ioa_Request.io_Flags = ADIOF_PERVOL;
/* Valuation du volume */
ioaud1->ioa_Volume = 50;
/* de la periode de restitution */
ioaud1->ioa_Period = (UWORD)vitesse;
ioaud1->ioa_Cycles = 1;
/* Enfin des données à jouer */
ioaud1->ioa_Data = (UBYTE *)chipfich;
Aptr = ioaud1;
/* On envoie la purée, on a vérifié avant la taille pour qu'elle */
/* n'excède pas 128000 octets */
ioaud1->ioa_Length = length[y];
BeginIO(ioaud1);
wakebit = 0L;
wakebit = Wait(1 << port->mp_SigBit);
msg = GetMsg(port);
referme(" ");
return(0L);
}
/*----------------------------------------------------------*/
/* */
/* chargement de la base de temps */
/* */
/*----------------------------------------------------------*/
ULONG chargehorloge()
{
struct GfxBase *GfxBase;
GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0L);
if(!GfxBase)
{
puts("Je ne peux pas ouvrir la library graphics !\n");
exit(1);
}
if(GfxBase->DisplayFlags & PAL)
{
CloseLibrary(GfxBase);
return(3546895L);
}
if(!(GfxBase->DisplayFlags & PAL))
{
CloseLibrary(GfxBase);
return(3579545L);
}
}
/*----------------------------------------------------------*/
/* */
/* Fonction de désallocation des ressources */
/* */
/*----------------------------------------------------------*/
void referme(message)
char *message;
{
puts(message);
if(gestfich) Close(gestfich);
if(pubfich) FreeMem(pubfich,pubtaille);
if(chipfich) FreeMem(chipfich,chiptaille);
if(!device) CloseDevice(ioaud1);
if(port) DeletePort(port);
if(ioaud1) FreeMem(ioaud1,sizeof(struct IOAudio));
}
|
Ainsi se termine cette première partie de l'étude du format 8SVX, vous êtes maintenant en mesure de
jouer n'importe quel échantillon audio sur un des canaux de l'audio.device. Le mois prochain,
nous verrons comment reproduire un son d'une taille supérieure à 128 ko et en stéréo s'il
vous plaît. En attendant, je vous souhaite un excellent mois et à bientôt.
|