TweetFollow Us on Twitter

Apr 99 Getting Started

Volume Number: 15 (1999)
Issue Number: 4
Column Tag: Getting Started

Sound Recording and Playback

by Dan Parks Sydow

How a Mac program records and plays back user-entered sounds

In the last two Getting Started columns we covered sound-playing basics and asynchronous sound playing (the playing of sound while other action takes place). Getting Started has a wealth of other topics to cover, but before moving on we'll wrap up our study of sound by exploring how a Mac program can easily allow a user to record a sound and then make that sound a part of the program.

Mac users aren't a passive bunch - they want to interact with applications. Giving the user the ability to record something (such as a short clip of music from a CD or the user's own voice) provides the user with a feeling of power. It's also just plain cool! Giving the user the ability to then play back that sound at any time furthers the user's feeling of control. In this article you'll see how a single Toolbox call makes sound recording possible, and how a few dozen lines of code make it possible to save the user's sound to a resource and then play that sound back on demand.

Sound Recording and the User

Interface consistency across applications has been important to the success of the Macintosh. When a user wants to open a file, he knows that choosing the Open item in the File menu results in the appearance of the very familiar standard Open dialog box. The same holds true for sound recording. If the user of a program wants to record a sound, choosing a Record menu item or clicking on a Record button should result in the appearance of the standard Sound Recording dialog box - the dialog box shown in Figure 1.


Figure 1. The standard Sound Recording dialog box.

Whatever sound input device is active serves as the supplier of the sound to the Sound Recording dialog box. Usually that device is a microphone (like the one supplied with current Mac models) that's plugged into the Mac's sound input port - but it can be a different device, such as a CD drive. Using this dialog box is intuitive and straightforward. Clicking the Record button starts sound recording. Clicking the Pause button temporarily halts recording, while clicking the Stop button ends recording. The Play button is used to listen to the just-recorded sound. When it comes time to dismiss the Sound Recording dialog box, a click on the Cancel button does the job - without saving whatever was just recorded. Clicking the Save button also dismisses the dialog box, but not until the last recorded sound is saved (more on that later).

Because adding sound recording to a program is easy, you'll want to play around with the code in this column just to have a little fun. When it comes time to incorporate sound recording into your own program, though, you'll want to give some thought to the matter. Your own application may not have a need for this feature, but your program may find that it's a useful option. Consider recent versions of the word processor Microsoft Word.

Microsoft Word allows a user to add comments to a document. Typically a person who is reviewing a document written by someone else uses this feature. When the original author of the document gets the reviewed copy back, he can choose to view the comments to get the reviewer's input. In Microsoft Word, the author can read what the reviewer wrote or, if the reviewer chose the voice comments option, the author can actually hear what the reviewer said. Figure 2 shows a Word document after a comment that includes both text and voice has been added to it.


Figure 2. An example use of sound recording.

To add a comment, a reviewer highlights a questionable word or phrase in the document, chooses Comment from the Insert menu, and then clicks on the Insert Sound Object button (the button that looks like a little cassette tape) in the Comment pane (the lower area of the document). Upon clicking the Insert Sound Object the standard Sound Recording dialog appears - the same dialog box shown back in Figure 1. (Yes, sometimes Microsoft does take advantage of the Macintosh interface when developing a Macintosh application!) Here the reviewer gets to record a comment - one that can be heard by anyone who eventually opens the document. Recording a comment is done by clicking on the small speaker icon that appears just below whatever text the reviewer typed in the Comment pane (see Figure 2).

Microsoft's use of sound recording of course isn't the only situation when this feature can be added to a program - but it's a great example of how sound recording can be included in an application to enhance the user's experience.

Sound Input Devices

All Macintosh models have had, and have, one or more built-in speakers. That means that your program doesn't have to check for the presence of sound-playing hardware before it plays a sound. The same is not true for sound input devices. While the user's Mac certainly supports some form of sound input, there's no guarantee that a sound input device (such as a microphone) is connected to the computer. So before your program attempts to record a sound, it should first make a check to verify that the host computer does indeed have at least one sound input device. A call to the Toolbox function Gestalt(), with a selector code of gestaltSoundAttr, starts off the test:

OSErr      err;
long       response;
long       mask;

err = Gestalt( gestaltSoundAttr, &response );
if ( err != noErr )
  DoError( "\pError calling Gestalt()" );

If Gestalt() does its work without a hitch it places a value of noErr in err, and we can move on to examine the value returned in the response parameter response:

   mask = 1 << gestaltHasSoundInputDevice;   
   if ( response & mask == 0 )
       DoError( "\pNo sound input source" );

Gestalt() uses the bits of the response parameter to return several pieces of sound-related information. Here we're only interested in whether the system the program is running on has a sound input device. That information is held in one bit in the response parameter - the bit defined by the constant gestaltHasSoundInputDevice. To determine if one particular bit in a variable is set (on), a logical AND operation is necessary. In the previous snippet we're looking to see if the bit number defined by the constant gestaltHasSoundInputDevice is set in the response value returned by Gestalt(). A little bit shifting sets up mask so that it can be used in the test of response. If the logical AND operation results in a non-zero value, then the test passes and we know the user's Mac has support for a sound input device. If the operation results in a value of 0, we call DoError() to post an error message and exit.

Bit shifting is an operation commonly performed by many programmers. But a novice Mac programmer may have encountered it only occasionally - if ever. If you know all about bit shifting, go ahead and skip this section. If the above code snippet has you confused, read on...

A program may declare a flag variable that is used to indicate the state of a single condition. You've seen an example of this - the Boolean variable named gDone is used by our example programs to indicate whether or not a program has finished executing. A program may also declare a variable whose purpose is to contain a number of flags - each bit in the byte or bytes of the variable tell the program whether certain things are true or not. A variable of this type may consist of any number of bits. If such a variable is declared to be of type short, it is eight bits, so it can hold up to eight flags. If the variable is declared to be of type long, it is 32 bits, so it can hold up to 32 flags. Consider if the eight bits of flag look like this:

00100011

Bit numbering is from right to left, with the first bit considered bit number 0. In this example bit 0 has a value of 1, bit 1 has a value of 1, bit 2 has a value of 0, bit 3 has a value of 0, and so forth. Thus if we want to know the value held in, say, bit 5, we'd need to look at the sixth bit from the right. Starting with the rightmost bit and counting from zero we see that bit 5, the sixth bit, has a value of 1. Of course we can tell that by looking at the written values - the program needs to use a mask and the logical AND operation to determine this. By placing a value of 1 in bit 5 (the sixth bit) of a mask variable, and then ANDing that mask with a variable that holds a number of flags, the value of only the sixth bit of the flag is revealed. Here the above flag variable is ANDed with a mask that has just the sixth bit set:

00100011
00100000
00100000

An AND operation looks at the corresponding bits of two operands and returns a value of 1 for that bit position if and only if both bits have a value of 1. In the above example, only bit 5, the sixth bit from the right, has a value of 1 in both operands. That's shown in the result, which has only a single value of 1.

Now let's see how this discussion pertains to the call to Gestalt() in our example. Gestalt() sets the bits of response to reflect the state of various sound-related attributes on the user's machine. We're interested only in one bit - the bit represented by the constant gestaltHasSoundInputDevice. Apple defined this constant to have a value of 5 (I know that from searching the Universal Header files), so to clarify this example let's substitute 5 for gestaltHasSoundInputDevice:

mask = 1 << 5;
if ( response & mask == 0 )
  DoError( "\pNo sound input source" );

The << left shift operator shifts the bits of the value of the left operand (1) to the left by the number of places given by the right operand (5). The vacated positions are filled with zeros. So mask becomes the result of shifting the number 1 (which in binary is 00000001) five places to the left. Thus mask in binary is 00100000. When we AND mask with response, the result is a value of 1 if bit 5 in response is 1, or 0 if bit 5 in response is 0. A value of 1 means that the gestaltHasSoundInputDevice bit in response is set, the user's machine has sound input device capabilities, and our program can carry on. One final note. Both the mask and the response variables are declared to be long integers, so each is a 32-bit value. For simplicity I've only shown the lower eight bits of each of these variables. Keep in mind that each really consists of 32 bits (so, for instance, after the bit shifting mask really looks like this: 00000000000000000000000000100000). Whew!

While your input device checking work is now done, near program start up you may want to remind the user that your program offers sound-recording capabilities, and that the user should have the appropriate sound input source set in the Monitors & Sound control panel of his or her machine. Figure 3 shows that control panel with the External Mic option selected. This is the option appropriate for a Mac that has an external microphone (like the one supplied with recent Mac models) plugged into its sound input jack.


Figure 3. The Monitors & Sound control panel.

Recording A Sound

The Toolbox function SndRecord() is responsible for displaying the standard Sound Recording dialog box. Once this dialog box appears, SndRecord() takes control and is responsible for handling all interaction between the user and the dialog box. Before calling SndRecord(), your program needs to allocate a block of heap memory in which the recorded sound data will be stored. Just how much memory to allocate is a bit tricky - at this point the sound hasn't been recorded, so your program doesn't know how much memory will be needed. If the program allocates too little memory, the user will be limited in the length of the recording that can be made. If the program allocates too much memory, the application may experience memory-related problems later on. The solution is to determine how much free space the application can spare at this point in its execution, and then reserve that amount for use by the SndRecord() function. Making that available memory determination is handled by the Toolbox function PurgeSpace():

long      totalHeap;
long      contigMem;
  
PurgeSpace( &totalHeap, &contigMem );

Contrary to its name, PurgeSpace() doesn't purge, or deallocate, any memory. Instead, a call to this function tells your application how much free space would exist if the heap were to be purged. After a call to PurgeSpace(), the first parameter holds the total free space (in bytes) in the heap if the heap were to be purged. The second parameter holds the size of the largest contiguous block of memory (in bytes) that will exist in the heap if the heap were to be purged. Because the sound data must reside in a contiguous block, it is the value of the second parameter that is of interest to your program. To make sure that your application has access to the entire available application heap, it's important that your program call the Toolbox function MaxApplZone() near the start of the program.

A call to NewHandle() allocates the block of memory needed for the sound data. The number of available contiguous bytes of memory is held in the variable contigMem, so that variable could be used as the parameter to NewHandle() - but it's not. Reserving the entire amount of available contiguous memory would leave the application with little free memory, so some amount of the total available contiguous memory should be subtracted out. In this next snippet, 100 KB are reserved for other, non-sound recording use by the program. The 100 KB is enough for a small example program like the one we'll develop in this column, but your more sophisticated application will want to keep more of its heap free. You can use a debugger or a memory-watching tool such as Metrowerks' ZoneRanger to get an idea of how your application makes use of memory.

#define            kHeapReserve      100 * 1024

long               sndDataMem;
SndListHandle   theSound;

sndDataMem = contigMem - kHeapReserve;
theSound = ( SndListHandle )NewHandle( sndDataMem );

NewHandle() allocates the block of memory and returns a handle to it. SndRecord() requires a SndListHandle rather than a generic handle, so typecasting of the returned handle is in order. After that, the call to SndRecord() can be made:

Point   corner = { 60, 30 }; 
SndRecord( nil, corner, siBestQuality, &theSound );

The first parameter to SndRecord() is a pointer to an optional filter function that's used to handle user actions (such as keystrokes). SndRecord() handles sound recording, pausing, stopping, and playing, so it won't be often that you'll need a filter function. Passing a nil pointer tells SndRecord() no filter function is to be used.

The second parameter specifies the screen placement of the standard Sound Recording dialog box. Pass a Point variable that holds the coordinates of the upper-left corner of the dialog box and SndRecord() takes care of the rest. The above snippet specifies that the upper-left corner of the dialog box appears 60 pixels from the top of the screen and 30 pixels from the left of the screen.

The third parameter specifies a quality level at which to record incoming sound data. Here you use one of three Apple-defined constants (siGoodQuality, siBetterQuality, or siBestQuality). The choice of quality level determines the duration of the sound that can be recorded: the lower the recording quality, the longer the sound that can be recorded. The degree of quality determines the amount of compression that the Sound Manager will use - a lower-quality sound has more compression performed on it. Compression conserves memory, but sacrifices sound quality. As a rule of thumb, use siGoodQuality for voice recording, siBestQuality for sound that demands higher quality (such as music being recorded from a CD player), and siBetterQuality when a compromise between sound quality and storage space is acceptable.

The final parameter to SndRecord() is a handle to the block of memory that is to be devoted to holding the newly recorded sound data. This is the SndListHandle returned by the prior call to NewHandle().

Playing a Recorded Sound

When the user records a sound in the Sound Recording dialog box, that sound can be played back from within that same dialog box (by clicking on the Play button). If the user wants the recorded sound to be preserved after the Sound Recording dialog box has been dismissed, a click on the dialog box Save button does the trick.

When SndRecord() finishes executing, its fourth parameter holds a handle that references the recorded sound data. From January's Getting Started article on sound-basics you know that once a program has a handle to sound data in memory, a call to SndPlay() plays the sound:

      SndPlay( nil, theSound, false );

As a reminder, the first parameter to SndPlay() is a pointer to a sound channel (or nil if the Sound Manager is to take care of the sound channel allocation), the second parameter is the handle to the sound data, and the final parameter indicates whether the sound should be played asynchronously (true) or synchronously (false).

If your program doesn't need to save the user-recorded sound after the program quits, you can keep the sound data in memory and play the sound at any time during the running of the program by making use of the handle in a call to SndPlay(). One way of doing this is to make the sound handle a global variable (in our example we'd declare theSound as a global variable).

Saving A Sound To A Resource

After the user clicks the Save button in the standard Sound Recording dialog box, the recorded sound is held in a block of memory that can be referenced by the SndListHandle returned by the call to SndRecord(). As long as this variable is preserved, your program can play the sound, save it to disk, or both. You've just seen that playing the sound involves a call to SndPlay(). Saving the sound to disk requires a little more (but not too much) work..

To save a sound as a snd resource in the application's resource fork, begin by calling the Toolbox function CurResFile() to get a reference number for the application's resource fork. Here we're making the assumption that your program hasn't explicitly opened a separate resource file and made that file current:

short   resourceFileRef;
resourceFileRef = CurResFile();

Next, call the Toolbox function AddResource() to create a new resource and to add that resource to the resource map in memory. When a program runs all of its resources aren't automatically loaded into memory - many remain on disk. When a program uses a resource it loads a copy of that resource into memory. The resource map serves as a guide to resources on disk.

AddResource( ( Handle )theSound, 'snd ', 
                  9000, "\pUser Sound" );

The first AddResource() parameter is a handle to the data to save, the second parameter is the type of resource to save the data to, the third parameter is the ID to assign to the new resource, and the last parameter is the name to store the resource under.

Next, call the Toolbox function UpdateResFile() to, yes, update the resource file. The call to AddResource() added the new resource to the resource map in memory, but it didn't actually add the new resource to a resource file. UpdateResFile() adds the resource to the resource file specified by the passed reference number. Use the number that was previously obtained from the call to CurResFile():

UpdateResFile( resourceFileRef );

With the new sound resource safely tucked away in the application's resource fork, your program can play the sound at any time - even in subsequent runnings of the program. To do that you'll again use SndPlay(). Before doing that, load the sound resource into memory:

Handle     theHandle;
OSErr      err;
  
theHandle = GetResource( 'snd ', 9000 );
SndPlay( nil, (SndListHandle)theHandle, false );

SoundSaver

This month's program is called SoundSaver. Running SoundSaver results in the appearance of the menu bar shown in Figure 4. The only menu that holds items of real significance is the Sound menu. Figure 4 shows this menu's two items. Choosing the Record Sound item brings on the standard Sound Recording dialog box. After recording a sound (via the Record button) and then saving the sound to a resource (via the Save button), the Play Recorded Sound item in the Sound menu becomes enabled. Choosing that item plays the last sound that was recorded. The Record Sound and Play Recorded Sound items can be selected as often as desired. After testing both items a few times, choose Quit from the File menu to end the program.


Figure 4. The SoundSaver menu.

Creating the SoundSaver Resources

Begin your work on SoundSaver by creating a folder named SoundSaver in your CodeWarrior development folder. Start up ResEdit and create a new resource file named SoundSaver.rsrc, making sure to designate the SoundSaver folder as the resource file's destination. Figure 5 shows the four types of resources that will be in the SoundSaver.rsrc file.


Figure 5. The SoundSaver resources.

If you're a regular reader of Getting Started, then the one ALRT and one DITL resource used by SoundSaver should be familiar to you - they're used in the display of an error-handling alert posted by the DoError() routine that's a part of each column's example program.

Figure 5 shows the four MENU resources the SoundSaver program uses. Note that the second item in MENU 131 is initially marked as disabled. After creating the MENU resources you'll create a single MBAR resource that references the ID of each of the four menus.

That completes the SoundSaver.rsrc file. Now save the file and quit ResEdit - we're ready to create the SoundSaver project.

Creating the SoundSaver Project

Launch CodeWarrior and choose New Project from the File menu to create a new project. Base the new project on the MacOS:C_C++:MacOS Toolbox:MacOS Toolbox Multi-Target project stationary. Uncheck the Create Folder check box before clicking the OK button. Now give the project the name SoundSaver.mcp, and make sure the SoundSaver folder will be the project's destination.

When the new project window appears add the SoundSaver.rsrc file to it, then remove the SillyBalls.rsrc file. The SoundSaver project doesn't use any standard ANSI libraries, so you can optionally remove the ANSI Libraries folder from the project window.

Choose New from the File menu to create a new, empty source code window. Save the new file, giving it the name SoundSaver.c. Choose Add Window from the Project menu to add this empty file to the project. Now remove the SillyBalls.c placeholder file from the project window. At this point you're ready to type in the source code.

If you want to get right down to business, then you can forego the above steps and instead download the entire SoundSaver project from MacTech's ftp site at <ftp://ftp.mactech.com/src/mactech/volume15_1999/15.04.sit>.

Walking Through the Source Code

SoundSaver begins with the usual list of constant definitions, most of which define resource IDs. Exceptions include kHeapReserve, which is used to reserve a block of memory for sound data, and kSndResIDMaxRsrvd, which specifies the largest ID that Apple reserves for use with its own system sound resources.

/********************* constants *********************/

#define kMBARResID            128
#define kALRTResID            128

#define kSleep                7
#define kMoveToFront          (WindowPtr)-1L

#define mApple                128
#define iAbout                1

#define mFile                 129
#define iQuit                 1

#define mSound                131
#define iRecord               1
#define iPlay                 2

#define kHeapReserve         100 * 1024
#define kSndResIDMaxRsrvd   8191

SoundSaver declares four global variables. The Boolean flag gDone tells the program that the user has elected to quit. Another Boolean variable, gUserRecordedSound, tells the program when a sound has been recorded by the user. The MenuHandle variable gSoundMenu is used during the enabling of the Play Recorded Sound menu item (which first occurs after the user records a sound). The long variable gCurrentUserSndID holds the ID of the snd resource after it's added to the program's resource fork.

/****************** global variables *****************/

Boolean            gDone;
Boolean            gUserRecordedSound = false;
MenuHandle         gSoundMenu;
long               gCurrentUserSndID;

Next come the program's function prototypes.

/********************* functions *********************/

void      ToolBoxInit( void );
void      MenuBarInit( void );

void      RecordSoundToMemory( void );
void      SaveSoundFromMemoryToResource( SndListHandle );
void      PlaySoundResource( void );

void      EventLoop( void );
void      DoEvent( EventRecord *eventPtr );
void      HandleMouseDown( EventRecord *eventPtr );
void      EnableDisableMenuItems( void );
void      HandleMenuChoice( long menuChoice );
void      HandleAppleChoice( short item );
void      HandleFileChoice( short item );
void      HandleSoundChoice( short item );
void      DoError( Str255 errorString );

The main() function begins by initializing the Toolbox. After that a call to the Toolbox function MaxApplZone() is made to set the application's heap to its maximum size. Later on in the program when a determination of available free memory is made, this call to MaxApplZone() will be of importance.

void   main( void )
{
   NumVersion      theSndMgrVers;
   OSErr           err;
   long            response;
   long            mask;

   ToolBoxInit();

   MaxApplZone();

As in the previous two Getting Started examples, a check is made to ensure that the user has version 3.0 or later of the Sound Manager. Versions prior to 3.0 don't include some of the functionality that your sound-intensive program might require, so your program may find this an important test.

   theSndMgrVers = SndSoundManagerVersion();  
   if ( theSndMgrVers.majorRev < 3 )
      DoError( "\pSound Manager is outdated" );

A call to Gestalt() with a selector code of gestaltSoundAttr tells the Toolbox to return a variety of sound-related information in the response variable. As described at length earlier in this article, a little bit shifting is performed in order to check the value of the bit that signals whether the user's machine supports a sound input device. After that the program's menu is displayed and the event loop begins.

   err = Gestalt( gestaltSoundAttr, &response );
   if ( err != noErr )
      DoError( "\pError calling Gestalt()" );

   mask = 1 << gestaltHasSoundInputDevice;   
   if ( response & mask == 0 )
      DoError( "\pNo sound input source" );

   MenuBarInit();
   
   EventLoop();
}

ToolBoxInit()is the same as prior versions. MenuBarInit() is essentially the same - there's just one addition. In this program's version of MenuBaRInit() a call to GetMenuHandle() is made to obtain a handle to the Sound menu and to store that handle in the global variable gSoundMenu. Later, when it comes time to enable the Play Recorded Sound menu item from this menu, we'll have a means of accessing the Sound menu.

/******************** ToolBoxInit ********************/

void      ToolBoxInit( void )
{
   InitGraf( &qd.thePort );
   InitFonts();
   InitWindows();
   InitMenus();
   TEInit();
   InitDialogs( nil );
   InitCursor();
}

/******************** MenuBarInit ********************/

void      MenuBarInit( void )
{
   Handle            menuBar;
   MenuHandle      menu;
   
   menuBar = GetNewMBar( kMBARResID );
   SetMenuBar( menuBar );
   gSoundMenu = GetMenuHandle( mSound );
   menu = GetMenuHandle( mApple );
   AppendResMenu( menu, 'DRVR' );
   
   DrawMenuBar();
}

When the user chooses Record Sound from the Sound memory, the application-defined function RecordSoundToMemory() is invoked. This function begins with the code that checks for free memory and reserves an appropriately sized block of that memory.

/**************** RecordSoundToMemory ****************/

void RecordSoundToMemory( void )
{
   OSErr               err;
   SndListHandle   theSound; 
   long                  totalHeap;
   long                  contigMem;
   long                  sndDataMem;
   Point               corner = { 60, 30 }; 
     
   PurgeSpace( &totalHeap, &contigMem);

   sndDataMem = contigMem - kHeapReserve;
   theSound = (SndListHandle)NewHandle( sndDataMem );

Next, SndRecord() is called to display the standard Sound Recording dialog box. After the user is finished with this dialog box, SndRecord() fills the variable err with an error value that the program examines:

   err = SndRecord( nil, corner, siBestQuality, &theSound );
   if ( err != noErr )
   {
      if ( err != userCanceledErr )
         DoError( "\pError recording sound" );
   }
   else   
   {
      SaveSoundFromMemoryToResource( theSound );
   }
}

If err doesn't have a value of noErr (err != noErr), then an error occurred. At this point we normally call DoError() to post an error message and exit the program. We will in fact do this, but not before making one further test of the value of err. If the user clicked the Cancel button in the standard Sound Recording dialog box, SndRecord() returns an err value of userCanceledErr. That's not a "real" error, so in that one instance we won't call DoError(). Any other err value is considered a true error and DoError() is invoked.

If err has a value of noErr, then two things happened: SndRecord() worked flawlessly and the user clicked the Save button in the standard Sound Recording dialog box. In such a case the else section of the if-else statement executes. Here we call the application-defined function SaveSoundFromMemoryToResource().

SaveSoundFromMemoryToResource() does just that - it place the new sound data in a resource. The function begins by obtaining a reference number for the application's resource fork:

/*********** SaveSoundFromMemoryToResource ***********/

void SaveSoundFromMemoryToResource( SndListHandle theSound )
{
   OSErr      err;
   short      resourceFileRef;

   resourceFileRef = CurResFile();

Before adding the sound data to the resource file an appropriate resource ID needs to be determined. We could simply choose an ID and use that in the call to AddResource() (as was done in the AddResource() snippet earlier in this article), but that method has a couple of drawbacks. Using a predetermined value for the sound resource ID has the potential for an ID conflict - your application may already have a snd resource with the same ID. That's not a worry in our very trivial SoundSaver application, but in a much larger program that allows snd resources to be added it could be a concern. The second drawback to using a predefined value for the sound ID is that each time the user records a sound, the new sound overwrites the previous sound. If we come up with a plan for using a value that's guaranteed to be unique each time AddResource() is called, each sound the user records can be saved as a resource. Here's that plan:

   do
   {
      gCurrentUserSndID = UniqueID( 'snd ' );
   } while ( gCurrentUserSndID <= kSndResIDMaxRsrvd );

The Toolbox routine UniqueID() searches all open resource files (including the resource fork of the application) for resources of the type specified in the parameter passed to it. UniqueID() takes note of the IDs of all such resources and returns an ID that is not used. The temptation exists to just call UniqueID() once, but we'll instead place the call in a loop to call it (potentially) a number of times. The reasoning here is that we want to reject some ID values that UniqueID() returns. Specifically, Apple reserves values less than 8192 for the IDs of system sound resources. So any value that UniqueID() returns that is less than 8192 should be rejected, and UniqueID() should be invoked again to try to come up with a value that is both greater than 8191 and that is unique to the snd resources in any open resource file.

With an appropriate ID found, it's time to call AddResource() to add the sound data to the resource map in memory. A call to the Toolbox function ResError() is then made to verify that no resource-related error occurred.

   AddResource( (Handle)theSound, 'snd ', 
                 gCurrentUserSndID, "\pNew Sound" );
   err = ResError();
   if ( err != noErr )
      DoError( "\pError adding sound resource to program" );

Now the memory resource map has been altered, so we need to call UpdateResFile() to make the resource file aware of the change. If this task is performed without error we set the global flag gUserRecordedSound to true so that the program knows that a sound has been successfully recorded. This flag will be used later to enable the initially disabled Play Recorded Sound item in the Sound menu.

   UpdateResFile( resourceFileRef );
   err = ResError();
   if ( err != noErr )
      DoError( "\pError updating program resource file" );
   else
      gUserRecordedSound = true;
}

When the user selects Play Recorded Sound from the Sound menu, the PlaySoundResource() function is invoked. The code in this function should look familiar to you - it's standard resource sound-playing code like that described in the January Getting Started column. PlaySoundResource() begins by loading the newly added sound resource to memory:

/***************** PlaySoundResource *****************/

void PlaySoundResource( void )
{
   Handle      theHandle;
   OSErr      err;
  
   theHandle = GetResource( 'snd ', gCurrentUserSndID );
   if ( theHandle == nil )
      DoError( "\pAttempt to load sound resource failed" );

With the sound data back in memory SndPlay() is called to play the sound. The playing of a sound can be a lengthy task, so we'll lock the handle that references the data to ensure that the system doesn't move the data during its periodic compacting of memory. After verifying that no error occurred, the memory that holds the sound data can be marked as available to the program by calling ReleaseResource():

   HLock( theHandle );
      err = SndPlay( nil, (SndListHandle)theHandle, false );
   HUnlock( theHandle );

   if ( err != noErr )
      DoError( "\pPlaying of sound failed" );
  
   ReleaseResource( theHandle );
}

The remainder of the SoundSaver code is the event-handling code included in all of our Getting Started examples. Much of the code is similar to, or exactly the same as, code found in previous examples. But you'll still want to carefully read the text preceding a function description to pick up on the purpose of the few new lines of code.

/********************** EventLoop ********************/

void      EventLoop( void )
{      
   EventRecord      event;
   
   gDone = false;
   while ( gDone == false )
   {
      if ( WaitNextEvent( everyEvent, &event, kSleep, nil ) )
         DoEvent( &event );
   }
}

/*********************** DoEvent *********************/

void      DoEvent( EventRecord *eventPtr )
{
   char   theChar;
   
   switch ( eventPtr->what )
   {
      case mouseDown: 
         HandleMouseDown( eventPtr );
         break;
      case keyDown:
      case autoKey:
         theChar = eventPtr->message & charCodeMask;
         if ( (eventPtr->modifiers & cmdKey) != 0 ) 
            HandleMenuChoice( MenuKey( theChar ) );
         break;
      case updateEvt:
         BeginUpdate( (WindowPtr)(eventPtr->message) );
         EndUpdate( (WindowPtr)(eventPtr->message) );
         break;
   }
}

A click of the mouse button results in DoEvent() calling HandleMouseDown(). The SoundSaver version of HandleMouseDown() includes one new line of code - a call to the application-defined function EnableDisableMenuItems(). This routine is called when the mouse button click occurs in the menu bar. This function (explained next) is responsible for determining which menu items need to be enabled or disabled, and for then carrying out the appropriate enabling or disabling. The reasoning for calling this function from HandleMouseDown() is quite simple. The only time a user sees a menu item is when he clicks the mouse in the menu bar - so that's the only time your program needs to worry about each menu item being in its proper state.

/******************* HandleMouseDown *****************/

void      HandleMouseDown( EventRecord *eventPtr )
{
   WindowPtr   window;
   short         thePart;
   long            menuChoice;
   
   thePart = FindWindow( eventPtr->where, &window );
   
   switch ( thePart )
   {
      case inMenuBar:
         EnableDisableMenuItems();
         menuChoice = MenuSelect( eventPtr->where );
         HandleMenuChoice( menuChoice );
         break;
      case inSysWindow : 
         SystemClick( eventPtr, window );
         break;
   }
}

A click in the menu bar results in EnableDisableMenuItems() being invoked. In the SoundSaver program our only concern is the state of the Play Recorded Sound item, but your program may need to keep track of the state of several menu items in several menus. For ease of changing, and correcting, menu item state code, place all such code in a single function. Devote a global MenuHandle variable (such as gSoundMenu) to each menu that holds items that will have state changes and a global Boolean flag variable (such as gUserRecordedSound) to each menu item that can have its state toggled. For our particular example recall that gUserRecordedSound is initialized to false (so that the Play Recorded Sound menu item remains disabled until a sound is in fact recorded). This flag variable is set to true in SaveSoundFromMemoryToResource() after a sound has successfully been recorded and saved.

/**************** EnableDisableMenuItems *************/

void      EnableDisableMenuItems( void )
{
   if ( gUserRecordedSound == true )
      EnableItem( gSoundMenu, iPlay );
   else
      DisableItem( gSoundMenu, iPlay );
}

/******************* HandleMenuChoice ****************/

void      HandleMenuChoice( long menuChoice )
{
   short   menu;
   short   item;
   
   if ( menuChoice != 0 )
   {
      menu = HiWord( menuChoice );
      item = LoWord( menuChoice );
      
      switch ( menu )
      {
         case mApple:
            HandleAppleChoice( item );
            break;
         case mFile:
            HandleFileChoice( item );
            break;
         case mSound:
            HandleSoundChoice( item );
            break;
      }
      HiliteMenu( 0 );
   }
}

/****************** HandleAppleChoice ****************/

void      HandleAppleChoice( short item )
{
   MenuHandle      appleMenu;
   Str255            accName;
   short            accNumber;
   
   switch ( item )
   {
      case iAbout:
         SysBeep( 10 );
         break;
      default:
         appleMenu = GetMenuHandle( mApple );
         GetMenuItemText( appleMenu, item, accName );
         accNumber = OpenDeskAcc( accName );
         break;
   }
}

/******************* HandleFileChoice ****************/

void      HandleFileChoice( short item )
{
   switch ( item )
   {
      case iQuit:
         gDone = true;
         break;
   }
}

When the user chooses one of the two items from the Sound menu, HandleSoundChoice() gets called. Choosing Record Sound results in RecordSoundToMemory() being called, while choosing Play Recorded Sound results in a the invocation of PlaySoundResource().

/****************** HandleSoundChoice ****************/

void      HandleSoundChoice( short item )
{
   switch ( item )
   {
      case iRecord:
         RecordSoundToMemory();
         break;
      case iPlay:
         PlaySoundResource();
         break;
   }
}

/*********************** DoError *********************/

void      DoError( Str255 errorString )
{
   ParamText( errorString, "\p", "\p", "\p" );
   
   StopAlert( kALRTResID, nil );
   
   ExitToShell();
}

Running SoundSaver

Run SoundSaver by selecting Run from CodeWarrior's Project menu. After the code compiles, a menu appears. Verify that the Play Recorded Sound item in the Sound menu is disabled (because you haven't yet recorded a sound to play), then choose Record Sound from the Sound menu. Doing that brings up the standard Sound Recording dialog box. Record a sound, then click the Save button. With a sound recorded and the dialog box now dismissed, the Play Recorded Sound item should be enabled. Choose it to hear the sound. You can repeat this process as often as you want before quitting.

To verify that the Sound Recording dialog box can record from whatever sound input device is current, open the Monitors & Sound control panel. Click on the Sound button and then select CD from the Sound Monitoring Source pop-up menu (this is assuming your Mac has a CD-ROM drive). Close the control panel and place an audio CD in your Mac's CD-ROM drive. With the CD playing, run the SoundSaver program and select Record Sound from the Sound menu. Click the Record button and whatever sound is playing on the audio CD will be recorded. Click the Stop button, then click Save to save the sound as a resource. You can play back the audio clip by choosing Play Recorded Sound from the Sound menu.

After you quit the SoundSaver program, launch ResEdit and open the SoundSaver program. Make sure to open the program itself, not the SoundSaver.rsrc resource file. Here you'll see that the SoundSaver program contains one or more snd resources - resources that didn't exist before you ran the program. Figure 6 shows the SoundSaver application opened in ResEdit.


Figure 6. The resources in the SoundSaver program.

Till Next Month...

Years ago, a user didn't expect a computer program to include sound - other than perhaps an annoying beep when he did something wrong! Now, a computer program without sound is a bit like one without graphics. That's why we've devoted the last three columns to the art of adding sound to your own Mac programs. To learn still more about sound files, sound playing, and sound recording, thumb through the Sound volume of Inside Macintosh. Then go ahead and experiment with the SoundSaver code. One improvement you may want to make is to include a scheme for saving the resource number of a recorded sound so that on subsequent runnings of SoundSaver the sound can be retrieved. If you do that you'll also want to change the way the global flag gUserRecordedSound works. As it stands, from one execution to another the program doesn't remember that a sound was recorded.

If your program would be better served by saving a recorded sound to a sound file, try substituting the call to SndRecord() with a call to its companion Toolbox function SndRecordToFile(). SndRecordToFile() saves a recorded sound to an AIFF or AIFF-C format sound file. The playing back of a sound file was described in detail two issues ago - in the January Getting Started column. You can also read about sound file playing in Inside Macintosh: Sound.

By now you should have the topic of sound mastered. Which means it's time to move on to something completely different. And that's just what we'll do next month...

 
AAPL
$96.93
Apple Inc.
-0.26
MSFT
$44.54
Microsoft Corpora
-0.33
GOOG
$595.42
Google Inc.
-0.56

MacTech Search:
Community Search:

Software Updates via MacUpdate

Airfoil 4.8.7 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
Microsoft Remote Desktop 8.0.8 - Connect...
With Microsoft Remote Desktop, you can connect to a remote PC and your work resources from almost anywhere. Experience the power of Windows with RemoteFX in a Remote Desktop client designed to help... Read more
xACT 2.30 - Audio compression toolkit. (...
xACT stands for X Aaudio Compression Toolkit, an application that encodes and decodes FLAC, SHN, Monkey’s Audio, TTA, Wavpack, and Apple Lossless files. It also can encode these formats to MP3, AAC... Read more
Firefox 31.0 - Fast, safe Web browser. (...
Firefox for Mac offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals... Read more
Little Snitch 3.3.3 - Alerts you to outg...
Little Snitch gives you control over your private outgoing data. Track background activityAs soon as your computer connects to the Internet, applications often have permission to send any... Read more
Thunderbird 31.0 - Email client from Moz...
As of July 2012, Thunderbird has transitioned to a new governance model, with new features being developed by the broader free software and open source community, and security fixes and improvements... Read more
Together 3.2 - Store and organize all of...
Together helps you organize your Mac, giving you the ability to store, edit and preview your files in a single clean, uncluttered interface. Smart storage. With simple drag-and-drop functionality,... Read more
Cyberduck 4.5 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
iExplorer 3.4 - View and transfer all th...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
Airmail 1.4 - Powerful, minimal email cl...
Airmail is a powerful, minimal mail client.It was designed to retain the same experience with a single or multiple accounts and provide a quick, modern and easy-to-use user experience. Airmail... Read more

Latest Forum Discussions

See All

More Paintings Have Been Added to Paint...
More Paintings Have Been Added to Paint it Back! Posted by Jessica Fisher on July 24th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
The Order of Souls Review
The Order of Souls Review By Campbell Bird on July 24th, 2014 Our Rating: :: STORY GRINDUniversal App - Designed for iPhone and iPad The Order of Souls is a free-to-play, turn-based RPG with a genre-mixing art style, interesting... | Read more »
Revolution 60 Review
Revolution 60 Review By Jordan Minor on July 24th, 2014 Our Rating: :: LASS EFFECTUniversal App - Designed for iPhone and iPad Revolution 60 is a bold, cinematic action game with ambition to spare.   | Read more »
Matter (Photography)
Matter 1.0.1 Device: iOS Universal Category: Photography Price: $1.99, Version: 1.0.1 (iTunes) Description: Add stunning 3D effects to your photos with real-time shadows and reflections. Export your creations as photos or video loops... | Read more »
Fanatic Earth Review
Fanatic Earth Review By Brittany Vincent on July 24th, 2014 Our Rating: :: BY-THE-NUMBERSUniversal App - Designed for iPhone and iPad Kemco’s stable of mobile RPGs grows, but in Fanatic Earth’s situation it’s a case of quantity... | Read more »
Together for iOS (Productivity)
Together for iOS 1.0 Device: iOS Universal Category: Productivity Price: $9.99, Version: 1.0 (iTunes) Description: Together is an app for keeping things in one place. Notes, documents, images, movies, sounds, web pages and bookmarks... | Read more »
The Phantom PI Mission Apparition (Game...
The Phantom PI Mission Apparition 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** Release sale! 50% off for a limited time! ** The Phantom PI Mission Apparition is a spooky, puzzly, rock’... | Read more »
The Great Prank War (Games)
The Great Prank War 1.0.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.0 (iTunes) Description: Help Mordecai, Rigby, Muscle Man and Skips take the park back from Gene and his goons with a plethora of prank-related... | Read more »
Teenage Mutant Ninja Turtles (Games)
Teenage Mutant Ninja Turtles 1.0.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0.0 (iTunes) Description: Download the all new Teenage Mutant Ninja Turtles Official Movie Game! | Read more »
Dream Revenant (Games)
Dream Revenant 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: EXCLUSIVE LAUNCH PRICE ! Dream Revenant is at $1.99 for a limited time ! | Read more »

Price Scanner via MacPrices.net

What Should Apple’s Next MacBook Priority Be;...
Stabley Times’ Phil Moore says that after expanding its iMac lineup with a new low end model, Apple’s next Mac hardware decision will be how it wants to approach expanding its MacBook lineup as well... Read more
ArtRage For iPhone Painting App Free During C...
ArtRage for iPhone is currently being offered for free (regularly $1.99) during Comic-Con San Diego #SDCC, July 24-27, in celebration of the upcoming ArtRage 4.5 and other 64-bit versions of the... Read more
With The Apple/IBM Alliance, Is The iPad Now...
Almost since the iPad was rolled out in 2010, and especially after Apple made a 128 GB storage configuration available in 2012, there’s been debate over whether the iPad is a serious tool for... Read more
MacBook Airs on sale starting at $799, free s...
B&H Photo has the new 2014 MacBook Airs on sale for up to $100 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels... Read more
Apple 27″ Thunderbolt Display (refurbished) a...
The Apple Store has Apple Certified Refurbished 27″ Thunderbolt Displays available for $799 including free shipping. That’s $200 off the cost of new models. Read more
WaterField Designs Unveils Cycling Ride Pouch...
High end computer case and bag maker WaterField Designs of San Francisco now enters the cycling market with the introduction of the Cycling Ride Pouch – an upscale toolkit with a scratch-free iPhone... Read more
Kingston Digital Ships Large Capacity Near 1T...
Kingston Digital, Inc., the Flash memory affiliate of Kingston Technology Company, Inc.,has announced its latest addition to the SSDNow V300 series, the V310. The Kingston SSDNow V310 solid-state... Read more
Apple’s Fiscal Third Quarter Results; Record...
Apple has announced financial results for its fiscal 2014 third quarter ended June 28, 2014, racking up quarterly revenue of $37.4 billion and quarterly net profit of $7.7 billion, or $1.28 per... Read more
15-inch 2.0GHz MacBook Pro Retina on sale for...
B&H Photo has the 15″ 2.0GHz Retina MacBook Pro on sale for $1829 including free shipping plus NY sales tax only. Their price is $170 off MSRP. B&H will also include free copies of Parallels... Read more
Apple restocks refurbished Mac minis for up t...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more

Jobs Board

Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
Senior Interaction Designer, *Apple* Online...
**Job Summary** Apple is looking for a hands on Senior…will be a key player in designing for the Apple Online Store. The ideal designer will have a Read more
*Apple* Sales Chat Rep - Apple (United State...
…is looking for motivated, outgoing, and tech savvy individuals who want to offer Apple Customers an unparalleled customer experience over chat. At Apple , we believe Read more
Mac Expert - *Apple* Online Store Mexico -...
…MUST be fluent in English and Spanish to be considered for this position At Apple , we believe that hard work, a fun environment, creativity and innovation fuel the Read more
*Apple* Industrial Design CAD Sculptor - App...
**Job Summary** The Apple Industrial Design team is looking for a CAD sculptor/Digital 3D modeler to create high quality CAD models used in the industrial design process Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.