source/IMADecompression.c

/*
**  Apple Macintosh Developer Technical Support
**
**  Routines demonstrating how to play IMA compressed WAVE files
**  using a combination of QuickTime and the Sound Manager.
**
**  by Mark Cookson, Apple Developer Technical Support
**
**  File:   IMADecompression.c
**
**  Copyright ©1998-1999 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 <Memory.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Windows.h>
#include <Menus.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Sound.h>
#include <SoundInput.h>
#include <Files.h>
#include <Endian.h>
#include <Navigation.h>
 
#include <stdio.h>
#include "SoundStruct.h"
#include "Wave.h"
 
typedef struct adpcmcoef_tag {
    short   iCoef1;
    short   iCoef2;
} ADPCMCOEFSET;
 
typedef struct waveformat_extended_tag {
    short       wFormatTag;         /* format type */
    short       nChannels;          /* number of channels (i.e. mono, stereo...) */
    long        nSamplesPerSec;     /* sample rate */
    long        nAvgBytesPerSec;    /* for buffer estimation */
    short       nBlockAlign;        /* block size of data */
    short       wBitsPerSample;     /* Number of bits per sample of mono data */
    short       cbSize;             /* The count in bytes of the extra size */
} WAVEFORMATEX;
 
typedef struct adpcmwaveformat_tag {
    WAVEFORMATEX    wfx;
    short           wSamplesPerBlock;
    short           wNumCoef;
    ADPCMCOEFSET    aCoef[32];
} ADPCMWAVEFORMAT;
 
typedef struct {
    long            atomSize;           // how big this structure is (big endian)
    long            atomType;           // atom type - always kMicrosoftADPCMFormat (big endian)
    // everything below here is little endian - right out of the wave header
    ADPCMWAVEFORMAT adpcm;
} AtomMSADPCMWaveFormatEx;
 
typedef struct {
    AudioFormatAtom             formatData;
    AtomMSADPCMWaveFormatEx     endianData;
    AudioTerminatorAtom         terminatorData;
} AudioCompressionAtom, *AudioCompressionAtomPtr, **AudioCompressionAtomHandle;
 
// Prototypes
OSErr InstallRequiredAppleEvents (void);
pascal OSErr HandleOApp (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
pascal OSErr HandleODoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
pascal OSErr HandlePDoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
pascal OSErr HandleQuit (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
OSErr GetSoundToPlay (FSSpec *fileToPlay);
OSErr PlaySound (FSSpec *fileToPlay);
 
// Globals
SoundInfo               theSoundInfo;
SndCommand              playCmd1,
                        playCmd2,
                        callCmd;
SndCommand*             playCmd             = nil;
CmpSoundHeader          WAVESndHeader1,
                        WAVESndHeader2;
CmpSoundHeaderPtr       WAVESndHeader       = nil;
SndChannelPtr           soundChan           = nil;
SoundConverter          sc                  = nil;
SndCallBackUPP          SoundCallBackFcnUPP = nil;
Ptr                     decomBuf            = nil,
                        decomBuf1           = nil,
                        decomBuf2           = nil,
                        WAVEBuffer          = nil,
                        compressedBuf       = nil;
unsigned long           inputFrames         = 0,
                        framesConverted     = 0,
                        totalFrames         = 0,
                        bytesConverted      = 0,
                        inputBytes          = 0,
                        outputFrames        = 0,
                        outputBytes         = 0,
                        gSleepTime          = 60;
long                    length              = 0;
short                   whichBuffer         = 0;
Boolean                 gBufferDone         = false,
                        Quitting            = false,
                        gSoundPlaying       = false;
 
static pascal void  SoundCallBackFcn (SndChannelPtr theChannel, SndCommand *theCmd) {
#pragma unused (theChannel)
    #if !GENERATINGCFM
        long        oldA5;
        oldA5 = SetA5 (theCmd->param2);
    #endif
 
    gBufferDone = true;
 
    #if !GENERATINGCFM
        oldA5 = SetA5 (oldA5);
    #endif
}
 
static OSErr MenuBarInit (void) {
    Handle                  menuBar;
    MenuHandle              menu;
    OSErr                   err             = noErr;
 
    menuBar = GetNewMBar (128);
    if (menuBar != nil) {
        SetMenuBar (menuBar);
        menu = GetMenuHandle (128);
        if (menu != nil) {
            AppendResMenu (menu, 'DRVR');
            DrawMenuBar ();
        } else {
            err = memFullErr;
        }
    } else {
        err = memFullErr;
    }
 
    return err;
}
 
static OSErr DispatchMenuChoice (long menuChoice) {
    OSErr                       err                 = noErr;
    short                       menu;
    short                       item;
    MenuHandle                  appleMenu;
    Str255                      accName;
    short                       accNumber;
    FSSpec                      fileToPlay;
 
    if (menuChoice != 0) {
        menu = HiWord (menuChoice);
        item = LoWord (menuChoice);
        switch (menu) {
            case 128:   // Apple Menu
                appleMenu = GetMenuHandle (128);
                GetMenuItemText (appleMenu, item, accName);
                accNumber = OpenDeskAcc (accName);
                break;
            case 129:   // File Menu
                switch (item) {
                    case 1: // Open
                        err = GetSoundToPlay (&fileToPlay);
                        break;
                    case 3: // Quit
                        Quitting = true;
                        break;
                }
        }
    }
 
    HiliteMenu (0);
 
    return err;
}
 
static void DoIdle (void) {
    Boolean         cleanup     = false;
    OSErr           err         = noErr;
 
    if (gSoundPlaying == true && gBufferDone == true) {
        if (whichBuffer == 1) {
            playCmd = &playCmd1;
            decomBuf = decomBuf1;
            WAVESndHeader = &WAVESndHeader1;
            whichBuffer = 2;
        } else {
            playCmd = &playCmd2;
            decomBuf = decomBuf2;
            WAVESndHeader = &WAVESndHeader2;
            whichBuffer = 1;
        }
 
        if (bytesConverted < length) {
            if (bytesConverted + inputBytes > length) {
                inputBytes = length - bytesConverted;
                inputFrames = totalFrames - framesConverted;
                if (inputFrames == 0)
                    inputFrames = 1;
            }
 
            BlockMoveData (WAVEBuffer + bytesConverted, compressedBuf, inputBytes);
 
            err = SoundConverterConvertBuffer (sc, compressedBuf, inputFrames, decomBuf, &outputFrames, &outputBytes);
            framesConverted += inputFrames;
            bytesConverted += inputBytes;
            WAVESndHeader->numFrames = outputFrames;
 
            gBufferDone = false;
            err = SndDoCommand (soundChan, &callCmd, true);         // Reuse callBackCmd.
            err = SndDoCommand (soundChan, playCmd, true);          // Play the next buffer.
        } else {
            err = SoundConverterEndConversion (sc, decomBuf, &outputFrames, &outputBytes);
            WAVESndHeader->numFrames = outputFrames;
 
            err = SndDoCommand (soundChan, playCmd, true);          // Play the last buffer.
            gSoundPlaying = false;
            cleanup = true;
        }
    }
 
    if (cleanup == true) {
        if (sc != nil) {
            err = SoundConverterClose (sc);
        }
 
        if (soundChan != nil)
            err = SndDisposeChannel (soundChan, false); // wait until sounds stops playing before disposing of channel
        if (theSoundInfo.refNum)
            FSClose (theSoundInfo.refNum);
        if (SoundCallBackFcnUPP)
            DisposeRoutineDescriptor (SoundCallBackFcnUPP);
        if (decomBuf1)
            DisposePtr (decomBuf1);
        if (decomBuf2)
            DisposePtr (decomBuf2);
        if (WAVEBuffer)
            DisposePtr (WAVEBuffer);
        if (compressedBuf)
            DisposePtr (compressedBuf);
 
        soundChan           = nil;
        decomBuf            = nil;
        decomBuf1           = nil;
        decomBuf2           = nil;
        WAVEBuffer          = nil;
        compressedBuf       = nil;
        gSleepTime          = 60;
    }
}
 
void main (void) {
    OSErr                       err                 = noErr;
    Boolean                     gotEvent;
    EventRecord                 event;
    WindowPtr                   window;
    short                       thePart;
 
    MaxApplZone ();
 
    InitGraf (&qd.thePort);
    InitFonts ();
    InitWindows ();
    InitMenus ();
    TEInit ();
    InitDialogs ((long)nil);
    InitCursor ();
 
    err = InstallRequiredAppleEvents ();
 
    err = MenuBarInit ();
 
    while (!Quitting) {
        gotEvent = WaitNextEvent (everyEvent, &event, gSleepTime, nil);
 
        if (gotEvent) {
            switch (event.what) {
                case kHighLevelEvent:
                    err = AEProcessAppleEvent (&event);
                    break;
                case mouseDown:
                    thePart = FindWindow (event.where, &window);
                    switch (thePart) {
                        case inMenuBar:
                            DispatchMenuChoice (MenuSelect (event.where));
                            break;
                    }
                    break;
                case keyDown:
                    if (event.modifiers & cmdKey) {
                        err = DispatchMenuChoice (MenuKey (event.message & charCodeMask));
                    }
                    break;
            }
        } else {
            DoIdle ();
        }
    }
}
 
OSErr GetSoundToPlay (FSSpec *fileToPlay) {
    OSErr                   err                 = noErr;
    SFTypeList              typeList            = {'WAVE', 'wav ', 0, 0};
    StandardFileReply       sfReply;
 
    if (NavServicesAvailable () == true) {
        NavReplyRecord              navReply;
        NavDialogOptions            dialogOptions;
 
        err = NavGetDefaultDialogOptions (&dialogOptions);
        if (err == noErr) {
            dialogOptions.dialogOptionFlags = kNavAllFilesInPopup;
        }
 
        if (err == noErr) {
            err = NavGetFile (nil, &navReply, &dialogOptions, nil, nil, nil, nil, nil);
        }
 
        if (navReply.validRecord && err == noErr) {
            ProcessSerialNumber         processSN       = {0, kCurrentProcess};
            AEAddressDesc               targetAddress   = {typeNull, nil};
            AppleEvent                  theODOC         = {typeNull, nil},
                                        theReply        = {typeNull, nil};
 
            // Create an Apple Event to ourselves.
            err = AECreateDesc (typeProcessSerialNumber, &processSN, sizeof (ProcessSerialNumber), &targetAddress);
 
            if (err == noErr) {
                // Create the open document event.
                err = AECreateAppleEvent (kCoreEventClass, kAEOpenDocuments, &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &theODOC);
                AEDisposeDesc (&targetAddress);
            }
 
            if (err == noErr) {
                // Put the list of files into the open document event Apple Event.
                err = AEPutParamDesc (&theODOC, keyDirectObject, &(navReply.selection));
            }
 
            if (err == noErr) {
                // Send the open document event to ourselves.
                err = AESend (&theODOC, &theReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil);
                AEDisposeDesc (&theODOC);
                AEDisposeDesc (&theReply);
            }
 
        }
 
        (void)NavDisposeReply (&navReply);
    } else {
        StandardGetFile (nil, 2, typeList, &sfReply);
 
        if (sfReply.sfGood == true) {
            *fileToPlay = sfReply.sfFile;
            err = PlaySound (fileToPlay);
        } else {
            err = userCanceledErr;
        }
    }
 
    return err;
}
 
OSErr PlaySound (FSSpec *fileToPlay) {
    OSErr                   err                 = noErr;
    AudioCompressionAtom    decomAtom;
    SoundComponentData      inputFormat,
                            outputFormat;
    OSType                  waveFormat;
    unsigned long           targetBytes         = 25000;
 
    gSleepTime = 1;
 
    if (err == noErr) {
        err = FSpOpenDF (fileToPlay, fsRdPerm, &theSoundInfo.refNum);
 
        if (err == noErr) {
            // Parse the WAVE file and get the 'fmt ' atom.
            err = ASoundGetWAVEHeader (&theSoundInfo, &length, (fmtChunk*)&(decomAtom.endianData.adpcm));
        }
 
        if (err == noErr) {
            // Figure out which type of ADPCM file we have.
            switch (EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.wFormatTag)) {
                case 0x0002:
                    waveFormat = kMicrosoftADPCMFormat;
                    break;
                case 0x0011:
                    waveFormat = kDVIIntelIMAFormat;
                    break;
                default:
                    err = badFormat;
            }
        }
    }
 
    if (err == noErr) {
        inputFormat.flags = 0;
        inputFormat.format = waveFormat;
        inputFormat.numChannels = EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.nChannels);
        inputFormat.sampleSize = EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.wBitsPerSample);
        inputFormat.sampleRate = (EndianU32_LtoN (decomAtom.endianData.adpcm.wfx.nSamplesPerSec)) << 16;
        inputFormat.sampleCount = 0;
        inputFormat.buffer = nil;
        inputFormat.reserved = 0;
 
        outputFormat.flags = 0;
        outputFormat.format = kSoundNotCompressed;
        outputFormat.numChannels = EndianU16_LtoN (decomAtom.endianData.adpcm.wfx.nChannels);
        outputFormat.sampleSize = 16;
        outputFormat.sampleRate = (EndianU32_LtoN (decomAtom.endianData.adpcm.wfx.nSamplesPerSec)) << 16;
        outputFormat.sampleCount = 0;
        outputFormat.buffer = nil;
        outputFormat.reserved = 0;
 
        err = SoundConverterOpen (&inputFormat, &outputFormat, &sc);
    }
 
    if (err == noErr) {
        // Make atom to send to ADPCM decompressor so it knows how to decompress the data.
        decomAtom.formatData.size = sizeof (AudioFormatAtom);
        decomAtom.formatData.atomType = kAudioFormatAtomType;
        decomAtom.formatData.format = waveFormat;
 
        decomAtom.endianData.atomSize = sizeof (AtomMSADPCMWaveFormatEx);
        decomAtom.endianData.atomType = waveFormat;
 
        decomAtom.terminatorData.size = sizeof (AudioTerminatorAtom);
        decomAtom.terminatorData.atomType = kAudioTerminatorAtomType;
 
        err = SoundConverterSetInfo (sc, siDecompressionParams, &decomAtom);
    }
 
    if (err == noErr) {
        // Find out how many frames are in the entire sound.
        err = SoundConverterGetBufferSizes (sc, length * 4, &totalFrames, &inputBytes, &outputBytes);
    }
 
    if (err == noErr) {
        do {
            targetBytes *= 2;
            err = SoundConverterGetBufferSizes (sc, targetBytes, &inputFrames, &inputBytes, &outputBytes);
        } while (err == notEnoughBufferSpace && targetBytes < (MaxBlock () / 4));
    }
 
    if (err == noErr) {
        WAVEBuffer = NewPtr (length);
        err = MemError ();
    }
 
    if (err == noErr) {
        decomBuf1 = NewPtr (outputBytes);
        err = MemError ();
    }
 
    if (err == noErr) {
        decomBuf2 = NewPtr (outputBytes);
        err = MemError ();
    }
 
    if (err == noErr) {
        compressedBuf = NewPtr (inputBytes);
        err = MemError ();
    }
 
    if (err == noErr) {
        err = SetFPos (theSoundInfo.refNum, fsFromStart, theSoundInfo.dataStart);
    }
 
    if (err == noErr) {
        err = FSRead (theSoundInfo.refNum, &length, WAVEBuffer);
    }
 
    if (err == noErr) {
        BlockMoveData (WAVEBuffer, compressedBuf, inputBytes);
        err = SoundConverterBeginConversion (sc);
    }
 
    if (err == noErr) {
        bytesConverted = 0;
        framesConverted = 0;
        err = SoundConverterConvertBuffer (sc, compressedBuf, inputFrames, decomBuf1, &outputFrames, &outputBytes);
        bytesConverted += inputBytes;
        framesConverted += inputFrames;
    }
 
    if (err == noErr) {
        if (bytesConverted + inputBytes > length) {
            inputBytes = length - bytesConverted;
            inputFrames = totalFrames - framesConverted;
        }
        BlockMoveData (WAVEBuffer + bytesConverted, compressedBuf, inputBytes);
        err = SoundConverterConvertBuffer (sc, compressedBuf, inputFrames, decomBuf2, &outputFrames, &outputBytes);
        bytesConverted += inputBytes;
        framesConverted += inputFrames;
    }
 
    if (err == noErr) {
        SoundCallBackFcnUPP = NewSndCallBackProc (SoundCallBackFcn);
        err = SndNewChannel (&soundChan, sampledSynth, 0, SoundCallBackFcnUPP);
    }
 
    if (err == noErr) {
        WAVESndHeader1.samplePtr = decomBuf1;
        WAVESndHeader1.numChannels = outputFormat.numChannels;
        WAVESndHeader1.sampleRate = outputFormat.sampleRate;
        WAVESndHeader1.loopStart = 0;
        WAVESndHeader1.loopEnd = 0;
        WAVESndHeader1.encode = cmpSH;
        WAVESndHeader1.baseFrequency = kMiddleC;
        WAVESndHeader1.numFrames = outputFrames;
        WAVESndHeader1.AIFFSampleRate = 0;      // not used
        WAVESndHeader1.markerChunk = nil;
        WAVESndHeader1.format = outputFormat.format;
        WAVESndHeader1.futureUse2 = 0;
        WAVESndHeader1.stateVars = nil;
        WAVESndHeader1.leftOverSamples = nil;
        WAVESndHeader1.compressionID = fixedCompression;        // even uncompressed sounds use fixedCompression
        WAVESndHeader1.packetSize = 0;          // the Sound Manager will figure this out for us
        WAVESndHeader1.snthID = 0;
        WAVESndHeader1.sampleSize = outputFormat.sampleSize;
        WAVESndHeader1.sampleArea[0] = 0;       // no samples here because use samplePtr instead
 
        BlockMoveData (&WAVESndHeader1, &WAVESndHeader2, sizeof (WAVESndHeader1));
        WAVESndHeader2.samplePtr = decomBuf2;           
 
        playCmd1.cmd = bufferCmd;
        playCmd1.param1 = 0;                        // not used, but clear it out anyway just to be safe
        playCmd1.param2 = (long)&WAVESndHeader1;
 
        playCmd2.cmd = bufferCmd;
        playCmd2.param1 = 0;                        // not used, but clear it out anyway just to be safe
        playCmd2.param2 = (long)&WAVESndHeader2;
 
        whichBuffer = 1;                            // buffer 1 will be free when callback runs
        callCmd.cmd = callBackCmd;
        callCmd.param2 = SetCurrentA5 ();
 
        gBufferDone = false;
        err = SndDoCommand (soundChan, &playCmd1, true);
    }
 
    if (err == noErr) {
        err = SndDoCommand (soundChan, &callCmd, true);
    }
 
    if (err == noErr) {
        err = SndDoCommand (soundChan, &playCmd2, true);
        gSoundPlaying = true;
    }
 
    return err;
}
 
OSErr InstallRequiredAppleEvents (void) {
    OSErr       err;
 
    err = AEInstallEventHandler (kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc (HandleOApp), 0, false);
 
    if (err == noErr)
        err = AEInstallEventHandler (kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc (HandleODoc), 0, false);
 
    if (err == noErr)
        err = AEInstallEventHandler (kCoreEventClass, kAEPrintDocuments, NewAEEventHandlerProc (HandlePDoc), 0, false);
 
    if (err == noErr)
        err = AEInstallEventHandler (kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc (HandleQuit), 0, false);
 
    return err;
}
 
pascal OSErr HandleOApp (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
#pragma unused (theAppleEvent, reply, handlerRefcon)
 
    return noErr;               /* We're up and running */
}
 
pascal OSErr HandleODoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
#pragma unused (reply, handlerRefcon)
 
    AEDescList          docList;
    OSErr               err;
    long                i               = 1,
                        itemsInList;
    Size                actualSize;
    AEKeyword           keywd;
    DescType            returnedType;
 
    err = AEGetParamDesc (theAppleEvent, keyDirectObject, typeAEList, &docList);
    if (err == noErr) {
        err = AECountItems (&docList, &itemsInList);
    }
 
    if (err == noErr) {
        FSSpecPtr   fileSpecPtr;
 
        do {
            fileSpecPtr = (FSSpecPtr)NewPtr (sizeof (FSSpec));
            err = MemError ();
 
            if (err == noErr) {
                err = AEGetNthPtr (&docList, i, typeFSS, &keywd, &returnedType, fileSpecPtr, sizeof (FSSpec), &actualSize);
            }
 
            if (err == noErr) {
                HParamBlockRec      pb;
 
                pb.fileParam.ioCompletion = nil;
                pb.fileParam.ioNamePtr = fileSpecPtr->name;
                pb.fileParam.ioVRefNum = fileSpecPtr->vRefNum;
                pb.fileParam.ioDirID = fileSpecPtr->parID;
                pb.fileParam.ioFDirIndex = 0;
 
                err = PBHGetFInfoSync (&pb);
                if (err == noErr && pb.fileParam.ioFlFndrInfo.fdType != 'pref') {
                    err = PlaySound (fileSpecPtr);
                    DisposePtr ((Ptr)fileSpecPtr);
                }
            }
 
            i += 1;
        } while (err == noErr);
 
        // The last time through the loop we allocate a pointer we don't need.
        DisposePtr ((Ptr)fileSpecPtr);
    }
 
    (void)AEDisposeDesc (&docList);
 
    return err;
}
 
pascal OSErr HandlePDoc (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
#pragma unused (theAppleEvent, reply, handlerRefcon)
 
    return noErr;
}
 
pascal OSErr HandleQuit (AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon) {
#pragma unused (theAppleEvent, reply, handlerRefcon)
 
    Quitting = true;
    return noErr;
}