source/WAVE.c

/*
**  Apple Macintosh Developer Technical Support
**
**  Routines demonstrating how to parse Microsoft's WAVE sound files.
**
**  by Mark Cookson, Apple Developer Technical Support
**
**  File:   WAVE.c
**
**  Copyright ©1996-1998 Apple Computer, Inc.
**  All rights reserved.
**
**  You may incorporate this sample code into your applications without
**  restriction, though the sample code has been provided "AS IS" and the
**  responsibility for its operation is 100% yours.  However, what you are
**  not permitted to do is to redistribute the source as "Apple Sample
**  Code" after having made changes. If you're going to re-distribute the
**  source, we require that you make it clear in the source that the code
**  was descended from Apple Sample Code, but that you've made changes.
*/
 
#include "WAVE.h"
 
/* Please note that this code was written in less than 6 hours, does very little error checking
   and probably will not work for all files.  I have tested it with the WAVE files at my
   disposal and it worked with them.  Your milage may vary.
 
   I do know that it will probably be impossible to get the Mac's IMA decompressor to
   decompress IMA (and ADPCM) compressed WAVE files because the Mac's IMA has a different
   file format from the WAVE file and the decompressor cannot correctly decompress WAVE files.
 
   The Mac IMA compressed file has predictor bytes in the data stream that the WAVE files
   do not have.
*/
 
/* Given an WAVE File gets the sample rate and other relavent pieces of information.
   This has been modified from its orignal form found in the Develop 11 MultiBuffer source. */
/*-----------------------------------------------------------------------*/
        OSErr   ASoundGetWAVEHeader     (SoundInfoPtr theSoundInfo, long *len, fmtChunk *formatChunk)
/*-----------------------------------------------------------------------*/
{
    WAVETemplatePtr         WAVETemplate;                   /* ...for ease of access */
    long                    filePosition        = kInit,
                            byteCount           = kInit;
    short                   format              = kInit;
    unsigned short          chunkFlags          = kInit;    /* remember chunks we've seen */
    OSErr                   err                 = noErr;
    char                    chunkBuffer[kWAVEChunkBufferSize];
 
    theSoundInfo->dataStart = kInit;
 
    /*  Now for the fun part!  We're going to assume that these are generic, non-compressed
            WAVE files and parse accordingly... */
 
    do {
        /*  Position ourselves at the beginning of the next chunk and read in
            a hunk-o-data... */
        err = SetFPos (theSoundInfo->refNum, fsFromStart, filePosition);
        if (err != noErr) {
            DebugPrint ("\pSetFPos failed!");
            break;
        }
 
        byteCount = kWAVEChunkBufferSize;
        err = FSRead (theSoundInfo->refNum, &byteCount, chunkBuffer);                                               /* read a chunk */
        if ((err != noErr) && (err != eofErr)) {
            DebugPrint ("\pFSRead failed!");
            break;
        }
 
        /*  Now, position the template over the data... */
        WAVETemplate = (WAVETemplatePtr) chunkBuffer;
 
        /* assume a failure and break out of the do {} while loop if the next case isn't found.
           if the case is found and no other error is detected, then each case needs to set noErr */
        err = kBadWAVEFileFormat;                               
        switch (WAVETemplate->generic.ckID) {
            case WAVEFORMID:
                chunkFlags |= kWAVEFORMID;                                                              /* otherwise mark it found */
                //filePosition += ReverseLong (WAVETemplate->container.ckSize) + 8;
                filePosition += 12;
                err = noErr;
                break;
            case FormatID:
                chunkFlags |= kFormatID;                                                                /* otherwise mark it found */
                format = SwapShort (WAVETemplate->fmt.wFormatTag);
                theSoundInfo->needsMasking = false;
                switch (format) {
                    case 2:     // MS ADPCM
                    case 17:    // IMA ADPCM
                        BlockMoveData (&(WAVETemplate->fmt.wFormatTag), formatChunk, ReverseLong (WAVETemplate->fmt.ckSize));
                        err = noErr;
                    case WAVE_FORMAT_PCM:
                        err = SetupDBHeader (theSoundInfo,
                                            SwapLong (WAVETemplate->fmt.dwSamplesPerSec),
                                            ((SwapShort (WAVETemplate->fmt.wBlockAlign) / SwapShort (WAVETemplate->fmt.wChannels)) * 8),
                                            SwapShort (WAVETemplate->fmt.wChannels),
                                            notCompressed,
                                            NoneType);
                        break;
                    case WAVE_FORMAT_MULAW:
                    case IBM_FORMAT_MULAW:
                        err = SetupDBHeader (theSoundInfo,
                                            SwapLong (WAVETemplate->fmt.dwSamplesPerSec),
                                            ((SwapShort (WAVETemplate->fmt.wBlockAlign) / SwapShort (WAVETemplate->fmt.wChannels)) * 8),
                                            SwapShort (WAVETemplate->fmt.wChannels),
                                            fixedCompression,
                                            kULawCompression);
                        //filePosition += 2;    /* This is here because the only µlaw compressed file I ever saw needed it. */
                        break;
                    default:
                        DebugPrint ("\pCan't deal with this file format");
                        err = kUnknownFormat;
                        break;
                }
                filePosition += ReverseLong (WAVETemplate->fmt.ckSize) + 8;
                //filePosition += 24;
                break;
            case WAVEDataID:
                chunkFlags |= kWAVEDataID;
                theSoundInfo->bytesTotal = ReverseLong (WAVETemplate->waveData.ckSize);
                filePosition += 8;
                theSoundInfo->dataStart = filePosition;
                err = noErr;
                break;
            /* We don't care about any of these, and my handling of them is probably not correct */
            case WAVEID:
                filePosition += ReverseLong (WAVETemplate->fact.ckSize) + 8;
                err = noErr;
                break;
            case WAVEListID:
                filePosition += ReverseLong (WAVETemplate->fact.ckSize) + 8;
                err = noErr;
                break;
            case SilenceID:
                filePosition += ReverseLong (WAVETemplate->fact.ckSize) + 8;
                err = noErr;
                break;
            case CueID:
                filePosition += ReverseLong (WAVETemplate->cuePoints.ckSize) + 8;
                err = noErr;
                break;
            case FactID:
                filePosition += ReverseLong (WAVETemplate->fact.ckSize) + 8;
                *len = ReverseLong (WAVETemplate->fact.dwFileSize);
                err = noErr;
                break;
            case PlaylistID:
                filePosition += ReverseLong (WAVETemplate->playList.ckSize) + 8;
                err = noErr;
                break;
            case AssocDataID:
                filePosition += ReverseLong (WAVETemplate->assocData.ckSize) + 8;
                err = noErr;
                break;
            case LabelID:
                filePosition += ReverseLong (WAVETemplate->label.ckSize) + 8;
                err = noErr;
                break;
            case NoteID:
                filePosition += ReverseLong (WAVETemplate->note.ckSize) + 8;
                err = noErr;
                break;
            case TextWithLenID:
                filePosition += ReverseLong (WAVETemplate->ltxt.ckSize) + 8;
                err = noErr;
                break;
            case EmbededFileID:
                filePosition += ReverseLong (WAVETemplate->file.ckSize) + 8;
                err = noErr;
                break;
            default:
                //DebugPrint ("\pSaw a field we didn't know about, this doesn't have to be bad");
                /* Start walking through the file looking for an ID we know about.
                   This could get ugly, but what the heck... */
                filePosition += 1;
                err = noErr;
                break;
        }
    } while (stillMoreDataWAVEToRead);
 
    if (err != noErr) {
        DebugPrint ("\pError in ASoundGetWAVEHeader");
    }
 
    return err;
}
 
/* These are here for the endian conversion required to parse the header.
   The endian conversion used when playing a WAVE file is much better, don't worry.
 */
short SwapShort (const short theShort) {
    char    tempChar    = kInit;
    short   newShort    = kInit;
 
    tempChar = ((char *)&theShort)[0];
    ((char *)&newShort)[0] = ((char *)&theShort)[1];
    ((char *)&newShort)[1] = tempChar;
 
    return newShort;
}
 
long SwapLong (const long theLong) {
    short   tempShort   = kInit;
    long    newLong     = kInit;
 
    tempShort = SwapShort (((short *)&theLong)[0]);
    ((short *)&newLong)[0] = tempShort;
    tempShort = SwapShort (((short *)&theLong)[1]);
    ((short *)&newLong)[1] = tempShort;
 
    return newLong;
}
 
long ReverseLong (const long theLong) {
    char    tempChar    = kInit,
            i           = kInit;
    long    newLong     = kInit;
 
    for (i = nil; i < 2; i++) {
        tempChar = ((char *)&theLong)[i];
        ((char *)&newLong)[i] = ((char *)&theLong)[3-i];
        ((char *)&newLong)[3-i] = tempChar;
    }
 
    return newLong;
}