SoundLevel.c

/*
    File:       SoundLevel.c
 
    Contains:   A simple application that echos the sound input level on the microphone using
                a display much like a VCR peak-hold bar meter.
    
                Its a fun app, to use up those wasted background cycles and seems to amuse
                people to no end. At 40k, its not a major memory hog so pop it in your Startup
                Items folder and enjoy.
    
                The About Box introduces a new power user short cut. Clap hard enough to raise
                the sound threshold about Å 95% and the dialog will dismiss. No need to reach
                and strain for that enter key anymore!
    
                Caveats:
                    ¥ Not much error checking.
                    ¥ Too many globals.
                    ¥ Saves the window position back to the app resource fork.
                    ¥ Requires a Sound Input Device (MacRecorder will do just fine)
                    ¥ It keeps the sound input device open with r/w so you wonÕt be able
                        to use other sound input apps unless you quit.
    
                Have fun and let me know what you think.
 
    Written by: Ken Bereskin    
 
    Copyright:  Copyright © 1992-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source 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 source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/2/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
/*  ---------------------------------------------------------------------------------
    ToolBox header files
*/
#include <Types.h>
#include <QuickDraw.h>
#include <Resources.h>
#include <Windows.h>
#include <Fonts.h>
#include <Events.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Menus.h>
#include <Memory.h>
#include <Desk.h>
#include <ToolUtils.h>
#include <OSUtils.h>
#include <Errors.h>
#include <Folders.h>
#include <OSEvents.h>
#include <SegLoad.h>
#include <Traps.h>
#include <SoundInput.h>
#include <GestaltEqu.h>
 
 
/*  ---------------------------------------------------------------------------------
    Forward reference prototypes
*/
void    Initialize(void);
void    SetupEnvironment(void);
void    SetupMemory(void);
void    SetupMenus(void);
void    DoEvents(void);
void    DoMouseDown (EventRecord *event);
void    DoKeyDown (EventRecord *event);
void    DoUpdateWindow (WindowPtr window);
void    DoActivateWindow (Boolean activate, WindowPtr window);
void    DoMenuCommand(short theMenu, short theItem);
void    DoContentClick(EventRecord *event, WindowPtr window);
void    DoSuspend(EventRecord *event);
void    DoResume(EventRecord *event);
void    DoIdle(void);
void    DoDeskAccessory(short whichMenu, short whichItem);
void    NewLevelWindow(void);
void    CleanUp(void);
void    DoAbout(void);
Boolean OptionTest(void);
Boolean CmdTest(void);
Boolean ShiftTest(void);
Boolean ColourQDExists (void);
Boolean ColourQDIsOn (void);
short   ColourDepth (void);
void    TwitchToFinder(void);
void    DrawTheMeter(Rect *meterRect, short numElements, short *peakLevel, short *lastLevel, long *peakTimeout, short meterLevel, short redZone, RGBColor *blueRGB, RGBColor *redRGB, RGBColor *blackRGB, Boolean useColour);
void    GetBarRect(Rect *meterRect, short whichBar, Rect *barRect);
void    InitLevelMeter (void);
void    CloseLevelMeter (void);
OSErr   OpenTheSoundDevice(void);
void    DrawOneBar(Rect *meterRect, short whichBar, Boolean useColour);
pascal  Boolean AboutFilterProc(DialogPtr dialog, EventRecord *event, short *item);
void    HiliteItem(DialogPtr dialog, short theitem);
void    UpdateAboutDialog(WindowPtr window);
void    MakeCursor(short cursorID);
 
/*  ---------------------------------------------------------------------------------
    Constants
*/
#define kMaxVolume              255
#define kPeakTimeoutTicks       60          // number of ticks needed to timeout peak
 
#define kMenuBarID  128
 
#define kAppleMenu      128
#define kAboutItem      1
 
#define kFileMenu       129
#define kQuitItem       1
 
#define kGestaltLevelMeter  'SnLv'
 
/*  ---------------------------------------------------------------------------------
    Global variables
*/
Boolean     WNEIsImplemented;               // is WaitNextEvent avaiable
Boolean     gQuitFlag = false;              // set to force a quit
WindowPtr   gLevelWindow;                   // sound level window pointer
RGBColor    blueRGB, redRGB, blackRGB;      // rgb colours used for drawing meter
short       gNumElements;                   // number of elements in the meter
long        gPeakTimeOut;
short       gPeakLevel;
short       gLastLevel;
short       gRedZone;
Rect        gMeterRect;
long        gSoundInputRefNum;
Boolean     gUseColour;
 
#ifdef powerc
   QDGlobals    qd;
#endif
 
/*  ---------------------------------------------------------------------------------
    Main
    
    Application main entry point
*/
void main()
{
    Initialize();                   // do application initialization
        
    NewLevelWindow();               // create the level window
    InitLevelMeter();               // start up the level meter
 
    DoUpdateWindow(gLevelWindow);
    
    while (!gQuitFlag)              // handle events until quit
        DoEvents();
 
    CleanUp();                      // clean up before quitting
}
 
/*  ---------------------------------------------------------------------------------
    Intialize
    
    First time application initialization
*/
 
#pragma segment Initialize
 
static void
Initialize()
{
    InitGraf(&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(nil);
    InitCursor();
    
    FlushEvents(everyEvent, 0);
        
    SetupMemory();
    SetupEnvironment();
    SetupMenus();
    
    gUseColour = ColourDepth() >= 4;
    
}
 
/*  ---------------------------------------------------------------------------------
    SetupEnvironment
    
    This routine will determine the hardware and software operating environment
*/
 
#define RequestedVersion    1
 
static void
SetupEnvironment()
{
    WNEIsImplemented = true;
}
 
/*
    --- SetupMemory ------------------------------------------------------------------------------------
    This routine will allocate some master pointers, grow the heap,
    and set up a grow zone function.
*/
static void
SetupMemory()
{
    MaxApplZone();
    MoreMasters();
    MoreMasters();
}
 
 
/*
    --- SetupMenus ------------------------------------------------------------------------------------
    This routine will set up the LeadSheet menus.
*/
 
static void
SetupMenus()
{
    Handle menuBarHndl;
    
    // load up the apple and file menu
    menuBarHndl = GetNewMBar(kMenuBarID);       // read menus into menu bar
    SetMenuBar(menuBarHndl);                    // install menus
    DisposeHandle(menuBarHndl);
    
    AppendResMenu(GetMenuHandle(kAppleMenu), 'DRVR');       // add DA names to Apple menu
    
    DrawMenuBar();
}
 
#pragma segment Main
 
/*  ---------------------------------------------------------------------------------
    DoEvents
    
    This is the LaunchPad Main Event Loop. We fetch events from the event queue, 
    and call the proper handlers for the given event class. 
*/
 
#define kSleepTicks 0
 
static void
DoEvents()
{
    Boolean newEvent;               // is there an event in the queue
    EventRecord event;              // the event record 
 
    /*
        Look for an event in the event queue.
    */
    if (WNEIsImplemented) {
        newEvent = WaitNextEvent(everyEvent, &event, kSleepTicks, nil);
    } else {
        SystemTask();
        newEvent = GetNextEvent(everyEvent, &event);
    }
        
    DoIdle();           // idle processing
    
    /*  Dispatch on the type of event */    
    switch(event.what) {
        case mouseDown:
            DoMouseDown(&event);
            break;
        case keyDown:
        case autoKey:
            DoKeyDown(&event);
            break;
        case updateEvt:
            DoUpdateWindow((WindowPtr)event.message);
            break;
        case activateEvt:
            DoActivateWindow(event.modifiers & activeFlag, (WindowPtr)event.message);
            break;
        case diskEvt:
            break;
        case osEvt:
            /*
                Check for suspend or resume events (the only ones now defined)
            */
            if ((event.message >> 24) == suspendResumeMessage) {
                if (event.message & resumeFlag)
                    DoResume(&event);
                else
                    DoSuspend(&event);
            }
            break;
        case highLevelEventMask:
            break;
    }
    
    DoIdle();           // more idle processing
}
 
/*  --- DoMouseDown ----------------------------------------------------------------------------------
    This routine handles mouseDown events.
*/
static void
DoMouseDown(event)
EventRecord *event;
{
    short whichPart;
    WindowPtr window;
    Rect dragBounds;
    long selection;
    short theMenu, theItem;
    
    /*
        Determine where the mouseDown event occurred
    */
    whichPart = FindWindow(event->where, &window);
    
    switch(whichPart) {
        case inMenuBar:
            selection = MenuSelect(event->where);
            theMenu = HiWord(selection);
            theItem = LoWord(selection);
            DoMenuCommand(theMenu, theItem);
            break;
        case inSysWindow:
            SystemClick(event, window);
            break;
        case inContent:
            DoContentClick(event, window);
            break;
        case inDrag:
            dragBounds = qd.screenBits.bounds;
            DragWindow(window, event->where, &dragBounds);
            break;
        case inGrow:
            break;
        case inGoAway:
            break;
        case inZoomIn:
        case inZoomOut:
            break;
        case inDesk:        // a click in the desktop will deselect the current selection
            break;
    }
}
 
 
/*  ---------------------------------------------------------------------------------
    DoKeyDown
    
    This routine handles keyDown events.
*/
static void
DoKeyDown (EventRecord *event)
{
#pragma unused(event)
    gQuitFlag = true;
}
 
/*  ---------------------------------------------------------------------------------
    DoUpdateWindow
 
    This routine handles update events for the given window
*/
 
static void
DoUpdateWindow (window)
WindowPtr window;
{
    GrafPtr savePort;
    Rect boundsRect;
    short i;
    
    GetPort(&savePort);
    
    BeginUpdate(window);        // set up the clipRgn and visRgn
    
    SetPort(window);
    
    RGBForeColor(&blackRGB);
    EraseRect(&window->portRect);
    boundsRect = window->portRect;
    InsetRect(&boundsRect, 2, 2);
    PaintRect(&boundsRect);
    
    // redraw the bar graph frames up to the current level
    for (i = 1; i <= gLastLevel; i++) {
        DrawOneBar(&gMeterRect, i, gUseColour);
    }
    
    EndUpdate(window);          // restore window regions
    
    SetPort(savePort);
}
 
/*  --- DoActivateWindow ----------------------------------------------------------------------------------
    This routine handles activate events for the given window
*/
static void
DoActivateWindow (Boolean activate,WindowPtr window)
{
#pragma unused(activate, window)
}
 
/*  ---------------------------------------------------------------------------------
    DoMenuCommand
    
    This routine handles menu selection dispatching
*/
 
static void
DoMenuCommand(short theMenu, short theItem)
{
    switch (theMenu) {
        case kAppleMenu:
            if (theItem == kAboutItem) {
                DoAbout();
            } else {
                DoDeskAccessory(theMenu, theItem);
            }
            break;
        case kFileMenu:
            if (theItem == kQuitItem) {
                ExitToShell();
            }
            break;
        default:
            break;
    }
    
    HiliteMenu(0);
}
 
/*  --- DoContentClick ------------------------------------------------------------------------------
    This routine handles clicks in the content area of a window. If the option key is down,
    drag the launch window to another location on the screen.
*/
static void
DoContentClick(event, window)
EventRecord *event;
WindowPtr window;
{
#pragma unused(event)
 
    GrafPtr savePort;
    Point clickPt;
    
    /*
        If the clicked window isn't frontmost, simply select it
    */
    if (window != FrontWindow()) {
        SelectWindow(window);
        return;
    }
    
    GetPort(&savePort);                 // save the current port
    SetPort(window);                    // set the port to the clicked window
        
    clickPt = event->where;             // convert point to local coordindates
    GlobalToLocal(&clickPt);
    
    DragWindow(window, event->where, &qd.screenBits.bounds);
        
    SetPort(savePort);                  // restore the saved port
}
 
/*  ---------------------------------------------------------------------------------
    DoSuspend
 
    This routine handles multifinder suspend events.
*/
static void
DoSuspend(event)
EventRecord *event;
{
#pragma unused(event)
    DoActivateWindow(false, gLevelWindow);
}
 
/*  ---------------------------------------------------------------------------------
    DoResume
    
    This routine handles multifinder resume events.
*/
static void
DoResume(event)
EventRecord *event;
{
#pragma unused(event)
    InitCursor();
    DoActivateWindow(true, gLevelWindow);
}
 
/*  ---------------------------------------------------------------------------------
    DoDeskAccessory
    
    Handle desk accessory selections from the apple menu
*/
 
static void
DoDeskAccessory(short whichMenu,short whichItem)
{
    Str255 daName;
    
    GetMenuItemText(GetMenuHandle(whichMenu), whichItem, daName);
        
    (void) OpenDeskAcc(daName);
}
 
/*  ---------------------------------------------------------------------------------
    NewLevelWindow
 
    This routine will create the sound level window.
*/
 
static void
NewLevelWindow()
{
    Point **userOffsetPtHndl;
    Rect screenRect;
    Rect globalRect;
    
    if (ColourQDExists())
        gLevelWindow = GetNewCWindow(128, nil, (WindowPtr)-1);
    else
        gLevelWindow = GetNewWindow(128, nil, (WindowPtr)-1);
        
    SetPort(gLevelWindow);
    
    // load the offset resource that tells us if the user has dragged
    // the window somewhere else. make sure the window remains visible
    // and eventually put this in a pref file where it belongs
    
    userOffsetPtHndl = (Point **)GetResource('WPos', 128);
    if (userOffsetPtHndl != nil) {
        screenRect = qd.screenBits.bounds;
        InsetRect(&screenRect, 5, 5);       // a little bit of a margin
        
        // the port is set for our window, so convert (0,0) to global for the
        // global topLeft of the window
        globalRect.left = (**userOffsetPtHndl).h;
        globalRect.top = (**userOffsetPtHndl).v;
        globalRect.right = globalRect.left + (gLevelWindow->portRect.right - gLevelWindow->portRect.left);
        globalRect.bottom = globalRect.top + (gLevelWindow->portRect.bottom - gLevelWindow->portRect.top);
 
        if (SectRect(&screenRect, &globalRect, &globalRect)) {
            MoveWindow(gLevelWindow, globalRect.left, globalRect.top, true);
        } 
        
        ReleaseResource((Handle)userOffsetPtHndl);
    }
    
    ShowWindow(gLevelWindow);           // make it visible
}
 
/*  ---------------------------------------------------------------------------------
    CleanUp
 
    This routine will clean up before exiting to the Finder
*/
static void
CleanUp()
{
    Point **userOffsetPtHndl;
    Point globalPt;
    
    CloseLevelMeter();              // close up the level meter
 
    // save the position of the top/left corner of the window in case the user
    // has moved it. For now, back to the WPos resource in the app will have to do
    userOffsetPtHndl = (Point **)GetResource('WPos', 128);
    if (userOffsetPtHndl != nil) {
        SetPort(gLevelWindow);
        globalPt.h = globalPt.v = 0;
        LocalToGlobal(&globalPt);
        
        **userOffsetPtHndl = globalPt;
        ChangedResource((Handle)userOffsetPtHndl);
        WriteResource((Handle)userOffsetPtHndl);
    }
    if (gLevelWindow != nil)
        DisposeWindow(gLevelWindow);
}
 
/*  ---------------------------------------------------------------------------------
    MakeCursor
    
    This routine will set the cursor to the 'CURS' resource whose id is given.
*/
void
MakeCursor(short cursorID)
{
#pragma unused(cursorID)
}
 
 
/*  ---------------------------------------------------------------------------------
    DoIdle
    
*/
static void
DoIdle()
{
    OSErr err;
    short recordingStatus = 0;                      // status of recording session
    short meterLevel = 0;                           // current meter level
    unsigned long totalSamplesToRecord = 0;         // total number of samples
    unsigned long numberOfSamplesRecorded = 0;      // number of samples recorded
    unsigned long totalMsecsToRecord;
    unsigned long numberOfMsecsRecorded;
    GrafPtr savePort;
    
    GetPort(&savePort);
    SetPort(gLevelWindow);
    
    /* get the sound input status   */
    err = SPBGetRecordingStatus(gSoundInputRefNum,
                                &recordingStatus,
                                &meterLevel,
                                &totalSamplesToRecord,
                                &numberOfSamplesRecorded,
                                &totalMsecsToRecord,
                                &numberOfMsecsRecorded);
             
    DrawTheMeter(&gMeterRect, 
                   gNumElements, 
                   &gPeakLevel, 
                   &gLastLevel, 
                   &gPeakTimeOut, 
                   meterLevel,
                   gRedZone,
                   &blueRGB,
                   &redRGB,
                   &blackRGB,
                   gUseColour);
                   
    SetPort(savePort);
}
 
/*  ---------------------------------------------------------------------------------
    OptionTest
    
    Returns whether the option key is being pressed
*/
static Boolean
OptionTest()
{
    KeyMap theKeys;
    
    GetKeys(theKeys);
    
    if (theKeys[1] & 4)
        return(true);
    else
        return(false);
 
}
 
/*  ---------------------------------------------------------------------------------
    CmdTest
    
    Returns whether the command (apple) key is being pressed
*/
static Boolean
CmdTest()
{
    KeyMap theKeys;
    
    GetKeys(theKeys);
    
    if (theKeys[1] & 0x8000)
        return(true);
    else
        return(false);
 
}
 
/*  ---------------------------------------------------------------------------------
    ShiftTest
    
    Returns whether the shift key is being pressed
*/
static Boolean
ShiftTest()
{
    KeyMap theKeys;
    
    GetKeys(theKeys);
    
    if (theKeys[1] & 1)
        return(true);
    else
        return(false);
 
}
 
#define ROM85           (*(short *)0x28e)
#define TWOHIGHMASK     0xc000
#define COLOURQDEXISTS  !(ROM85 & TWOHIGHMASK)
 
 
/*  ---------------------------------------------------------------------------------
    ColourQDExists
    
    This routine is called to determine whether or not color quickdraw
    exists in the current roms. It checks the lom memory global just
    like Apple usually doesÉ
*/
Boolean
ColourQDExists()
{
    return(COLOURQDEXISTS);
}
 
/*  ---------------------------------------------------------------------------------
    ColourQDIsOn
    
    This routine is called to determine if colour quickdraw exists
    in the current roms and if the main screen is displaying color.
*/
 
#define QD_MIN_FOR_COLOUR   4
 
Boolean
ColourQDIsOn()
{
    GDHandle maindevice;
    PixMapHandle mainpix;
    short depth;
 
    /*
        First confirm that color is available in the roms
    */
    if (COLOURQDEXISTS) {
        /*
            Determine if the main device is displaying color
        */
        maindevice = GetMainDevice();
        mainpix = (*maindevice)->gdPMap;
        
        /*
            Check the depth of the pixel map
        */
        depth = (*mainpix)->pixelSize;
        
        if (depth < QD_MIN_FOR_COLOUR)
            return(false);
        else
            return(true);
    } else
        return(false);
}
 
short
ColourDepth()
{
    GDHandle maindevice;
    PixMapHandle mainpix;
 
    /*
        First confirm that color is available in the roms
    */
    if (COLOURQDEXISTS) {
        /*
            Determine if the main device is displaying color
        */
        maindevice = GetMainDevice();
        mainpix = (*maindevice)->gdPMap;
        
        /*
            Check the depth of the pixel map
        */
        return (*mainpix)->pixelSize;
    } else
        return 1;
}
 
/*  ---------------------------------------------------------------------------------
    TwitchToFinder
    
    Switch back to Finder. Use the System 7.0 process manager if present
*/
static void
TwitchToFinder()
{
 
}
 
/*  ---------------------------------------------------------------------------------
    DrawTheMeter
    
    This routine is called to draw the volume meter during sound input.
*/
 
void
DrawTheMeter(Rect *meterRect, short numElements, short *peakLevel, short *lastLevel, long *peakTimeout, short meterLevel, short redZone, RGBColor *blueRGB, RGBColor *redRGB, RGBColor *blackRGB, Boolean useColour)
{
    Rect barRect;
    short i;
    
    PenNormal();
    
    // quantize the meterLevel based on number of elements
    // in our bar chart
    if (meterLevel > kMaxVolume)
        meterLevel = kMaxVolume;
        
    meterLevel = (meterLevel * numElements) / kMaxVolume;
    
    // determine if this is a new peak value and erase the
    // peak bar if it has timed out
    if (meterLevel >= *peakLevel) {
        *peakLevel = meterLevel;
        *peakTimeout = TickCount() + kPeakTimeoutTicks; 
    } else {
        // has the current peak timed out?
        if (*peakLevel > 0 && *peakTimeout <= TickCount()) {
            if (*peakLevel >= *lastLevel) {
            
                if (useColour)
                    RGBForeColor(blackRGB);
 
                GetBarRect(meterRect, *peakLevel, &barRect);
                PaintRect(&barRect);
                
                // erase the previous element (peak is two elements wide)
                if (*peakLevel > 1) {           // donÕt erase a non-element
                    GetBarRect(meterRect, *peakLevel - 1, &barRect);
                    PaintRect(&barRect);
                }
            }
            *peakLevel = 0;
        }
    }
        
    // check whether the signal is now stronger than last time
    if (meterLevel > *lastLevel) {
        for (i = *lastLevel + 1; i <= meterLevel; i++) {
            GetBarRect(meterRect, i, &barRect);                 // get the rect for this bar
            
            if (useColour) {                                    // watch out for colour
                if (i >= redZone)                               // are we beyond the clipping point?
                    RGBForeColor(redRGB);                       // draw in red to show distortion
                else
                    RGBForeColor(blueRGB);                      // draw in blue for normal signal
                PaintRect(&barRect);                            // fill the element in
                
            } else
                EraseRect(&barRect);                            // no colour so white will have to do
        }
        *lastLevel = meterLevel;                                // remember for next time
    }
        
    // check whether the signal level is now weaker than last time
    // and if it is remove the rightmost two level bar currently on
    if (meterLevel < *lastLevel) {
        if (*peakLevel != 0) {
            if (*lastLevel == *peakLevel)               // donÕt erase the peak!
                *lastLevel -= 2;
            if (*lastLevel == *peakLevel -1)
                *lastLevel -= 1;
        }
        
        if (useColour)
            RGBForeColor(blackRGB);
 
        for (i = 0; i < 2; i++) {
            if (*lastLevel > 0) {
                GetBarRect(meterRect, *lastLevel, &barRect);
                PaintRect(&barRect);
                *lastLevel -= 1;
            }
            
            if (*lastLevel < 0 )
                *lastLevel = 0;
        }
        
    }
 
    if (useColour)
        RGBForeColor(blackRGB);
 
}
 
/*  ---------------------------------------------------------------------------------
    GetBarRect
*/
void
GetBarRect(Rect *meterRect, short whichBar, Rect *barRect)
{
    if (whichBar == 0) {
        return;
    }
    
    SetRect(barRect, meterRect->left + 2, 
                     meterRect->top + 1, 
                     meterRect->left + 4, 
                     meterRect->bottom - 1);
                     
    OffsetRect(barRect, (whichBar - 1)* 3, 0);
}
 
/*  -------------------------------------------------------------------------------
    InitLevelMeter
*/
 
void
InitLevelMeter()
{
    RGBColor **colorRsrcHndl;
    OSErr err;
    
    // load the colours used for the bar chart
    colorRsrcHndl = (RGBColor **)GetResource(128, 'RGBv');
    if (colorRsrcHndl) {
        blueRGB = **colorRsrcHndl;
        ReleaseResource((Handle)colorRsrcHndl);
    } else {
        blueRGB.red = 39321;        // flourescant blue
        blueRGB.green = 65535;
        blueRGB.blue = 65535;
    }
    
    colorRsrcHndl = (RGBColor **)GetResource(129, 'RGBv');
    if (colorRsrcHndl) {
        redRGB = **colorRsrcHndl;
        ReleaseResource((Handle)colorRsrcHndl);
    } else {
        redRGB.red = 65535;         // peak level red
        redRGB.green = 0;
        redRGB.blue = 0;
    }
    
    blackRGB.red = 0;
    blackRGB.green = 0;
    blackRGB.blue = 0;
    
    gMeterRect = gLevelWindow->portRect;
    InsetRect(&gMeterRect, 2, 2);
    
    gNumElements = (gMeterRect.right - gMeterRect.left - 2) / 3;
    if (gNumElements > 255)
        gNumElements = 255;
 
    gPeakTimeOut = 0;
    gPeakLevel = 0;
    gLastLevel = 0;
    gRedZone = (gNumElements * 2) / 3;
 
    // open the sound input device
    err = OpenTheSoundDevice();
}
 
 
// watch out for other usages of the soundInputRefNum
 
static void
CloseLevelMeter()
{
    OSErr err;
    
    err = SPBCloseDevice(gSoundInputRefNum);
    gSoundInputRefNum = 0;
}
 
#define kAboutDLOG          128
#define kAboutMeterItem     4
 
short   gAboutPeakLevel;
short   gAboutLastLevel;
long    gAboutPeakTimeOut;
 
void
DoAbout()
{
    DialogPtr dialog;
    short itemHit;
    GrafPtr savePort;
    ModalFilterUPP aboutFilterProcUPP;
    
    /* create a UPP for our modal dialog filter */
    aboutFilterProcUPP = NewModalFilterProc(AboutFilterProc);
    
    GetPort(&savePort);
    dialog = GetNewDialog(kAboutDLOG, nil, (WindowPtr)-1);
    SetPort(dialog);
    
    // ¥¥¥ force an update event first
    UpdateAboutDialog((WindowPtr)dialog);
    
    gAboutPeakLevel = 0;
    gAboutLastLevel = 0;
    gAboutPeakTimeOut = 0;
    
    do {
        ModalDialog(aboutFilterProcUPP, &itemHit);
    } while (itemHit != ok);
    
    SetPort(savePort);
    DisposeDialog(dialog);
    
    DisposeRoutineDescriptor(aboutFilterProcUPP);
 
}
 
OSErr OpenTheSoundDevice()
{
    OSErr err;
    short meterState;
    
    // set the default zone to the system heap so that the SI manager will
    // possibly get fooled into allocating there instead of my heap
    
    SetZone(SystemZone());
    err = SPBOpenDevice(nil, siWritePermission, &gSoundInputRefNum);
    if (err != noErr) 
        goto BailOut;
        
    // turn on sound metering
    meterState = 1;             // turn it on
    err = SPBSetDeviceInfo(gSoundInputRefNum, siLevelMeterOnOff, (char *)&meterState);
 
BailOut:
    SetZone(ApplicationZone());
    
    return err;
}
 
void
DrawOneBar(Rect *meterRect, short whichBar, Boolean useColour)
{
    Rect barRect;
    
    GetBarRect(meterRect, whichBar, &barRect);          // get the rect for this bar
    
    if (useColour) {                                    // watch out for colour
        if (whichBar >= gRedZone)                       // are we beyond the clipping point?
            RGBForeColor(&redRGB);                      // draw in red to show distortion
        else
            RGBForeColor(&blueRGB);                     // draw in blue for normal signal
        PaintRect(&barRect);                            // fill the element in
        
    } else
        EraseRect(&barRect);                            // no colour so white will have to do
}
 
/*  ---------------------------------------------------------------------------------
    AboutFilterProc
    
*/
 
#define CRKey           0x0d
#define EnterKey        0x03
#define EscapeKey       0x1b
 
pascal Boolean
AboutFilterProc(DialogPtr dialog, EventRecord *event, short *item)
{
    unsigned char theKey;
    WindowPtr window;
    short itemType;
    Handle itemHndl;
    Rect itemRect;
    OSErr err;
    short recordingStatus = 0;                      // status of recording session
    short meterLevel = 0;                           // current meter level
    unsigned long totalSamplesToRecord = 0;         // total number of samples
    unsigned long numberOfSamplesRecorded = 0;      // number of samples recorded
    unsigned long totalMsecsToRecord;
    unsigned long numberOfMsecsRecorded;
        
    /* get the sound input status   */
    if (event->what != updateEvt) {
        err = SPBGetRecordingStatus(gSoundInputRefNum,
                                    &recordingStatus,
                                    &meterLevel,
                                    &totalSamplesToRecord,
                                    &numberOfSamplesRecorded,
                                    &totalMsecsToRecord,
                                    &numberOfMsecsRecorded);
                 
        GetDialogItem(dialog, kAboutMeterItem, &itemType, &itemHndl, &itemRect);
        InsetRect(&itemRect, 2, 2);
        
        SetPort(dialog);
        
        DrawTheMeter(&itemRect, 
                       gNumElements, 
                       &gAboutPeakLevel, 
                       &gAboutLastLevel, 
                       &gAboutPeakTimeOut, 
                       meterLevel,
                       gRedZone,
                       &blueRGB,
                       &redRGB,
                       &blackRGB,
                       gUseColour);
                       
        // check for nearly maximum volume to dismiss dialog;
        if (meterLevel > 250) {
            HiliteItem(dialog, ok);
            *item = ok;
            return true;
        }
        
    }
    
    switch(event->what) {
        case mouseDown:
            break;
        case keyDown:
        case autoKey:
            theKey = event->message & charCodeMask;
            
            if (theKey == CRKey || theKey == EnterKey || theKey == EscapeKey) {
                HiliteItem(dialog, ok);
                *item = ok;
                return true;
            }
                        
            break;
        case updateEvt:
            window = (WindowPtr)event->message;
            if (window == (WindowPtr)dialog) {
                UpdateAboutDialog(window);
            }
            break;
        default:
            break;
    }
    return false;
}
 
void
UpdateAboutDialog(WindowPtr window)
{
    GrafPtr savePort;
    short itemType;
    Handle itemHndl;
    Rect itemRect;
    
    BeginUpdate(window);
    GetPort(&savePort);
    SetPort(window);
    
    UpdateDialog((DialogPtr)window, window->visRgn);
    
    PenNormal();
    GetDialogItem((DialogPtr)window, kAboutMeterItem, &itemType, &itemHndl, &itemRect);
    FrameRect(&itemRect);
    InsetRect(&itemRect, 2, 2);
    PaintRect(&itemRect);
    
    SetPort(savePort);
    EndUpdate(window);
}
 
/*  ---------------------------------------------------------------------------------
    HiliteItem
    
    This routine will hilite the given item control so the user knows
    which item has been activated by the  key equivalent
*/
 
#define ButtonInvertState   10      // inverted state for push buttons
#define CheckRadioState     11      // inverted state for checks and radios
#define NormalState         0       // non-inverted state
#define SleepDuration       10      // pause for 10 ticks
 
void
HiliteItem(DialogPtr dialog, short theitem)
{
    Handle thehndl;
    short thetype;
    Rect thebox;
    unsigned long junkTicks;
    short hiliteState;
    
    /*
        Get Handle to the item
    */
    GetDialogItem(dialog, theitem, &thetype, &thehndl, &thebox);
    
    switch (thetype) {
        case btnCtrl + ctrlItem:
            hiliteState = ButtonInvertState;
            break;
        case chkCtrl + ctrlItem:
        case radCtrl + ctrlItem:
            hiliteState = CheckRadioState;
            break;
        default:
            return;
            break;
    }
    /*
        Invert the control for a little while
    */
    HiliteControl((ControlHandle)thehndl, hiliteState);
    Delay(SleepDuration, &junkTicks);
    HiliteControl((ControlHandle)thehndl, NormalState);
}