crash C - Génération de sons (deuxième partie)
Obligement - L'Amiga au maximum

Samedi 20 avril 2024 - 04:39  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

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


Trucs et astuces

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


Glossaire

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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


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.

génération de sons

génération de sons

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.

génération de sons

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);
}


[Retour en haut] / [Retour aux articles] [Article précédent]