SndEqualizer.c

//////////
//
//  File:       SndEqualizer.c
//
//  Contains:   Sample code for displaying a graphic equalizer (a la QuickTime Player).
//
//  Written by: Tim Monroe
//
//  Copyright:  © 2000 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <2>      04/07/00    rtm     added functions to shown equalizer display with sound files
//     <1>      03/24/00    rtm     first file
//
//  This file contains functions that create and manage a dialog window containing a graphic
//  equalizer display, similar to the one in the "LCD panel" in QuickTime Player. Really there
//  are just two key functions that you use to make this happen, MediaSetSoundEqualizerBands
//  and MediaGetSoundEqualizerBandLevels.
//
//  For displaying a graphic equalizer while a sound resource is playing, you instead use the
//  SndSetInfo and SndGetInfo functions, with the siEQSpectrumBands, siEQSpectrumOnOff, and 
//  siEQSpectrumLevels selectors.
//
//////////
 
#include "SndEqualizer.h"
 
 
//////////
//
// global variables
//
//////////
 
UInt8                       gLevels[kSndEqNumBands];
 
DialogPtr                   gSndEqDialog = NULL;
Boolean                     gSndEqDialogIsVis = false;
UserItemUPP                 gSndEqUserItemProcUPP = NULL;           // UPP to our custom dialog user item procedure
 
RGBColor                    gDGray = {0x7777, 0x7777, 0x7777};
RGBColor                    gBlack = {0x0000, 0x0000, 0x0000};
//RGBColor                  gMauve = {0x9999, 0xcccc, 0xffff};
RGBColor                    gMauve = {0xffff, 0x6666, 0xffff};
 
// globals variables for playing sound resources
SndChannelPtr               gSndEqSndChannelPtr = NULL;
SndListHandle               gSndEqResourceHandle = NULL;
short                       gSndEqResourceFile = -1;
Boolean                     gSndEqResourcePlaying = false;
Boolean                     gSndEqResourceDone = false;
SndCallBackUPP              gSndCallBackProc = NULL;                // UPP to our sound callback procedure
 
 
//////////
//
// SndEq_Init
// Do any initialization required for displaying the sound equalizer.
//
//////////
 
OSErr SndEq_Init (void)
{
    gSndEqUserItemProcUPP = NewUserItemUPP(SndEq_UserItemProcedure);
    
    SndEq_InitSoundResource();
    
    return(SndEq_ShowDialog());
}
 
 
//////////
//
// SndEq_Stop
// Do any clean-up required for hiding the sound equalizer.
//
//////////
 
OSErr SndEq_Stop (void)
{
    DisposeUserItemUPP(gSndEqUserItemProcUPP);
    
    SndEq_StopSoundResource();
 
    return(SndEq_HideDialog());
}
 
 
//////////
//
// SndEq_InitWindowData
// Do any window-specific initialization for displaying the sound equalizer.
//
//////////
 
Handle SndEq_InitWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl              myAppData = NULL;
    Track                           myTrack = NULL;
    MediaHandler                    myHandler = NULL;
    UnsignedFixed                   myBands[kSndEqNumBands];
    MediaEQSpectrumBandsRecord      myInfo;
 
    myAppData = (ApplicationDataHdl)NewHandleClear(sizeof(ApplicationDataRecord));
    if (myAppData != NULL) {
    
        // find the sound media handler
        myTrack = GetMovieIndTrackType((**theWindowObject).fMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
        if (myTrack != NULL)
            myHandler = GetMediaHandler(GetTrackMedia(myTrack));
    
        // remember the sound media handler
        (**myAppData).fSoundMediaHandler = myHandler;
        
        // set the frequencies for the bands
        myBands[0] = kBandFreq0;
        myBands[1] = kBandFreq1;
        myBands[2] = kBandFreq2;
        myBands[3] = kBandFreq3;
        myBands[4] = kBandFreq4;
        myBands[5] = kBandFreq5;
        myBands[6] = kBandFreq6;
        myBands[7] = kBandFreq7;
        
        myInfo.count = kSndEqNumBands;
        myInfo.frequency = myBands;
        
        MediaSetSoundEqualizerBands(myHandler, &myInfo);
    }
    
    return((Handle)myAppData);
}
 
 
//////////
//
// SndEq_DumpWindowData
// Do any window-specific initialization for displaying the sound equalizer.
//
//////////
 
OSErr SndEq_DumpWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl      myAppData = NULL;
    OSErr                   myErr = noErr;
    
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        goto bail;
 
    DisposeHandle((Handle)myAppData);
    
bail:   
    return(myErr);
}
 
 
//////////
//
// SndEq_ShowDialog
// Show the equalizer display dialog box.
//
//////////
 
OSErr SndEq_ShowDialog (void)
{
    short       myItemKind;
    Handle      myItemHandle = NULL;
    Rect        myItemRect;
    OSErr       myErr = noErr;
 
    if (gSndEqDialog == NULL) {
        gSndEqDialog = GetNewDialog(kSndEqResID, NULL, (WindowPtr)-1L);
        if (gSndEqDialog == NULL) {
            myErr = ResError();
            goto bail;
        }
        
        // set the user item drawing procedure
        GetDialogItem(gSndEqDialog, kSndEqUserItemIndex, &myItemKind, &myItemHandle, &myItemRect);                      
        SetDialogItem(gSndEqDialog, kSndEqUserItemIndex, myItemKind, (Handle)gSndEqUserItemProcUPP, &myItemRect);
    }
 
    MacShowWindow(GetDialogWindow(gSndEqDialog));
    gSndEqDialogIsVis = true;
    SndEq_UserItemProcedure(gSndEqDialog, kSndEqUserItemIndex);
 
bail:
    return(myErr);
}
 
 
//////////
//
// SndEq_HideDialog
// Hide the equalizer display dialog box.
//
//////////
 
OSErr SndEq_HideDialog (void)
{
    if (gSndEqDialog != NULL) {
        HideWindow(GetDialogWindow(gSndEqDialog));
        gSndEqDialogIsVis = false;
    }
        
    return(noErr);
}
 
 
//////////
//
// SndEq_ToggleDialog
// Toggle the visibility of the equalizer display dialog box.
//
//////////
 
OSErr SndEq_ToggleDialog (void)
{
    if (gSndEqDialogIsVis)
        SndEq_HideDialog();
    else
        SndEq_ShowDialog();
 
    return(noErr);
}
 
 
//////////
//
// SndEq_UpdateMovieLevels
// Update the equalizer display for a QuickTime movie.
//
//////////
 
OSErr SndEq_UpdateMovieLevels (WindowObject theWindowObject)
{
    ApplicationDataHdl  myAppData = NULL;
    OSErr               myErr = paramErr;
    
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        goto bail;
    
    myErr = MediaGetSoundEqualizerBandLevels((**myAppData).fSoundMediaHandler, gLevels);
    if (myErr != noErr)
        goto bail;
 
    SndEq_UserItemProcedure(gSndEqDialog, kSndEqUserItemIndex);
 
bail:
    return(myErr);
}
 
 
//////////
//
// SndEq_UserItemProcedure
// A user-item procedure to draw the sound levels.
//
//////////
 
PASCAL_RTN void SndEq_UserItemProcedure (DialogPtr theDialog, short theItem)
{
    short                   myItemKind;
    Handle                  myItemHandle = NULL;
    Rect                    myItemRect;
    Rect                    myTempRect;
    GrafPtr                 mySavedPort;
    short                   myWidth;
    short                   myHeight;
    static UInt8            myPrevLevels[kSndEqNumBands];
    static unsigned long    myTicks = 0;
    short                   myCount;
    OSErr                   myErr = noErr;
    
    GetPort(&mySavedPort);
    MacSetPort(GetDialogPort(gSndEqDialog));
    
    if (theItem != kSndEqUserItemIndex)
        goto bail;
 
    // if we updated within the last kSndEqTickThreshold ticks, don't bother to do it now
    if (TickCount() - myTicks < kSndEqTickThreshold)
        goto bail;
        
    myTicks = TickCount();
 
    // get the rectangle surrounding the user item
    GetDialogItem(theDialog, kSndEqUserItemIndex, &myItemKind, &myItemHandle, &myItemRect);
    myWidth = myItemRect.right - myItemRect.left;
    myHeight = myItemRect.bottom - myItemRect.top;
    
    // draw a frame around the picture
    MoveTo(myItemRect.left, myItemRect.top);
    MacLineTo(myItemRect.right, myItemRect.top);
    MoveTo(myItemRect.left, myItemRect.bottom);
    MacLineTo(myItemRect.right, myItemRect.bottom);
    MoveTo(myItemRect.left, myItemRect.bottom);
    MacLineTo(myItemRect.left, myItemRect.top);
    
    for (myCount = 0; myCount < kSndEqNumBands; myCount++) {
        MoveTo(myItemRect.left + ((myCount + 1) * (myWidth / kSndEqNumBands)), myItemRect.bottom);
        MacLineTo(myItemRect.left + ((myCount + 1) * (myWidth / kSndEqNumBands)), myItemRect.top);
    }
    
    for (myCount = 0; myCount < kSndEqNumBands; myCount++) {
        if (myPrevLevels[myCount] >= gLevels[myCount]) {
            MacSetRect(&myTempRect,
                        myItemRect.left + (myCount * (myWidth / kSndEqNumBands)) + 2,
                        myItemRect.top + 1,
                        myItemRect.left + ((myCount + 1) * (myWidth / kSndEqNumBands)) - 1,
                        myItemRect.bottom - ((gLevels[myCount] * myHeight) / 256));
            EraseRect(&myTempRect);
        }
        
        RGBForeColor(&gDGray);  // gMauve
        
        MacSetRect(&myTempRect,
                    myItemRect.left + (myCount * (myWidth / kSndEqNumBands)) + 2,
                    myItemRect.bottom - ((gLevels[myCount] * myHeight) / 256),
                    myItemRect.left + ((myCount + 1) * (myWidth / kSndEqNumBands)) - 1,
                    myItemRect.bottom - 1);
        PaintRect(&myTempRect);
        RGBForeColor(&gBlack);
        
        myPrevLevels[myCount] = gLevels[myCount];
    }
 
bail:
    MacSetPort(mySavedPort);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sound resource file functions.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// SndEq_InitSoundResource
// Do any initialization required for playing sound resource files.
//
//////////
 
OSErr SndEq_InitSoundResource (void)
{
    UnsignedFixed                   myBands[kSndEqNumBands];
    MediaEQSpectrumBandsRecord      myInfo;
    OSErr                           myErr = memFullErr;
 
    gSndEqResourcePlaying = false;
    gSndCallBackProc = NewSndCallBackUPP(SndEq_CallbackProc);
    
    // create storage for a new sound channel   
    gSndEqSndChannelPtr = (SndChannelPtr)NewPtrClear(sizeof(SndChannel));
    if (gSndEqSndChannelPtr == NULL)
        goto bail;
    
    // set the number of commands in the sound channel queue
    gSndEqSndChannelPtr->qLength = kSndEqNumCmdsInQueue;
    
    // create the sound channel
    myErr = SndNewChannel(&gSndEqSndChannelPtr, sampledSynth, initMono, gSndCallBackProc);
    if (myErr != noErr) {
        DisposePtr((Ptr)gSndEqSndChannelPtr);
        gSndEqSndChannelPtr = NULL;
        goto bail;
    }
 
    // set the frequencies for the bands
    myBands[0] = kBandFreq0;
    myBands[1] = kBandFreq1;
    myBands[2] = kBandFreq2;
    myBands[3] = kBandFreq3;
    myBands[4] = kBandFreq4;
    myBands[5] = kBandFreq5;
    myBands[6] = kBandFreq6;
    myBands[7] = kBandFreq7;
    
    myInfo.count = kSndEqNumBands;
    myInfo.frequency = myBands;
    
    // configure the equalizer display
    myErr = SndSetInfo(gSndEqSndChannelPtr, siEQSpectrumOnOff, (void *)true);
    if (myErr != noErr)
        goto bail;
 
    myErr = SndSetInfo(gSndEqSndChannelPtr, siEQSpectrumBands, &myInfo);
 
bail:
    return(myErr);
}
 
 
//////////
//
// SndEq_StopSoundResource
// Do any shut-down required after playing sound resource files.
//
//////////
 
OSErr SndEq_StopSoundResource (void)
{
    if (gSndCallBackProc != NULL)
        DisposeSndCallBackUPP(gSndCallBackProc);
 
    if (gSndEqSndChannelPtr != NULL)
        DisposePtr((Ptr)gSndEqSndChannelPtr);
    
    return(noErr);
}
 
 
//////////
//
// SndEq_OpenSoundFile
// Prompt the user to select a file of type 'sfil' and get the (single) sound resource from it.
//
//////////
 
SndListHandle SndEq_OpenSoundFile (void)
{
    OSType              myTypeList[] = {kQTFileTypeSystemSevenSound};
    FSSpec              myFSSpec;
    OSErr               myErr = noErr;
    SndListHandle       myResource = gSndEqResourceHandle;
    
    // elicit a new sound file from the user
    myErr = QTFrame_GetOneFileWithPreview(1, (QTFrameTypeListPtr)myTypeList, &myFSSpec, NULL);
    if (myErr == noErr) {
        // close any sound file that's currently open
        SndEq_CloseSoundFile();
 
        gSndEqResourceFile = FSpOpenResFile(&myFSSpec, fsRdPerm);
        if (gSndEqResourceFile != -1) {
            myResource = (SndListHandle)Get1IndResource(soundListRsrc, 1);
        }
    }
    
    return(myResource);
}
 
 
//////////
//
// SndEq_CloseSoundFile
// Close the open file of type 'sfil'.
//
//////////
 
void SndEq_CloseSoundFile (void)
{
    if (gSndEqResourceHandle != NULL) {
        ReleaseResource((Handle)gSndEqResourceHandle);
        gSndEqResourceHandle = NULL;
    }
    
    if (gSndEqResourceFile != -1) {
        CloseResFile(gSndEqResourceFile);
        gSndEqResourceFile = -1;
    }
}
 
 
//////////
//
// SndEq_PlaySoundResource
// Play the specified sound resource.
//
//////////
 
void SndEq_PlaySoundResource (SndListHandle theSndResource)
{
    SndCommand          myCommand;
    
    gSndEqResourcePlaying = true;
 
    // play the sound asynchronously
    SndPlay(gSndEqSndChannelPtr, theSndResource, true);
    
    // install a callback command that is executed when the sound is finished playing
    myCommand.cmd = callBackCmd;
    myCommand.param1 = 0;
    myCommand.param2 = 0;
    SndDoCommand(gSndEqSndChannelPtr, &myCommand, false);
}
 
 
//////////
//
// SndEq_UpdateResourceLevels
// Update the equalizer display for a sound resource.
//
//////////
 
OSErr SndEq_UpdateResourceLevels (void)
{
    unsigned long       myTicks;            // for Delay
    OSErr               myErr = noErr;
    
    if (gSndEqResourceDone) {
        // zero out the equalizer display
        gLevels[0] = 0;
        gLevels[1] = 0;
        gLevels[2] = 0;
        gLevels[3] = 0;
        gLevels[4] = 0;
        gLevels[5] = 0;
        gLevels[6] = 0;
        gLevels[7] = 0;
        
        // wait just a bit, to ensure that the display redrawing is triggered
        Delay(kSndEqTickThreshold, &myTicks);
        
        SndEq_UserItemProcedure(gSndEqDialog, kSndEqUserItemIndex);
        
        gSndEqResourceDone = false;
        goto bail;
    }
 
    if (!gSndEqResourcePlaying)
        goto bail;
        
    if (gSndEqSndChannelPtr != NULL) {
        myErr = SndGetInfo(gSndEqSndChannelPtr, siEQSpectrumLevels, gLevels);
        if (myErr != noErr)
            goto bail;
            
        SndEq_UserItemProcedure(gSndEqDialog, kSndEqUserItemIndex);
    }
    
bail:
    return(myErr);
}
 
 
//////////
//
// SndEq_CallbackProc
// Handle callback messages.
//
//////////
 
PASCAL_RTN void SndEq_CallbackProc (SndChannelPtr theChannel, SndCommand *theCommand)
{
#pragma unused(theChannel, theCommand)
    // in this application, a callback message always means that the sound is finished playing,
    // so we simply set our global flag to indicate that fact
    gSndEqResourcePlaying = false;
    gSndEqResourceDone = true;
}