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 SMUS
(Article écrit par Pascal Amiable et extrait d'Amiga News Tech - août 1992)
|
|
Suite logique de l'étude du format 8SVX, cet article concerne le format de
gestion des partitions musicales sur Amiga. Il s'agit du format SMUS qui est utilisé par presque tous les logiciels
du commerce, dont le célèbre Deluxe Music.
Le format SMUS a été créé pour pouvoir composer et jouer des morceaux de musique "classique".
Cela signifie que les évènements musicaux ont été encodés pour respecter le format classique musical
(partitions, mesures, clefs, notes...). De par le faible nombre de types d'évènements différents
apparaissant dans une partition, le codage des informations est très compact et il ne faut que très
peu de place mémoire pour stocker une oeuvre musicale importante.
A cette notion d'évènement ("event"
en anglais) se superpose une autre notion essentielle, celle de piste (track). Une piste musicale (terme très
utilisé dans le monde des synthétiseurs) décrit une suite d'évènements musicaux en fonction du temps.
Cette suite est linéaire et en aucun cas deux évènements ne peuvent se superposer : il s'agit soit d'une note unique,
soit d'un accord dont les différentes notes qui le composent débutent et se terminent au même moment.
Ces différentes pistes peuvent ensuite être superposées pour former une partition polyphonique où plusieurs
instruments peuvent jouer simultanément. Bien entendu, le nombre maximum de pistes superposables dépend directement,
pour leur restitution, du nombre de canaux sonores disponibles sur l'ordinateur.
Ainsi, Deluxe Music restituera un maximum de quatre canaux directement sur l'Amiga, alors qu'il pourra en
restituer 16 sur un synthétiseur via l'interface MIDI. Le format SMUS en prévoit jusqu'à 255.
Chaque piste est stockée dans un bloc de données TRAK. Le nombre de pistes est indiqué dans
un bloc de données SHDR (Score HeaDeR) qui se trouve au début du fichier. Ces blocs de données
TRAK sont stockés de manière ordonnée (1, 2, 3...). Il s'agit d'un ordre des priorités, la piste
la plus importante étant jouée en premier.
Ainsi, un programme de restitution pouvant jouer par exemple quatre pistes, restituera les
quatre premières et ignorera simplement les suivantes.
Les instruments
Une autre notion importante associée au format SMUS concerne la gestion des instruments. L'instrument
est le moyen de restitution des notes présentes sur la piste. Afin de déterminer quel instrument va
être utilisé pour restituer une note, il est nécessaire de définir la notion d'instrument courant.
Si aucune commande spécifique n'est passée, l'instrument utilisé est l'instrument par défaut qui est
défini grâce au bloc de données INS1 (voir ci-dessous). Sinon, on pointera vers un registre d'instrument,
registre qui peut être utilisé par plusieurs instruments simultanément.
Les données associées aux instruments dépendent de l'environnement de restitution (Amiga ou synthétiseurs
MIDI). Dans tous les cas, il est nécessaire de rendre la partition indépendante de cet environnement.
C'est pour cela qu'est apparue cette notion de registre d'instrument. Ce registre contient le nom de
l'instrument, et pointe à son tour vers ses données réelles, quel que soit leur type. La gestion
de ces données est laissée au programme de restitution.
Le schéma ci-dessous résume la manière dont sont gérés les instruments dans le format SMUS.
Gestion des instruments
Les blocs de données FORM et SHDR
Associé au bloc de données FORM (décrit dans les précédents articles), on trouve le bloc
de données SHDR définissant les données de base de la partition. La structure ScoreHeader
associée au bloc de données est la suivante :
Le champ "tempo" représente le tempo initial du morceau à jouer et s'applique à toutes les pistes.
Il s'exprime en 128e de noires par minute. Ainsi, un tempo de 1 représente une noire toutes les
128 minutes, alors que 12 800 représente 100 noires par minute. Ce mot de 16 bits représente en
fait un nombre décimal réel, les neuf bits de poids fort représentant la partie entière et les
sept bits de poids faible la partie décimale. Ce tempo global peut être modifié en cours de morceau
grâce à une marque dynamique SEvents que l'on trouve dans la description d'une piste.
Le champ volume représente le volume de sortie du morceau, qui est compris entre 0 et 127.
Ce volume peut également être modifié grâce à un ordre SEvents.
Enfin, le champ "ctTrack" indique le nombre de pistes différentes que possède le morceau de musique.
Les blocs de données NAME, (c), AUTH, ANNO
Les blocs de données NAME, (c), AUTH et ANNO sont identiques à ceux utilisés pour le format
8SVX et ne seront pas décrits en détail dans cet article. Voici pour mémoire leurs déclarations.
Le bloc de données INS1
Ce bloc de données identifie l'instrument qui va être utilisé par le morceau. Il s'agit d'un champ
optionnel qui peut être ignoré par le programme de restitution (le lecteur). La structure du
bloc de données est la suivante :
Ce type de bloc de données associe à un numéro de registre instrumental (champ "register"),
un nom d'instrument (champ "name[]"). Il peut y avoir jusqu'à 255 registres musicaux dans
une partition, mais en général, seuls quelques-uns sont utilisés. Afin d'aider à la localisation
d'un instrument MIDI référencé par ce bloc de données, il existe deux nombres, data1
et data2. Si le champ "type" vaut "INS1_Name", alors on cherche l'instrument par son nom
et data1 et data2 ne servent à rien (on les initialisera à 0). Si type vaut
"INS1_MIDI", alors on devra rechercher l'instrument ayant pour preset data2
sur le canal MIDI data1.
Il est à noter que le bloc de données INS1 remplace le bloc de données INST
qui devient donc obsolete et hors norme.
Le bloc de données TRAK
Le principal contenu de la partition SMUS est stocké dans les bloc de données TRAK, qui
représentent les pistes de la partition. Il doit y avoir au moins autant de blocs de données
TRAK dans le fichier IFF que de pistes dans la partition.
Le contenu d'un bloc de données
TRAK est représenté par un tableau de mots de 16 bits définissant chacun un évènement (note,
silence, changement d'instrument, de tempo, de volume...). Cet évènement représente une série
d'ordres qui sont exécutés en séquence. Un bloc de données TRAK peut être polyphonique, c'est-à-dire
contenir des accords en tenant compte des restrictions énoncées ci-dessus.
Chaque évènement est stocké dans un enregistrement SEvent (pour Simple musical Event). Chaque
enregistrement est constitué d'un champ type codé sur 8 bits et représentant le type d'évènement
à exécuter. Le second champ représente, quant à lui, les données associées à la commande.
Ce format est ainsi extensible et permettra dans le futur de définir de nouveaux évènements.
Deux évènements doivent toutefois être compris par n'importe quel programme, ce sont les
évènements "note" et "rest".
Avant d'entamer la réalisation d'un programme d'exemple, il est nécessaire de détailler deux
SEvents qui nous intéressent plus particulièrement : Note et Rest.
Ces deux évènements possèdent la même structure, ce qui est normal compte tenu de leur similitude.
Cette implémentation sous forme de champs de bits "Unsigned :n" est compactée bits à bits,
du poids le plus fort au plus faible, pour totaliser comme toutes les structures SEvents,
16 bits au total.
Certains compilateurs interprétant mal cette forme de structure bits à bits vous pouvez la
remplacer par deux UBYTES et gérer vous-même le deuxième en tenant compte du schéma explicatif ci-dessous.
Détermination du type de note
Le programme d'exemple
Le programme que nous allons réaliser restitue une partition au format SMUS en utilisant des instruments
au format 8SVX, instruments qui sont bien entendu spécifiés dans le fichier SMUS. Les fichiers 8SVX seront
décodés sous la forme d'une série d'échantillons étalée sur huit octaves. Si ces derniers n'étaient pas enregistrés
sur cette base, les octaves manquant seraient remplacés en utilisant les plus proches disponibles.
Une table de périodes de douze éléments donne la vitesse de restitution pour un octave, couvrant ainsi les
douze tons de ce dernier.
La première partie du programme d'exemple correspond au main(). Elle contient la trame du lecteur
ainsi que les déclarations des variables globales.
/*------------------------------------------*/
/* Player IFF SMUS V 1.0 */
/* (c) ANT 1992 */
/*------------------------------------------*/
#include <exec/types.h>
#include <exec/memory.h>
#include <graphics/gfxbase.h>
#include <libraries/dosextens.h>
#include <devices/audio.h>
#include <devices/timer.h>
#include <iff/iff.h>
#include <iff/smus.h>
#include <iff/8svx.h>
#define SMUS MakeID('S','M','U','S') /* chunk form SMUS */
#define SHDR MakeID('S','H','D','R') /* chunk ScoreHeader décrivant les données de base */
#define INS1 MakeID('I','N','S','1') /* chunk de description des instruments */
#define TRAK MakeID('T','R','A','K') /* chunk décrivant une piste */
#define I8SVX MakeID('8','S','V','X') /* chunk form 8SVX */
#define VHDR MakeID('V','H','D','R') /* chunk VoiceHeader pour l'instrument */
#define BODY MakeID('B','O','D','Y') /* chunk des données associées à l'instruments */
#define LIMIT_INSTRUMENT 8
struct GfxBase *OpenLibrary(); /* Ouverture des bibliothèques */
struct GfxBase *GfxBase; /* Pointeur sue la bibliothèque graphique */
struct Message *GetMsg(); /* Récupération de message */
struct MsgPort *CreatePort(); /* Création d'un port */
struct IORequest *CreateExtIO(); /* Création du timer */
struct Task *FindTask(); /* recherche de taches */
struct FileHandle *Open(), /* pour l'ouverture du fichier */
*inshandle; /* gestion d'un instrument */
APTR AllocMem(); /* pour l'allocation memoire */
ULONG notes(); /* jouer les SEvents */
void finaudio(); /* Arret du morceau */
ULONG initaudio(); /* initialisation audio */
ULONG chargeinst(); /* chargement d'un instrument */
void referme(); /* desallocation générale */
LONG arret(); /* gestion de l'arret user */
void dechinst(); /* dechargement d'un instrument */
ULONG calcul(); /* calcul de la note */
/*------------------------------------------*/
/* Définition des variables globales */
/*------------------------------------------*/
struct FileHandle *smusfichier; /* pointeur sur le fichier SMUS */
UBYTE *sdonnee; /* donnees associées au fichier SMUS */
ChunkHeader *smusheader; /* header SMUS */
SEvent *piste[8]; /* tableau des pistes */
LONG tcompteur; /* compteur de piste */
LONG icompteur, horloge; /* compteur et constante d'horloge */
UBYTE *idonnee; /* pointeur vers les données du fichier 8SVX */
Voice8Header *instheader; /* header d'un instrument 8SVX */
Chunk *instchunk; /* chunk d'instrument */
LONG taillefich,numinst,scompteur;
UBYTE *sechant,*sechants[LIMIT_INSTRUMENT]; /* pointeur vers les blocs d'échantillons */
LONG *ptabptr, *ptabptrs[LIMIT_INSTRUMENT],staille,stailles[LIMIT_INSTRUMENT],longueur[16*LIMIT_INSTRUMENT];
BYTE *echant[16*LIMIT_INSTRUMENT];
/*LONG frequence[] = { 41860,44540,46986,49780,52740,55860,
59200,62720,66448,70400,74586,79022};*/
struct IOAudio *ioaud[20]; /* 2 attaques, 2 parties principales, */
struct Message *msg; /* 1 descente x 4 canaux = 20 */
struct MsgPort *port[4]; /* port de gestion de message pour les ioauds */
struct MsgPort *tport[4];
ULONG device[4]; /* device */
struct timerequest *treq[4]; /* timers */
ULONG timer[4];
UBYTE inreg[4];
UBYTE canal1[] = {1};
UBYTE canal2[] = {2};
UBYTE canal3[] = {4};
UBYTE canal4[] = {8};
UBYTE *canaux[] = {canal1,canal2,canal3,canal4};
ULONG ttable[] = {26880,13440, 6720, 3360, 1680, 840, 420, 210,
40320,20160,10080, 5040, 2520, 1260, 630, 315,
17920, 8960, 4480, 2240, 1120, 560, 280, 140,
26880,13440, 6720, 3360, 1680, 840, 420, 210,
21504,10752, 5376, 2688, 1344, 672, 336, 168,
32256,16128, 8064, 4032, 2016, 1008, 504, 252,
23040,11520, 5760, 2880, 1440, 720, 360, 180,
34560,17280, 8640, 4320, 2160, 1080, 540, 170};
/*------------------------------------------*/
/* Programme principal */
/*------------------------------------------*/
LONG main(argc,argv)
LONG argc;
char *argv[];
{
SEvent *sevent;
RefInstrument *instref;
SScoreHeader *partheader;
Chunk *chunk;
char *nom,instnom[60],iobuffer[8];
LONG i,lcompteur,compteur,gamme;
ULONG etat;
onbreak(&arret);
/* On commence par tester la validité des instruments d'entrée */
smusfichier = 0;
scompteur = 0;
numinst = 0;
etat = 1;
if(argv[1] == NULL)
referme("Aucun nom de fichier en entrée\n");
/* On lit le fichier */
nom = argv[1];
if(!(smusfichier = Open(nom,MODE_OLDFILE)))
referme("Je n'arrive pas à ouvrir le fichier");
lcompteur = Read(smusfichier,iobuffer,8);
if(lcompteur == -1)
referme("Problème de lecture du fichier");
if(lcompteur < 8)
referme("Ce n'est pas un fichier IFF il est trop petit");
/* On analyse le Header */
smusheader = (ChunkHeader *)iobuffer;
if(smusheader->ckID != FORM)
referme(" Ce n'est pas un fichier IFF\n");
if(smusheader->ckSize > 65536)
referme("Fichier trop gros\n");
/* On charge le fichier */
if (!(sdonnee = (UBYTE *) AllocMem(smusheader->ckSize, MEMF_PUBLIC | MEMF_CLEAR)))
referme("Impossible d'allouer de la mémoire\n");
lcompteur = Read(smusfichier,sdonnee,smusheader->ckSize);
if(lcompteur == -1)
referme("Problème de lecture du fichier");
if(lcompteur < smusheader->ckSize)
referme("fichier IFF malformé");
/* Evaluation du type de fichier */
if(MakeID( *sdonnee, *(sdonnee+1), *(sdonnee+2) , *(sdonnee+3)) != SMUS)
referme("Ce n'est pas un fichier IFF SMUS");
/* recherche des différents chunks */
compteur = 4;
tcompteur = 0;
while(compteur < smusheader->ckSize);
{
chunk = (Chunk *)(sdonnee+compteur);
switch(chunk->ckID)
{
case SHDR:
partheader = (SScoreHeader *)(sdonnee+compteur+8L);
gamme = (8929 / (partheader->tempo >> 7));
for(i=0;i<64;i++) ttable[i] *= (ULONG)gamme;
break;
case INS1:
instref = (RefInstrument *)(sdonnee + compteur + 8L);
for(i=0;i <= chunk->ckSize; i++)
instnom[i] = instref->name[i];
instnom[chunk->ckSize-4L] = 0;
if(etat = chargeinst(instnom))
referme("Impossible de charger un instrument \n");
break;
case TRAK:
if(tcompteur < 4)
{
sevent = (SEvent *)(sdonnee+compteur+8L);
piste[tcompteur] = sevent;
piste[tcompteur+4] = (SEvent *)(sdonnee+compteur+8L+chunk->ckSize);
tcompteur ++;
}
else
puts("Les pistes supérieures à 4 ne sont pas prises en compte ...\n");
sevent = (SEvent *)(sdonnee+compteur+8L+chunk->ckSize);
break;
default:
break;
}
compteur += 8L + chunk->ckSize;
if(chunk->ckSize & 1L == 1)
compteur ++;
}
/* on est parti pour jouer la partition */
if(!etat && !numinst)
{
if(!(etat = initaudio()))
notes(piste);
/* C'est fini */
finaudio();
dechinst();
}
if(smusfichier) Close(smusfichier);
if(sdonnee) FreeMem(sdonnee,smusheader->ckSize);
return(0L);
}
|
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/antsmusplayer.lha.
|