Nachdem wir uns im ersten Teil der Serie mit der Theorie des Samplings beschĂ€ftigt haben, wird es nun konkret. Wir prĂ€sentieren den Quell-Code fĂŒr ein modulares Rahmenprogramm zum Wandeln von Sample-Files und die ersten zwei Module fĂŒr das AVR- und das Rohdaten-Format.
Das Programm âVLUNâ soll verschiedene Sample-File-Formate ineinander umwandeln. Damit es leicht an verschiedene Formate anzupassen ist, haben wir ein modulares Konzept entwickelt, das eine Erweiterung jederzeit sehr einfach macht. Jedes Modul enthĂ€lt nĂ€mlich sĂ€mtliche zu einem Format gehörigen Funktionen.
Das VLUN.C-Rahmenprogramm soll in seiner jetzigen Form nur als einfaches Beispiel dienen, wie man diese Module sinnvoll miteinander kombiniert. Es sollte aber fĂŒr jeden Programmierer leicht möglich sein, es um die nötige interaktive Benutzereingabe, eine Kommandozeilenauswertung oder gar eine GEM-OberflĂ€che zu erweitern. SĂ€mtliche Programm-Module verwenden fĂŒr das File-I/O die Standard-C-Funktionen, somit lĂ€Ăt sich das Programm auch auf andere Computer (mit Bigendian-Zahlenformat) portieren.
Die Integer-Bit-GröĂe ist 16 Bit, Long bedeutet 32 Bit. FĂŒr GNU C sind also kleine Anpassungen nötig (z.B. #define int short in VLUN.H). Wir liefern das Project-File fĂŒr Pure C in einer der nĂ€chsten Folgen mit.
Die Wandelfunktionen der Module
Zu jedem Formatmodul gehören die folgenden Funktionen:
Header-Test: Diese Funktion ĂŒberprĂŒft, ob es sich beim Input-File um das File-Format, fĂŒr das das Modul zustĂ€ndig ist, handelt und liest, wenn dem so ist, die fĂŒr die Wandlung nötigen Header-Informationen aus.
Wandlung ins Standardformat: Liest die Sounddaten blockweise und wandelt sie in das interne Standardformat (16 Bit signed Stereo).
Wandlung ins Zielformat: Konvertiert vom Standardformat blockweise ins jeweilige Modulformat und schreibt die Daten.
Header schreiben: Baut einen dem Format entsprechenden Header auf und schreibt ihn an den File-Anfang.
File abschlieĂen: Manchmal sind Zusatzinformationen im File nötig, die erst nach der kompletten Wandlung bekannt sind. Diese werden damit in das File eingefĂŒgt.
FĂŒr einige Formate sind nicht alle Funktionen nötig. So braucht man fĂŒr Rohdaten zum Beispiel keinen Header zu schreiben. Andere Funktionen sind fĂŒr verschiedene Formate gleich.
Intern werden die zur Wandlung nötigen Informationen des Input-Samples in der folgenden Struktur festgehalten:
typedef struct
{
int Typ;
int Format;
long Freq;
long BufLen;
long DataLen;
int SizeFac;
int Div;
} SAMPLE_SPEC;
Der Typ gibt an, um welches File-Format es sich handelt. Mögliche Werte sind die in VLUN.H definierten Konstanten.
In Format ist festgehalten, wie die Sounddaten konkret im File gespeichert sind. Jedes Bit entspricht einer bestimmten Eigenschaft, die als Format-Flags ebenfalls in VLUN.F1 definiert ist. Ein typisches Falcon-Sound-File hĂ€tte z.B. in Format die Flags fĂŒr SIGNED, BIT16, STEREO und BIGENDIAN gesetzt. Das heiĂt es sind vorzeichenbehaftete 16-Bit-Sample-Werte im Motorola-Zahlenformat mit 2 KanĂ€len. Das Nichtgesetztsein eines Flags entspricht jeweils dem Fehlen der Eigenschaft.
Die Sample-Frequenz in Hz wird in der Variablen Freq gespeichert.
BufLen gibt an, in welchen Blöcken das File geladen werden muĂ. Diese BlocklĂ€nge in Bytes wird meist nur vom Arbeitsspeicher, den man belegen will, bestimmt und ist in BUFSIZE festgelegt. Bei blockweise komprimierten Sounddaten (wie DSVM-Deltapack) ist es jedoch naheliegend, die BufLen auf die GröĂe eines Blocks im File zu setzen.
DataLen gibt an wieviele Sounddaten (in Bytes) das Input-File enthÀlt - das ist aber nur in einigen FÀllen bekannt und nötig. Bei einigen File-Formaten kann es sein, daà nach den Sounddaten andere Daten folgen. In diesem Fall wird, DataLen als ZÀhler benutzt, der die noch nicht gewandelte DatenlÀnge angibt (z.B. WAVE-Format).
SizeFac gibt an, um welchen Faktor die Daten lĂ€nger werden, wenn sie vom Input-Format ins Standardformat gewandelt werden, z.B. ist SizeFac 4, wenn 8-Bit-Mono-Daten vorliegen (Mono->Stereo, 8 Bit->16 Bit). Diese Information ist nötig, um den fĂŒr die Wandlung benötigten Buffer bereitzustellen.
Div ist in den bisherigen Formaten nicht benutzt. Sollte es nötig sein, kann man damit formatspezifische Informationen vom Header-Test zu anderen Funktionen ĂŒbergeben.
FĂŒr die Festlegung des Output-Formates sind nur wenige Variablen erforderlich.
typedef struct
{
int Typ;
int Format;
} OUT_FORMAT;
Typ und Format haben dieselbe Belegung wie in SAMPLE_SPEC. Das Output-Format muĂ natĂŒrlich vorgegeben werden. WĂ€hrend es in unserem Rahmenprogramm einfach festgelegt wird, sollte es normalerweise interaktiv vom Benutzer erfragt werden.
Das Hauptprogramm
Dieser Programmteil ist erfreulich kurz und einfach, weil er nur die Modulroutinen sinnvoll miteinander kombinieren muĂ. ZunĂ€chst mĂŒssen das Input- und das Output-File festgelegt und geöffnet und das Output-Format bestimmt werden. Dann mĂŒssen alle vorhandenen Header-Test-Routinen der Reihe nach aufgerufen werden, bis eine von ihnen das Format erkennt. Vor jedem Aufruf muĂ man den Filepointer an den File-Anfang setzen.
Ist das Format erkannt, wird der Buffer, der zur Wandlung eines Blockes nötig ist, alloziert. Als nĂ€chstes wird, wenn im Modul dazu die entsprechende Funktion vorhanden ist, der Header des Output-Files geschrieben. Die eigentliche Wandlung findet in einer while-Schleife statt, in der die Sounddaten blockweise ins Standardformat geladen und im Output-Format geschrieben werden. NatĂŒrlich ruft man dazu die Ladefunktion des Input- und die Schreibfunktion des Output-Formats auf. Falls nötig, rufen Sie dann die AbschluĂfunktion des Output-Formats auf. Zum SchluĂ mĂŒssen nur der Buffer freigegeben und die Files geschlossen werden.
Die Liste mit den Wandelfunktionen
Der zu einem Modul gehörige Funktionssatz wird (zusĂ€tzlich mit dem Namen des Formats) in der Struktur FMT_FUNCTION gespeichert. Wenn fĂŒr ein Format eine Funktion nicht existiert (da unnötig), wird ein Null-Pointer eingetragen.
In der Liste CnvFuncs sind dann alle Funktionen entsprechend ihrer Reihenfolge in VLUN.H enthalten. Dabei ist es wichtig, daĂ die Funktionen des headerlosen Rohformates als letztes enthalten sind. Denn die ,Header-Testâ-Funktion dieses Formats akzeptiert jedes File als RAW-Format (es gibt ja keinen Header, der dagegen spricht...).
Die Wandelfunktionen
FĂŒr die fĂŒnf verschiedenen Wandelfunktionen pro Modul werden Typen in VLUN.H definiert. Dabei gibt es fĂŒr die verschiedenen Funktionen einiges zu beachten:
Header-Test
typedef int (HEAD_FUNC)
(FILE *InFH,
SAMPLE_SPEC *Smp)
InFH ist der Filepointer auf das bereits geöffnete Input-File. Bei erkanntem File-Format muĂ Smp entsprechend dem Header ausgefĂŒllt und SUPPORTED zurĂŒckgeliefert werden. Handelt es sich bei dem File nicht um das Modulformat muĂ UNKNOWN gemeldet werden. Ist das Format zwar bekannt, wird jedoch diese spezielle Version nicht unterstĂŒtzt (z.B. 32-Bit-SND-Format), dann wird NONSUPPORTED zurĂŒckgeliefert, damit das aufrufende Hauptprogramm keine weiteren Formate ĂŒberprĂŒfen muĂ.
typedef long (READ_FUNC)
(FILE *InFH,
SAMPLE_SPEC *Smp,
char *StdBuf,
long StdBufLen);
StdBuf ist der Pointer auf den Beginn des Wandel-Buffers, StdBufLen seine LĂ€nge. Die Funktion muĂ nun einen Block aus InFH laden und ins Standardformat wandeln. Das Hauptprogramm stellt sicher, daĂ der Buffer lang genug ist, um einen
Input-Format-Block im Standardformat zwischenzuspeichern. Die Daten im Standardformat sollen nach der Wandlung immer am Buffer-Anfang liegen. ZurĂŒckgegeben wird die LĂ€nge der Daten im Standardformat. Wird weniger als ein Block gelesen, so wird hier auch ein kleinerer Wert zurĂŒckgeliefert.
typedef long (WRITE_FUNC)
(FILE *OutFH,
OUT_FORMAT *OutSmp,
long DataLen,
char *StdBuf);
Wandelt einen Standardformat-Datenblock der LĂ€nge DataLen ins Zielformat OutSmp um und speichert diesen in OutFH. AuĂerdem kĂŒmmert sich die Funktion darum, daĂ die Format-Flags vom Zielformat wirklich unterstĂŒtzt werden. Beim Schreiben von AVR muĂ man beispielsweise immer BIGENDIAN setzen. ZurĂŒckgegeben werden die geschriebenen Bytes.
typedef int (WRT_HEAD_FUNC)
(FILE *OutFH,
SAMPLE_SPEC *InSmp,
OUT_FORMAT *OutSmp);
Baut den Header entsprechend der Information in OutSmp und InSmp auf und schreibt ihn. ZurĂŒckgeliefert wird 1, wenn der Header geschrieben werden konnte, 0 andernfalls.
File abschlieĂen
typedef int (FINISH_FUNC)
(FILE *OutFH,
OUT_FORMAT *OutSmp);
Bei einigen File-Formaten steht im Header auch die LÀnge der Sample-Daten. Da dies erst nach der Wandlung bekannt ist, schreibt diese Funktion in diesen FÀllen zum Beispiel die entsprechenden Werte ins File. Einige Module unterhalten dazu eine statische Variable, die die Anzahl der geschriebenen Bytes mitzÀhlt.
Nachdem das Grundkonzept hoffentlich ausreichend dargestellt wurde, kommen wir zum ersten konkreten Soundformat:
Das AVR-Modul
Der Hauptanwendungsbereich des AVR Formats liegt im ATARI-Bereich (SAM). Der Header von AVR Files hat folgenden Aufbau:
typedef struct
{
long magic;
char sample_name[8];
int mode;
int resolution;
int sign;
int loop;
int note;
long speed;
long len; long beg_loop;
long end_loop;
int res1,res2,res3;
char extension[20];
char free_area[64];
} AVRHEAD;
 |
 |
magic: |
hat immer den Wert ,2BITâ (0x32424954L). Er dient als Erkennung fĂŒr das AVR-Format. |
sample_name: |
Name des Samples |
mode: |
0 fĂŒr Mono Samples, -1 (0xffff) fĂŒr Stereo |
resolution: |
Anzahl der Bits pro Sample-Wert (8 oder 16) |
sign: |
0 fĂŒr vorzeichenlose Darstellung, -1 fĂŒr vorzeichenbehaftete |
loop: |
0 schleifenloses Sample, -1 mit Schleife |
note: |
-1 keine MIDI-Noten-Zuweisung |
speed: |
Sample-Frequenz in Hz in den unteren 3 Bytes. Wenn höchstes Byte ungleich 0xFF ist, steht auch eine Festfrequenz von 0 bis 7 im höchsten Byte |
len: |
Sample-DatenlÀnge in Sample-Werten. Bei Stereo-Samples wird linker und rechter Kanal einzeln gezÀhlt |
beg_loop: |
Schleifenanfang (in Sample-Werten) oder 0 |
end_loop: |
Schleifenende oder ,len' |
extension: |
File-Namenerweiterung |
free_area: |
Benutzerdef. Info, z.B. ASCII-Text |
Danach folgen die Sound-Daten. Vor Belegen des Headers sollte er komplett mit Nullen initialisiert werden.
Die AVR-Wandelfunktionen
Im AVR-Format kann eine Schleife festgelegt werden, mit deren Hilfe man ein Sample durch Wiederholung verlĂ€ngern kann. Das Sample wird nach dem end_loop wieder ab begin Joop vorgespielt. Wie bei allen anderen Formaten, wird auch beim AVR-Format die Information ĂŒber Schleifen ignoriert. Nur die reinen Sample-Werte werden konvertiert, denn sonst wĂŒrden die entstehenden Files doch recht lang ... Das Modul besteht aus AVR.C und dem AVR.H File.
Die Header-Test-Funktion AvrTstHead ĂŒberprĂŒft, ob das Input-File im AVR-Format ist und setzt in diesem Fall ,SAMPLE_SPECâ entsprechend.
Die Wandlung ins Standardformat wird von der Funktion AllToStd ĂŒbernommen, die im UTIL.C File (nĂ€chste Ausgabe) enthalten ist. Da das AVR-Format so angenehm ist, braucht man hier keine spezielle Funktion.
Das Wandeln ins AVR ĂŒbernimmt Avr-FromStd, das verhindert, daĂ MULAW oder DELTAPACK als Format in AVR-Files benutzt werden und auĂerdem in einer statischen Variablen die gewandelten und geschriebenen Bytes mitzĂ€hlt. Die eigentliche Wandlung ĂŒbernimmt auch hier eine allgemeine Funktion AllFromStd.
AvrWrtHead initialisiert den Header, setzt die nötigen Werte und schreibt den Header.
AvrFinish schreibt die nach dem Wandeln bekannte Sample-LĂ€nge in den Header und schreibt ihn an den Fileanfang. Die ZĂ€hl variable muĂ auf Null zurĂŒckgesetzt werden damit mit einem neuen Wandeln begonnen werden kann.
Das RAW-Modul
Rohdaten sind im SUN- und NeXT-Bereich als Mu-Law-Daten und auf dem ST als 8-Bit-Mono-Daten weitverbreitet. Das Problem ist, daĂ sie keinen Header haben, deshalb muĂ man das richtige Datenformat und die Frequenz von Hand per Trial and Error herausfinden.
Die Funktion, die sonst testet, ob ein Header einem bestimmten Format entspricht, ist bei Rohdaten verstĂ€ndlicherweise nicht möglich. Diese Funktion heiĂt deswegen auch RawSetHead, weil sie jedes File als RAWDATA akzeptiert und einfach die Frequenz und das Datenformat auf den typischen ST-Standard setzt. Um das oben genannte Trial-and-Error-Verfahren zu vereinfachen, sollte man diese Funktion etwas ausbauen, um interaktiv das Format und die Frequenz eingeben zu können. Das Wandeln ins RAW-Format ĂŒbernimmt RawFromStd. Hier wird dafĂŒr gesorgt, daĂ DELTAPACK nicht verwendet wird. Die eigentliche Konvertierung ĂŒbernimmt wieder AllFromStd. Die Funktionen zum Header schreiben und File abschlieĂen entfallen.
Im nÀchsten Teil der Serie werden wir dann UTILS.C mit den eigentlichen kurzen Konvertierungsroutinen vorstellen und weitere Sound-Formate besprechen.
/* AVR Routinen zur AVR Konvertierung */
/* Modulname: AVR.C */
/* (c) MAXON Computer 1994 */
/* Autoren: H. Schönfeld, B. Spellenberg */
# include "vlun.h"
# include "avr.h"
/* ZĂ€hler fĂŒr geschriebene Sound-Bytes */
static long ByteCount=0L;
/* Header fĂŒr Input und Export */
static AVRHEAD AvrHead;
/* AVR-Headertest-Funktion */
int AvrTstHead(FILE *InFH, SAMPLE_SPEC *Smp)
{
/* Soundformatbeschreibung initialisieren */
memset(Smp,0,sizeof(SAMPLE_SPEC));
/* AVR-Header lesen und testen */
if(!fread(&AvrHead,sizeof(AVRHEAD),1L,InFH))
return(UNKNOWN);
if(strncmp(&AvrHead.magic,â2BIT",4))
return(UNKNOWN);
/* Header auswerten und Formatbeschreibung */
/* aufbauen */
Smp->Typ=AVRHEADER;
Smp->Freq=0xFFFFFFL&AvrHead.speed;
Smp->SizeFac=1;
if(AvrHead.mode==-1)
Smp->Format|=STEREO;
else
Smp->SizeFac*=2;
if(AvrHead.resolution==16)
Smp->Format|=BIT16|BIGENDIAN;
else if(AvrHead.resolution==8)
Smp->SizeFac*=2;
else
return(NONSUPPORTED);
if(AvrHead.sign==-1)
8mp->Format|=SIGNED;
Smp->BufLen=BUFSIZE;
return(SUPPORTED);
}
/* Std.format nach AVR konvertieren */
long AvrFromStd(FILE *OutFH,OUT_FORMAT *OutSmp, long DataLen, char *StdBuf)
{
long DataWrite;
/* Mu-law und Deltapack werden von AVR nicht */
/* unterstĂŒtzt */
OutSmp->Format&=~(MULAW|DELTAPACK);
/* 16Bit Samples immer im Motorolaformat */
if(OutSmp->Format&BIT16)
OutSmp->Format|=BIGENDIAN;
/* nÀchsten Block konvertieren und speichern */
DataWrite=AllFromStd(OutFH,OutSmp,DataLen,StdBuf);
/* BytezÀhler entsprechend erhöhen */
ByteCount+=DataWrite;
return(DataWrite);
}
/* AVR-Header setzen und schreiben */
int AvrWrtHead(FILE *OutFH,SAMPLE_SPEC *InSmp, OUT_FORMAT *OutSmp)
{
int Fmt;
Fmt=OutSmp->Format;
memset(&AvrHead,0,sizeof(AVRHEAD));
strcpy(&AvrHead.magic,"2BIT");
AvrHead.mode=(Fmt&STEREO)?0xffff:0;
AvrHead.resolutions(Fmt&BIT16)?16:8;
AvrHead.sign=(Fmt&SIGNED)?0xffff:0;
AvrHead.speed=0xFF000000L|InSmp-»Freq;
AvrHead.note=0xffff;
return(fwrite(SAvrHead,sizeof(AVRHEAD),1,OutFH));
}
/* Fehlende Information in AVR-Header */
/* schreiben */
int AvrFinish(FILE *OutFH,OUT_FORMAT *OutSmp)
{
/* Anzahl der Samplepunkte bestimmen */
AvrHead.len=ByteCount;
if(OutSmp->Format&BIT16)
AvrHead.len/=2 ;
AvrHead.end_loop=AvrHead.len;
/* BytezĂ€hler fĂŒr evtl. neue Wandlung */
/* zurĂŒcksetzen */
ByteCount=0;
/* kompletten Header an Fileanfang schreiben */
fseek(OutFH,0L,0);
return(fwrite(&AvrHead,sizeof(AVRHEAD),1,OutFH));
}
/* AVR Headerfile zur AVR Konvertierung */
/* Modulname: AVR.H */
/* (c) MAXON Computer 1994 */
/* Autoren: H. Schönfeld, B. Spellenberg */
typedef struct
{
long magic; /* Headerkennung */
char sample_name[8]; /* Filename */
int mode; /* Stereo/Mono */
int resolution; /* 8/16 Bit Auflösung*/
int sign; /* Mit/Ohne Vorz. */
int loop; /* Looping Sample? */
int note; /* MIDI Notes? */
long speed; /* Frequenz */
long len; /* LĂ€nge in Samples */
long beg_loop; /* Anfang der Loop */
long end_loop; /* Ende der Loop */
int res1,res2,res3;
char extension[20]; /* Filename-Extension*/
char free_area[64]; /* Zusatzinfo. */
} AVRHEAD;