Ton Digital: Wunderbare Welt der Soundformate, Teil 4

In diesem letzten Teil werden wir die Formate WAVE und DVSM behandeln und dabei auch auf das Deltapack-Verfahren eingehen. Zum Schluß zeigen wir, wie man grundsätzlich weitere Formate implementieren kann.

Zunächst interessiert uns das WAVE-Format. Die Heimat dieses Formats ist der PC-Bereich, und dort ganz besonders die Windows-Welt. WAVE ist eine bestimmte Form eines RIFF-Files, das ähnlich dem IFF-Format (bekannt vom AMIGA) nicht nur Sounddaten, sondern auch Bilder, Texte und so weiter enthalten kann.

In diesem Format ist die enthaltene Information in einzelnen ‚Chunks‘ verpackt. Ein Chunk besteht aus einem 4 Byte langen Namen und einer Long-Variable, die die Länge des zugehörigen Datenblocks angibt. Beim Lesen eines WAVE-Files muß man also nach den unterstützten Chunks suchen, diese auswerten und hoffen, daß in den eventuell vorhandenen Unbekannten keine wesentliche Information steckt.

Das WAVE-File-Format

Ein RIFF-File beginnt mit einem ,RIFF‘-Chunk, d.h., die ersten 4 Bytes haben den Wert ,RIFF‘ oder 0x52494646L, und die Chunk-Länge ist die Länge des restlichen Files. Achtung: Im WAVE-File wird die Intel-Zahlendarstellung verwendet. Bevor man also die Längenangaben auswertet, muß man sie erst ins Motorola-Format umrechnen.

Zur speziellen Kennzeichnung eines WAVE-Files müssen nun die 4 Bytes ,WAVE‘ (0x57415645L) folgen (das ist kein kompletter Chunk!). Nun muß als erster Chunk der fmt-Chunk folgen. Er beinhaltet diese Struktur:

typedef struct {
    int format; 
    int channels; 
    long frequency; 
    long average; 
    int align;
    int bps;
} FORMATCHUNK;

format: Gibt an, in welchem Format die Sounddaten abgelegt sind. Definiert sind zum Beispiel:

1 WAVE_FORMAT_PCM Microsoft
Pulse Code Modulation (PCM) Format

257 IBM_FORMAT_MULAW
IBM mu-law Format

258 IBM_FORMAT_ALAW
IBM a-law Format

259 IBM_FORMAT_ADPCM
IBM AVC Adaptive Differential Pulse Code Modulation Format

Mit Abstand am verbreitetsten ist das ungepackte _PCM-Format (1). Deshalb wird von VLUN nur dieses Format unterstützt.

channels: Anzahl der Kanäle, also meist 1 oder 2

frequency: Abtastrate in Samples pro Sekunde

average: Gesamtdatendurchsatz in Bytes pro Sekunde. Damit können Programme, wenn nötig, ihre Buffer-Größen abschätzen.

align: Benötigte Bytes pro Sample-Wert, summiert über alle Kanäle.

bps: Anzahl der Bits pro Sample-Wert. Wir unterstützen 8- oder 16-Bit-Samples. Dabei ist zu beachten, daß 8-Bit-Samples grundsätzlich vorzeichenlos, 16-Bit-Samples dagegen mit Vorzeichen und im Intel -Format gespeichert sind.

Grundsätzlich endet der fmt-Chunk nach der align-Angabe, aber je nach format folgen ganz bestimmte zusätzliche Informationen (die natürlich in der Länge des Chunks eingerechnet sind). Im Fall des _PCM-Formats ist diese Zusatzinformation die bps-Variable. Damit kann man sich ausrechnen, welche Werte normalerweise die Variablen average und align besitzen:

average = channels*frequency*bps/8 
align   = channels*bps/8

Dabei rundet man jeweils zum nächst größeren Integerwert auf. Zwischen diesem Format-Chunk und dem Data-Chunk mit den eigentlichen Sounddaten können nun noch weitere Chunks folgen, wie z.B.:

silence-chunk: Definiert Stille bestimmter Länge.

cue-points-chunk: Ist eine Liste von Marken innerhalb des Samples.

playlist-chunk: Gibt die Reihenfolge der Marken zum Abspielen an.

assoc-data-list: Zusatzinformationen (Texte, Labels).

VLUN kümmert sich um diese Chunks nicht, weil sie in herkömmlichen Samples praktisch nie auftreten. Schließlich folgt der data-Chunk. Er beginnt mit data (0x64617461L) und enthält die Sample-Werte in der üblichen Reihenfolge (links, rechts, links, rechts...).

Die WAVE-Wandelfunktionen

Das WAVE-Modul setzt sich aus den Dateien WAV.H und WAV.C zusammen.

Die Header-Test-Funktion WavTstHead überprüft anhand der ersten 4 Bytes, ob es sich überhaupt um ein RIFF-File handelt. Danach wird getestet, ob darin Sounddaten im WAVE-Format gespeichert sind. Wenn dem so ist, dann wird der fmt-Chunk gesucht, geladen, ins Motorola-Format gewandelt und ausgewertet.

Danach wird der data-Chunk gesucht und der Filepointer an den Beginn der Sounddaten gesetzt. Die Länge des Datenbereichs wird in die DataLen- Variable der internen Sounddatenbeschreibungsstruktur geschrieben.

Dabei werden zwei Hilfsfunktionen benötigt:

long SearchChunk (char Name, FILE Fh):

Durchsucht das File Fh nach dem Name-Chunk, beginnend mit der aktuellen Position, und gibt dessen Länge zurück.

*void ConvFmtChunk (FORMATCHUNK FmtChunk):

Konvertiert den Inhalt der fmt-Chunk-Struktur von Intel- nach Motorola-Format und umgekehrt. Dabei werden zwei Präprozessor-Makros i_long und i_int benutzt, die in WAV.H definiert wurden. Sie übernehmen die Wandlung der einzelnen Variablentypen. Dabei werden die Definitionen für UBYTE, UWORD und ULONG aus dem PORTAB.H-File benutzt.

Die Wandlung vom WAVE ins Standardformat wird von WavToStd übernommen. Diese ruft wieder die Allround-Wandelfunktion AllToStd auf. Zusätzlich wird die Länge der schon gewandelten Daten mitprotokolliert, damit nicht über das Ende des Daten-Chunks hinaus gelesen wird - sollten im File noch andere Daten folgen.

Das Umwandeln vom Standard- ins WAVE-Format führt WavFromStd durch. Auch hier wird die Allround-Funktion AllFromStd benutzt. Dabei wird dafür gesorgt, daß µ-Law und Deltapack nicht verwendet werden können. Außerdem werden 8-Bit-Daten unsigned und 16-Bit-Daten nur signed abgespeichert. In der Variable ByteCount wird dann die Anzahl der schon geschriebenen Bytes aufaddiert.

Der Fileheader wird von der Funktion WavWrtHead aufgebaut. Dabei werden der F/FF-Chunk, das WAVE-Feld, der _/mr-Chunk und schließlich der data-chunk aufgebaut und geschrieben. Zu beachten ist wieder, daß sämtliche Werte ins Intel-Format gewandelt werden müssen.

Am Ende werden fehlende Informationen von WavFinish in den Header des Files geschrieben. Die Länge des RIFF-Chunks und des data-chunks werden berechnet und im Intel-Format eingefügt.

Das DVSM-Format

Das DVSM-Format ist bisher wohl nur einigen wenigen glücklichen Falcon-Usern bekannt. Es wurde ursprünglich für das Harddiskrecording-Programm WinRec entwickelt. Das Besondere an dem Format ist das Deltapackverfahren, das bei gleichguter Datenreduktion wie µ-Law deutlich besser klingt.

Da das Format noch relativ neu ist, befindet es sich immer noch in der Entwicklung, und gerade in der letzten Zeit wurden einige Erweiterungen angekündigt, von denen noch nicht klar ist, wie sie endgültig aussehen werden, und ob sie überhaupt von irgendwelchen Programmen genutzt werden. Von den bisher in Aussicht gestellten Änderungen können nur zwei den hier vorgestellten Routinen gefährlich werden. Doch dazu später.

Ähnlich dem WAVE-Format werden künftige Erweiterungen in Form von Chunks - hier heißen sie allerdings Cookies - eingefügt, die direkt hinter dem DVSM-Fileheader folgen.

Das DVSM-File-Format

Jedes DVSM-File beginnt mit dem folgenden Header:

typedef struct { 
    char magic[6];
    unsigned int headlen; 
    unsigned int freq; 
    char pack; 
    char mode; 
    long blocklen;
} DVSMHEAD;

magic: Hat immer den Wert DVSM mit zwei Null-Bytes (0x4456, 0x534D, 0x0000).

headlen: Gibt die Länge des Fileheaders einschließlich der Länge aller folgenden Cookies an und zeigt damit vom File-Beginn aus direkt auf den Anfang der Sounddaten im File. Da es sich hier um eine 16-Bit-Variable handelt, ist die Länge des Headers zusammen mit allen Cookies auf 64KB beschränkt.

freq: Enthält die Frequenz des Samples. Diese kann als normale Zahl abgespeichert sein. Wenn der Wert von freq >256 ist, so ist dies definitionsgemäß der Fall. Werte von 0 bis 7 geben dagegen indirekt den zu verwendenden Vorteiler der Falcon-Soundmatrix an. Als Grundfrequenz dient dabei normalerweise die interne Falcon-DMA-Frequenz (25.175 MHz). Sie kann sich aber auch auf eine externe CD-(22.5792 MHz) oder DAT-(24.576 MHz)Frequenz beziehen.

Den Vorteiler erhält man, wenn man freq als Index auf die folgende Tabelle benutzt: [11,9,7,5,4,3,2,1], Der Wert 0 entspricht also zum Beispiel dem Vorteiler 11.

Danach muß man aus der Grundfrequenz über die folgende Formel die tatsächliche Frequenz ausrechnen (die man für andere File-Formate benötigt):

Frequenz = Clock/256.0/(Vorteiler+1)

pack: Gibt an, ob und wie die Daten gepackt sind: 0 = ungepackt, 2 = Deltapack. Weitere Verfahren sind in Planung und können von VLUN noch nicht behandelt werden.

mode: Beschreibt das Sample-Format: Bit 0: 0/16 Bit; Bit 1: Stereo/Mono

Von WinRec selbst wird jedoch Mono nicht unterstützt. VLUN dagegen unterstützt wenigstens 8 Bit Mono.

blocklen: Enthält bei gepackten Files die Länge eines Blocks. Ein Block besteht aus zwei 16-Bit-Stützwerten (für den linken und den rechten Kanal). Danach folgen nur noch 8-Bit-Differenzwerte. Näheres siehe unten.

Die Cookies

Ein DVSM-Cookie besteht aus einer 4-Byte-Namenskennung und seiner Gesamtlänge, einem 2-Byte-Wort. Anschließend können dann die Daten des Cookies folgen. VLUN unterstützt nur folgenden Cookie:

CLCK: Wird gefolgt von einer Integerva-nablen, die angibt, ob die Grundfrequenz internem Takt (0), CD (1) oder DAT (2) entspricht.

Weitere Cookies sind zum Beispiel:

PEAK: Zwei 16-Bit-Werte, die die höchsten im Sample auftretenden Werte für beide Kanäle enthalten.

DSPE: n Bytes, die ein DSP-Programm im Binary-Format enthalten.

INFO: n Bytes, die zum Beispiel den kompletten Minix-Filesystem-File-Namen enthalten können (oder andere beliebige Informationen).

PACK: 256 Bytes. Enthält eine alternative Deltapack-Distanzwerttabelle, die zum Entpacken des Files benutzt werden soll. Wird bisher von keinem Programm benutzt, auch nicht von VLUN.

Die DVSM-Wandelfunktionen

Das DVSM-Modul besteht aus den Files D VSM.C, DVSM.H und speziellen Delta-pack-Routinen in UTILS.C.

In DVSM.H werden einige Falcon-Systemkonstanten definiert, für den Fall, daß Sie nicht das neuste Pure C benutzen.

Die Header-Testfunktion DvsTstHead lädt zunächst den regulären Filehaeder und wertet die Parameter aus. Wenn das File im Deltapackformat vorliegt, wird als Buffer-Länge nicht das sonst übliche BUFSIZE verwendet, sondern die Buffer-Länge wird entsprechend der blocklen- Variable des Headers eingestellt. Dabei wird der Wert um 2 Bytes verringert, weil ja die ersten 2 Stützstellen im 16-Bit-Format vorliegen und komplett in die blocklen eingerechnet werden. Beim Entpacken werden diese 2 Werte deshalb gesondert behandelt.

Danach werden sämtliche eventuell vorhandenen Cookies gelesen und überprüft, ob der CLCK-Cookie dabei ist. Wird dieser gefunden, dann wird die Sample-Frequenz entprechend der angegebenen Grundfrequenz neu ausgerechnet.

Zum Schluß wird dann auf jeden Fall an den Anfang der Sounddaten „geseekt“.

Die Umwandlung vom DVSM-Format ins Standardformat übernimmt die Routine DvsToStd. Wenn die Daten ungepackt sind, so wird diese Aufgabe einfach an AllToStd weitergereicht.

Beim Deltapack wird dagegen ein kompletter Block einschließlich der beiden Stützpunkte an das Ende des Buffers geladen und mit der Funktion ConvD_Std ins Standardformat gewandelt, also entpackt. Dazu später mehr.

Das Wandeln vom Standardformat nach DVSM geschieht mit DvsFromStd. Zunächst wird dabei sichergestellt, daß die Daten vorzeichenbehaftet sind und nicht im µ-Law-Format abgespeichert werden. Außerdem muß 16-Bit in Stereo und Motorola-Format geschrieben werden.

Auch hier muß sich die Funktion selbst nur um das Schreiben von Deltapack-Daten kümmern. Dazu wird zunächst ConvStd_D aufgerufen, das einen Block einpackt und die Stützpunkte am Anfang unverändert läßt. Dann wird der Block mit den Stützpunkten gespeichert.

Das Schreiben des Headers wird von DvsWrtHead übernommen. Dabei wird auf Cookies vollständig verzichtet. Aus der gewünschten Sample-Frequenz wird die nächste Festfrequenz des Falcons berechnet, da die meisten anderen Programme die Angabe der kompletten 2-Byte-Frequenz in freq noch nicht unterstützen. Sollte sich das bald ändern, dann wäre es eine einfache Übung, hier die tatsächliche Frequenz abzuspeichern.

Das Deltapack-Verfahren

Bei diesem Packverfahren wird im File immer nur die Differenz zweier aufeinanderfolgender 16-Bit-Sample-Werte als 8-Bit-Wert gespeichert. Das geschieht für jeden Kanal getrennt, aber die Werte folgen wie immer in der Reihenfolge l, r, l, r... Die 16-Bit-Werte x müssen dabei über eine logarithmische Kennlinie in die 8-Bit-Werte y gewandelt werden. Das geschieht nach folgender Formel:

y=ln(abs(x))*12.310998*sign(x) (1)

Die Daten sind in Blöcken mit einer (im Header festgelegten Länge) organisiert, die jeweils mit einem ungepackten 16-Bit-Stützwert für jeden der beiden Kanäle beginnen. Danach folgen dann jeweils die gepackten Differenzwerte.

Auspacken

Zuerst werden die beiden Stützwerte des Blocks gelesen, die als Basis zum Entpacken dienen. Dann liest man das erste Differenzwertpaar und wandelt die 8-Bit-Werte über eine 128 Byte lange Tabelle in 16-Bit-Werte um, die dann jeweils zu den vorhergehenden Sound werten addiert werden (oder falls der Differenzwert negativ ist, subtrahiert). Damit entstehen die endgültigen 16-Bit-Werte, die dann als Basis zur Berechnung der nächsten dienen.

Die Umwandlung von 8 Bit nach 16 Bit geschieht über die folgende Formel, die der Formel (1) nach x aufgelöst entspricht:

x=1,08462^abs(x)*sign(x) (2)

Ähnlich dem µ-Law-Verfahren, geht es auch hier wesentlich schneller, die Wandlung über eine Lookup-Tabelle durchzuführen, die für alle Eingangswerte nur einmal ausgerechnet wird. Wie man an (2) erkennen kann, reicht es aus, dabei nur die 128 positiven Werte in der Tabelle Delta-Look-Up zu speichern, denn die Negativen kann man durch Umdrehen des Vorzeichens vor und nach der Berechnung erhalten. Das Aufbauen der Tabelle erledigt die Funktion SetDelta, die vor der Wandlung des ersten Blocks durch die Funktion ConvD_Std aufgerufen wird.

Sobald die Tabelle für Deltapack aufgebaut ist, wird dies im Flag DeltaLkSet vermerkt.

Einpacken

Zum Umwandeln von 16 Bit nach 8 Bit wird wiederum eine Tabelle benutzt, in der für alle 32768 betragsmäßig möglichen Differenzwerte die entsprechenden 8-Bit-Werte abgelegt sind. Da man wohl nie gleichzeitig nach µ-Law und Deltapack wandelt, benutzt VLUN den selben Speicherbereich (InvLookUp) für die beiden Wandeltabellen. In der Variablen InvLkSet ist vermerkt, für welches Format die Tabelle gerade aufgebaut ist.

Die Wandlung ins Deltapack-Format, die von ConvStd_D durchgeführt wird, ist allerdings etwas komplizierter als µ-Law, deswegen benötigt man auch die Entpacktabelle DeltaLookUp, wenn man die Daten einpacken will. Deswegen muß auch diese Tabelle wieder aufgebaut sein.

Am Beginn des zu wandelnden Buffers läßt man die beiden ersten 16-Bit-Werte unverändert im Speicher, denn diese sind ja die Stützwerte für den Block. Dann wird die Differenz zu den beiden folgenden berechnet und über die Look-Up-Tabelle in 8-Bit-Werte umgewandelt. Um Folgefehler zu vermeiden, werden nun diese Werte gleich wieder zurückgewandelt, um herauszufinden, welche Werte das entpackende Programm später errechnen würde. Von diesen Werten (die sich von den gewünschten leicht unterscheiden) ausgehend, wird dann die Differenz zu den folgenden beiden berechnet und so weiter.

Damit aber nicht genug. Durch die Verwendung ungenauer Differenzwerte kann es passieren, daß der entpackte Wert ein anderes Vorzeichen erhält als der ursprüngliche. Auf den ersten Blick könnte man meinen, das könne nur. für betragsmäßig kleine Werte geschehen. Für diese kleinen Werte ist es auch tatsächlich völlig natürlich, daß dies passiert. Es hat auch keinen speziellen Einfluß auf die Klangqualität. Es kann aber auch bei betragsmäßig extrem großen Werten Vorkommen: Wenn auf irgend einen Wert ein (betragsmäßig) sehr großer Wert mit umgekehrtem Vorzeichen folgt, dann entsteht durch die, Rundung“ der Differenz beim Resultat ein kleiner* Fehler. Da sich das Ergebnis aber nahe an der Grenze zwischen großen positiven und großen negativen Werten bewegt, kann dieser kleine Fehler dazu führen, daß sich das Vorzeichen vertauscht. Dadurch kann eine Ungenauigkeit in der Differenz von ,1' zu einem Fehler im Soundwert von bis zu 65567 führen. Daher muß die verpackende Routine überprüfen, ob eine solche Situation eintritt, und in diesem Fall den nächst größeren Differenzwert aus der Tabelle lesen. Im Programm wurde der Betrag von 20000 willkürlich als Untergrenze gewählt, ab der derartige Fehler auftreten können und überprüft und korrigiert werden.

Damit sind wir am Ende aller Formate, die wir im Rahmen dieser kleinen Serie vorstellen wollten, angelangt.

Einbauen weiterer Formate

Sollte es aber nötig sein, im Rahmen dieses Programms noch nicht unterstützte Formate einzubauen, dann sollte das auf die folgende Art relativ leicht möglich sein:

Zuerst muß das neue Format - nennen wir es FOSM - in VLUN.H mit einer Nummer vor RAWDATA definiert werden (also z.B. #define FOSMHEADER 4). Die Zahl für RAWDATA muß dann eben um Eins erhöht werden.

Die eigentliche Arbeit ist das Schreiben der Header-Test-, Header-Schreib- und der Wandelfunktionen. Wenn man Glück hat, kann man dabei ausgiebig auf die bereits vorhandenen Allround-Wandelfunktionen und Low-Level-Konvertierungsfunktionen zurückgreifen.

Eventuell noch nötige Low-Level-Funktionen, die auch für spätere Formate wieder interessant sein könnten, sollte man in UTILS.C einfügen, anstatt sie in FOSM.C einzubauen.

Die Prototypen sollten in derselben Art definiert werden wie alle anderen in VLUN.H.

Schließlich müssen alle 4 oder 5 Funktionen in CnvFuncs an der entsprechende Stelle, wie es der FOSMHEADER angibt, eingetragen werden. Also zum Beispiel:

FMT_FUNCTION CnvFuncs[] =
{{AvrTstHead, AHToStd, AvrFromStd, 
  AvrWrtHead, AvrFinish, "AVR-Format"},
  {...}, {...}, {...}, { FosmTstHead, 
  FosmToStd, FosmFromStd, FosmWrtHead, FosmFinish,
  "Fortune Zitat"}, { RawSetFlead, AllToStd, 
  RawFromStd, 0L, 0L, „Raw-Format"},
  { 0L, 0L, 0L, 0L, 0L, 0L}};

Sollten Sie solche Ergänzungen machen, dann schicken Sie doch Ihre Routinen an die Redaktion.

Damit ist das neue Format voll in ,VLUN‘ integriert (ach ja, für alle, die es bis jetzt immer noch nicht erraten haben: ,VLUN‘ steht für ,Viel Lärm um Nichts‘, die englische Version heißt deswegen ,MAAN‘).

Wir hoffen, daß die Einführung in die Welt der Soundformate interessant und vor allem verständlich war. Auf der Mega-Disk befindet sich diesmal zusätzlich zu den normalen Listings noch ein komplettes Sample-Wandelprogramm mit GEM-Oberfläche. Das Programm ,525‘ (gesprochen: ,FiveToFive‘, also, Fünf vor Fünf1), kann dieselben fünf Formate konvertieren, die hier in der Serie vorgestellt wurden. Viel Vergnügen!

Harald Schönfeld Bernd Spellenberg

/* WAV Headerfile zur WAV-Konvertierung     */
/* Modulname: WAV.H                         */
/* (c) MAXON Computer 1994                  */
/* Autoren: H. Schönfeld, B. Spellenberg    */

/* Def. von UBYTE, UWORD, ULONG */
# include <portab.h>

/* Sounddaten Format-Chunk */
typedef struct {
    int format;     /* Format siehe unten   */
    int channels;   /* Anzahl der Kanäle    */
    long frequency; /* Frequenz             */
    long average;   /* Bytes pro Sekunde    */
    int align;      /* Block-Alignment      */
    int bps;        /* Bits per Sample      */
} FORMATCHUNK;

/* Chunk-Struktur */
typedef struct 
{
    char name[4];   /* Chunk-Name           */
    long len;       /* Chunk-Länge          */
.} CHUNK;

/* Unterstützte(s) WAV-Soundformat          */
#define WAVE_FORMAT_PCM 0x0001

/* Makros zum Konvertieren des Intel-Formats */
#define i_int(x)  (*((UBYTE *)&(x)) + (((UWORD)*((UBYTE *) (&(x))+1))«8))
#define i_long(x) (*((UBYTE *)&(x)) + (((ULONG)*((UBYTE *) (&(x))+1))«8) + 
(((ULONG)*((UBYTE *) (&(x))+2))«16) + (((ULONG)*((UBYTE *) (&(x))+3))«24))

/* Prototypen der lokalen Hilfsfunktionen */
long SearchChunk(char *, FILE *); 
void ConvFmtChunk (FORMATCHUNK *);

/* WAV-Routinen zur WAV-Konvertierung       */
/* Modulname: WAV.C                         */
/* (c) MAXON Computer 1994                  */
/* Autoren: H. Schönfeld, B. Spellenberg    */

# include "vlun.h"
# include "wav.h"

/* Zähler für geschriebene Sound-Bytes      */
static  long ByteCount=0L;
/* Sound-'Header'                           */
static  FORMATCHUNK WavHead;
/* File-Header des RIFF-Formats             */
static struct
    {
        char        Riff[4];
        long        GesLen;
        char        Wave[4];
        CHUNK       FmtChk;
        FORMATCHUNK Format;
        CHUNK       DtaChk;
    } RiffHead;

/* WAV-Header-Test-Funktion                 */
int WavTstHead(FILE *InFH, SAMPLE_SPEC *Smp)
{
    CHUNK   Chunk;  /* Dummy zu Chunk-Lesen */
    char    Str[5]="";   /* Name des Chunks */

    memset(Smp,0,sizeof(SAMPLE_SPEC));

    if(!fread(&Chunk,sizeof(CHUNK),1L,InFH)) 
        return(UNKNOWN);

/* Testen, ob RIFF-Format                   */
    if(strncmp(Chunk.name,"RIFF", 4)) 
        return(UNKNOWN);

/* WAVE-Format muß gleich folgen            */
    fread(Str,4,1L,InFH); 
    if(strncmp(Str,"WAVE",4)) 
        return(NOT_SUPPORTED);

/* Chunk mit Sounddatenbeschreibung suchen  */ 
    if(!SearchChunk("fmt ",InFH)) 
        return (NOT_SUPPORTED);

/* Format-Chunk lesen und auswerten         */
    fread(&WavHead,sizeof(FORMATCHUNK),1L,InFH);
/* Inhalt des Chunks nach Motorola-Format   */ 
    ConvFmtChunk(&WavHead);

    if(WavHead.format!=WAVE_FORMAT_PCM) 
        return(NOT_SUPPORTED);

    Smp->Typ=WAVHEADER;
    Smp->SizeFac=1;
    switch (WavHead.channels)
    {
        case 1:
            Smp->Format=0;
            Smp->SizeFac*=2; 
            break;

        case 2:
            Smp->Format=STEREO;
            break;

        default:
            return (NOT_SUPPORTED);
    }

    Smp->Freq=WavHead.frequency;

    if(WavHead.bps>8)
        Smp->Format|=BIT16|SIGNED; 
    else
        Smp->SizeFac*=2;

/* Data-Chunk suchen                        */
    if(!(Smp->DataLen=SearchChunk("data",InFH))) 
        return (NOT_SUPPORTED);

    Smp->BufLen=BUFSIZE; 
    return(SUPPORTED);
}

/* WAV- nach Std.-Format konvertieren       */
long WavToStd(FILE *InFH,SAMPLE_SPEC *Smp,char *StdBuf,long StdBufLen)
{
    long DataRead;

    DataRead=AllToStd(InFH,Smp,StdBuf,StdBufLen);

/* Am Ende des Daten-Chunks mit dem Lesen   */
/* aufhören                                 */
    DataRead/=Smp->SizeFac; 
    if(DataRead>Smp->DataLen)
        DataRead=Smp->DataLen;

    Smp->DataLen-=DataRead;

    return(DataRead*Smp->SizeFac);
}

/* Std.-Format nach WAV konvertieren        */
long WavFromStd(FILE *OutFH,OUT_FORMAT *OutSmp,long DataLen,char *StdBuf)
{
    long DataWrite;

/* Kein Mu-Law, deltapack, 8bit-signed und  */
/* bigendian erlaubt                        */
    OutSmp->Format&=~(DELTAPACK|MULAW|BIGENDIAN|SIGNED);
/* 16 Bit nur signed                        */
    if(OutSmp->Format&BIT16)
        OutSmp->Format|=SIGNED;

    DataWrite=AllFromStd(OutFH,OutSmp,DataLen,StdBuf);
    ByteCount+=DataWrite; 

    return(DataWrite);
}

/* WAV-Header setzen und schreiben          */
int WavWrtHead(FILE *OutFH,SAMPLE_SPEC *InSmp, OUT_FORMAT *OutSmp)
{
    int Fmt; 
    long temp;

    Fmt=OutSmp->Format;
    memset(&RiffHead,0,sizeof(RiffHead));

/* Riff-, Wave-, Format- und Data-Chunks auf- */ 
/* bauen und speichern                      */
    strncpy(&RiffHead.Riff,"RIFF”,4);
    strncpy(&RiffHead.Wave,"WAVE",4); 
    strncpy(&RiffHead.FmtChk.name, "fmt ",4);
    strncpy(&RiffHead.DtaChk.name, “data",4);

    temp=sizeof(FORMATCHUNK);
/* Länge des Chunks im Intel-Format         */
    RiffHead.FmtChk.len=i_long(temp);
    RiffHead.Format.format=WAVE_FORMAT_PCM;

    RiffHead.Format.frequency=InSmp->Freq;

    RiffHead.Format.average=InSmp->Freq;
    RiffHead.Format.align=1;
    RiffHead.Format.bps=8;
    RiffHead.Format.channels=1;

    if(Fmt&STEREO)
    {
        RiffHead.Format.average*=2;
        RiffHead.Format.align*=2;
        RiffHead.Format.channels=2;
    }
    if(Fmt&BIT16)
    {
        RiffHead.Format.average*=2;
        RiffHead.Format.align*=2;
        RiffHead.Format.bps*=2;
    }

/* Sämtliche Werte im Chunk nach Intel-     */
/* Format wandeln                           */
    ConvFmtChunk(&RiffHead.Format); 
    return(fwrite(&RiffHead,sizeof(RiffHead),1,OutFH));
}

/* Fehlende Information in WAV-Header       */
/* schreiben                                */
int WavFinish(FILE *OutFH,OUT_FORMAT *OutSmp)
{
    long temp;

/* Länge der Daten                          */
    RiffHead.DtaChk.len=i_long(ByteCount);
/* File-Länge minus RIFF-Chunk-Länge        */
    temp=ByteCount+sizeof(RiffHead)-8;
    RiffHead.GesLen=i_long(temp);
    ByteCount=0;

    fseek(OutFH,0L,0);
    return(fwrite(&RiffHead,sizeof(RiffHead),1,OutFH));
}

/* Alle Chunks skippen, bis der in ChunkName */
/* angegebene vorliegt                      */
long SearchChunk(char *ChunkName, FILE *InFH)
{
    CHUNK Chunk;

    while (fread(&Chunk,sizeof(CHUNK),1L,InFH))
    {
        if (!strncmp(Chunk.name,ChunkName,4)) 
            return(i_long(Chunk.len));

        fseek(InFH,i_long(Chunk.len),SEEK_CUR);
    }

    return 0;
}

/* Fmt-Chunk-Inhalt nach Intel wandeln und  */
/* zurück                                   */
void ConvFmtChunk (FORMATCHUNK *Chunk)
{
    Chunk->format   = i_int (Chunk->format); 
    Chunk->channels = i_int (Chunk->channels); 
    Chunk->freguency= i_long (Chunk->frequency); 
    Chunk->average  = i_long (Chunk->average); 
    Chunk->align    = i_int (Chunk->align);
    Chunk->bps      = i_int (Chunk->bps);
}

/* DVSM-Headerfile zur DVSM-Konvertierung   */
/* Modulname: DVSM.H                        */
/* (c) MAXON Computer 1994                  */
/* Autoren: H. Schönfeld, B. Spellenberg    */

/* DVSM Header                              */
typedef struct
{
    char magic[6];          /* File-Kennung */
    unsigned int headlen;   /* Header-Länge */
    unsigned int freq;      /* Frequenz     */
    char pack;              /* Packformat   */
    char mode;              /* 8/16 Bit,    */
                            /* Stereo/Mono  */
    long blocklen;        /* Pack-Blocklänge*/
} DVSMHEAD;

/* Cookie-Struktur                          */
typedef struct
{
    long Name;              /* Cookie-Name  */
    unsigned int Len;       /* Cookie-Länge */
} DVSMCOOKIE;
/* Falcon Sample-Frequenzen                 */

# ifndef ACT_CLK50K
# define ACT_CLK50K 49170
# define ACT_CLK33K 32778
# define ACT_CLK25K 24585
# define ACT_CLK20K 19668
# define ACT_CLK16K 16390
# define ACT_CLK12K 12292
# define ACT_CLK10K 9834
# define ACT_CLK8K 8195
/* Vorteiler für Falcon DMA-Matrix          */
# define CLK50K 1
# define CLK33K 2
# define CLK25K 3
# define CLK20K 4
# define CLK16K 5
# define CLK12K 7
# define CLK10K 9
# define CLK8K 11
# endif

/* DVSM-Routinen zur DVSM-Konvertierung     */
/* Modulname: DVSM.C                        */
/* (c) MAXON Computer 1994                  */
/* Autoren: H. Schönfeld, B. Spellenberg    */

# include "vlun.h"
# include "dvsm.h"

/* Header für Im- und Export                */
static  DVSMHEAD DvsHead;
/* Tabelle aller DVSM-Festfrequenzen        */
static long DvsFreqTab[] =
       { ACT_CLK8K ,ACTJ3LK10K,ACT_CLK12K, 
         ACT_CLK16K,ACT_CLK20K,ACT_CLK25K, 
         ACT_CLK33K,ACT_CLK50K
       };
/* Zugehörige Falcon-DMA-Matrix Vorteiler   */
static int DvsDivTab[]= 
        { CLK8K,CLK10K,CLK12K,CLK16K,
          CLK20K,CLK25K,CLK33K,CLK50K
        };

/* DVSM-Header-Test-Funktion                */
int DvsTstHead(FILE *InFH, SAMPLE_SPEC *Smp)
{
    long FilePos;   /* Akt. Pos. im Header  */
    DVSMCOOKIE Cookie;/* Dummy zum Lesen der*/
                    /* DSVM Cookies         */
    int ClockFlag;  /* Wert des CLCK Cookies*/
    int Clock;      /* DMA-Matrix-Takt      */

    memset(Smp,0,sizeof(SAMPLE_SPEC));

    if(!fread(&DvsHead,sizeof(DVSMHEAD),1L,InFH)) 
        return(UNKNOWN); 
    if(strcmp(&DvsHead.magic,"DVSM")) 
        return(UNKNOWN);
    Smp->Typ=DVSMHEADER;

/* Beliebige Frequenz oder DVSM-Festfrequenz?*/ 
    if(DvsHead.freq>256)
        Smp->Freq=DvsHead.freq; 
    else
        Smp->Freq=DvsFreqTab[DvsHead.freq];

    switch(DvsHead.mode)
    {
        case 0:
            Smp->Format = STEREO|SIGNED; 
            Smp->SizeFac=2; 
            break;

        case 1:
            Smp->Format = STEREO|BIT16|BIGENDIAN|SIGNED;
            Smp->sizeFac=1;
            break;

        case 2:
            Smp->Format=SIGNED;
            Smp->SizeFac=4;
            break;

        default:
            return(NOT_SUPPORTED);
    }

    switch(DvsHead.pack)
    {
        case 0:                     /* ungepackt */
            Smp->BufLen=BUFSIZE; 
            break;

        case 2:                     /* Deltapack */
            Smp->BufLen=DvsHead.blocklen-2; 
            Smp->Format=STEREO|DELTAPACK|SIGNED; 
            Smp->SizeFac=2; 
            break;

        default; /* Weitere Formate in Vorher.  */ 
            return (NOT_SUPPORTED);
    }

/* Eventuell folgende Cookies lesen             */
    FilePos=sizeof(DVSMHEAD); 
    while(FilePos<DvsHead.headlen)
    {
        fread(&Cookie,sizeof(Cookie),1L,InFH);

/* Falls CLCK-Cookie, dann auswerten            */
        if(!strncmp(&Cookie.Name,"CLCK", 4))
        {
/* Cookie-Daten lesen                           */
            fread(&ClockFlag,2L,1L,InFH);
/* Nur gültig, wenn Festfrequenz im Header      */ 
            if((DvsHead.freq<=256) & ClockFlag)
            {
                if(ClockFlag==1)     /* CD Takt */
                    Clock=22579200L; 
                else
                    Clock=24576000L; /* DAT Takt*/
/* Eff. Frequenz aus Clock&Vorteiler berech.    */ 
                Smp->Freq=0.5+Clock/256.0/(DvsDivTab[DvsHead.freq]+1);
            }
            break;
        }
        else
/* Sonst Cookie-Daten skippen                   */
            fseek(InFH,Cookie.Len-sizeof(Cookie),1); 
        FilePos+=Cookie.Len;
    }

/* An Sounddaten-Anfang seeken                  */
    fseek(InFH,DvsHead.headlen,0);

    return(SUPPORTED);
}

/* DVSM nach Std.-Format konvertieren           */
long DvsToStd(FILE *InFH,SAMPLE_SPEC *Smp,char *StdBuf,long StdBufLen)
{
/* Anfangsadr. für die zu ladenden Daten        */
    char *InBuf;
/* Ergibt Länge im Standardformat               */
    long DataRead,
/* Offset für Datenbeginn im Standardformat     */ 
         DataLen;

/* Falls Daten im Deltapack, dann               */
    if(Smp->Format&DELTAPACK)
    {
/* Block+2 Stützpunkte ans Buffer-Ende laden    */ 
        DataLen=Smp->BufLen+2;
        InBuf=StdBuf+StdBufLen-DataLen;
        DataRead=fread(InBuf,1,DataLen,InFH);

        if(!DataRead)
            return(0); /* Datenende erreicht    */
        else
        {              /* Daten wandeln         */
            ConvD_Std(InBuf,StdBuf,DataRead);
/* Datenlänge im Std.-Format zurückgeben        */
            return((DataRead-2)*2);
        }
    }
    else
/* Allround-Wandelfunktion aufrufen             */
        return(AllToStd(InFH,Smp,StdBuf,StdBufLen));
}

/* Std.-Format nach DVSM konvertieren           */
long DvsFromStd(FILE *OutFH,OUT_FORMAT *OutSmp,long DataLen, char *StdBuf)
{
    long DataWrite;

/* Mu-Law und unsigned nicht erlaubt, 16 Bit    */
/* nur Stereo und Bigendian                     */
    OutSmp->Format&=~MULAW;
    OutSmp->FormatI=SIGNED; 
    if(OutSmp->Format&BIT16)
        OutSmp->Format|=BIGENDIAN|STEREO;

/* Falls Deltapack gewünscht...                 */
    if(OutSmp->Format&DELTAPACK)
    {
/* Daten konvertieren, mit 2 Stützstellen       */ 
        ConvStd_D(StdBuf,DataLen;; /* schreiben */ 
        DataLen=DataLen/2+2;
        DataWrite=fwrite(StdBuf,1,DataLen,OutFH);
    }
    else
/* Allround-Wandelfunktion aufrufen             */
        DataWrite=AllFromStd(OutFH,OutSmp,DataLen,StdBuf);
    return(DataWrite);
}

/* DVSM-Header setzen und schreiben             */
int DvsWrtHead(FILE *OutFH,SAMPLE_SPEC *InSmp, OUT_FORMAT *OutSmp)
{
    int Fmt, i;
/* Hilfsvar. zur Berechnung der Frequenz        */
    float tmp1,tmp2=1000000.0;

    Fmt=OutSmp->Format;
    memset(&DvsHead,0,sizeof(DVSMHEAD));

    strcpy(&DvsHead.magic,"DVSM");

/* Es werden keine Cookies geschrieben          */
    DvsHead.headlen=(int)sizeof(DVSMHEAD);

    if(Fmt&BIT16)
        DvsHead.mode=1; 
    else if(Fmt&STEREO)
        DvsHead.mode=0; 
    else
        DvsHead.mode=2;

    DvsHead.blocklen=1;

    if(Fmt&DELTAPACK)
    {
        DvsHead.mode=1;
        DvsHead.pack=2;
        DvsHead.blocklen=InSmp->BufLen;
/* Blocklänge korrigieren                       */
        if(!(InSmp->Format&STEREO))
            DvsHead.blocklen*=2; 
        if(InSmp->Format&BIT16)
            DvsHead.blocklen/=2;
/* Stützpunkte dazuzählen                       */
        DvsHead.blocklen+=2;
    }

/* Näheste DVSM-Festfrequenz suchen             */
    for (i = 0; i < 8; i++) {
        tmp1 = ((float)abs(InSmp->Freq-DvsFreqTab[i]))/DvsFreqTab[i]; 
        if (tmp1 < tmp2) { 
            tmp2 = tmp1;
            DvsHead.freq = i;
        }
    }

    return(fwrite(&DvsHead,sizeof(DVSMHEAD),1,OutFH));
}


Aus: ST-Computer 06 / 1994, Seite 68

Links

Copyright-Bestimmungen: siehe Über diese Seite