TweetFollow Us on Twitter

Mar 99 Getting Started

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

Asynchronous Sound: Action and Sound

By Dan Parks Sydow

How a Mac program plays sounds that coincide with animated effects

In last month's Getting Started we covered sound-playing basics. The information in that article provided a good framework for including sound in your Mac applications. However, one key use of sound was omitted from that article - the playing of sounds asynchronously. Asynchronous sound playing is the playing of a sound in such a way that other action can take place for the duration of the sound. Rather than have your program - and the user - wait while a sound plays, you'll want other events to take place. Most typically asynchronous sound playing coincides with animation. As some animated effect goes on in a window, an accompanying sound plays. This month we'll tackle asynchronous sound playing so that you can turn your programming efforts into a really polished multimedia Mac application.

Sound Channels

When a program is to play sound data that's loaded into memory, the program must create a corresponding sound channel that holds a queue of sound-playing commands for that data. A program that calls SndPlay() and passes a nil pointer as the first parameter is telling the Sound Manager to take care of the allocation of the sound channel. That's exactly what we did in last month's Getting Started column for the SoundPlayer program:

SndPlay( nil, (SndListHandle)theHandle, false );

If your program plays a sound synchronously, as SoundPlayer did, then letting the Sound Manager take care of the task of creating the sound channel makes sense. No action can take place in a program during synchronous sound playing, so there's no need to access the sound channel. If your program is to instead play a sound asynchronously, then your program will want to keep tabs on the sound channel. In particular, your program will want to insert a special command into the sound channel after it starts playing a sound. This command will be used by the sound channel to notify your program when sound playing ends. As long as the sound is still playing your program knows it can carry on with some other action - typically another step, or frame, in an animated sequence that is choreographed with the sound.

To allocate a sound channel, use the Toolbox function SndNewChannel(). A call to this function creates a new sound channel record and returns to your program a pointer to that record:

SndChannelPtr   theChannel;

SndNewChannel( &theChannel, 0, 0, nil );

The first parameter to SndNewChannel() is a pointer to a SndChannelPtr. When the routine completes, the Toolbox will have filled in this first parameter with a pointer to a new sound channel record. The second parameter is a constant that indicates the type of sound data that is to be played on the new channel. Passing a value of 0 means the channel can be used for any type of sound. The third parameter can be used to supply channel initialization information. Again, simply pass a value of 0 if you're uncertain of the exact type of sound that is to be played from the channel. For synchronous sound playing, the last parameter can be nil. For asynchronous sound playing, the last parameter is a pointer to a callback routine. As you'll see ahead, a callback routine is an application-defined function that the system executes when a sound has finished playing on this channel. It's important to take note of that last point: even though you'll write the code for the callback routine and include it in your source code listing with your other application-defined routines, it will be the Sound Manager that invokes function.

Sound Commands

When you participate in the allocation of your program's own sound channel (rather than allowing the Sound Manager to do all the work), you gain the power to send sound commands of your choice to the sound channel. A sound channel includes a queue of sound commands. Each sound command can affect the way in which a sound is played. Each sound command has a type and one or two command options. Consider the amplitude sound command, which affects the amplitude (volume) of a sound. For this command the type is ampCmd, the first option holds the amplitude (a short value in the range of 0 to 255), and the second option is ignored. To define a sound command that can be used to set the amplitude of a sound to its maximum, use this code:

SndCommand      theCommand;

theCommand.cmd = ampCmd;
theCommand.param1 = 255;
theCommand.param2 = 0;

The above code sets up a sound command, but it doesn't affect any particular sound channel. To place the command in an existing sound channel, call the Toolbox function SndDoCommand():

SndDoCommand( theChannel, &theCommand, false );

The first parameter to SndDoCommand() is a sound channel pointer. In the above call the sound channel pointer theChannel is the one created earlier in this article with the call to SndNewChannel(). The second parameter is a pointer to a command. Before calling SndDoCommand() you'll want to have created and filled in this command, as shown for the ampCmd in the previous snippet. The last parameter tells the Sound Manager what to do if the specified sound channel's command queue is full. A value of true means an error is to be returned, while a value of false means the Sound Manager is to wait for a free position in the queue.

A call to SndDoCommand() places a command in a sound channel's command queue. Once a sound is played on that sound channel, the command goes into effect. For instance, if the above ampCmd was placed in theChannel, then any subsequent sounds played on that channel would be played at full volume.

Asynchronous Sound and the Callback Routine

So far we've discussed the sound channel and the sound commands that can be placed in the channel - but we haven't seen how those topics tie in with asynchronous sound playing. We're just about there. Before getting into the details of implementing asynchronous sound, let's step back to get an overview of the sound channel, sound commands, asynchronous sound, and something referred to as a callback routine.

To play a sound resource asynchronously, you'll first allocate a new sound channel using SndNewChannel(). In the call to SndNewChannel() you'll specify the name of a callback routine. This callback routine is a function that is to execute when this newly created sound channel finishes playing a sound. Next, you'll load the sound to memory and call SndPlay() to play it. After the sound begins playing, you'll call SndDoCommand() to add a callback command to the command queue of the sound channel that's playing the sound. The timing of the addition of this command is important. Because the sound has started playing, the newly added callback command will be placed at the end of the sound channel's command queue. Doing this means that the sound channel executes this command when the sound has completed.

A callback routine is a simple function that exists to tell your program that a sound has completed playing. When your program gets this information it knows that any action taking place that was to be timed to the playing of the sound should now stop.

Before looking at the code that makes all this asynchronous business possible, let's complete the overview with a list of steps necessary to make sound and animation happen together:

  1. Create a sound channel, pairing it with a callback routine.
  2. Begin sound playing on the new sound channel.
  3. Add a callback command to the sound channel that is playing the sound.
  4. Enter a loop, with each pass through the loop performing a step in an animation.

The above steps seem straightforward enough, but the last step may puzzle you a bit. Performing animation using a loop is simple enough - you can just move a picture a pixel or two at each pass through the loop. But as the loop executes, how does the loop know when the corresponding sound has ended, and that animation should cease? That's the job of the callback routine and the system. When the sound ends, the system executes the callback routine - even if your application is in the middle of an animation loop. Your callback routine will be written such that it sets a global flag indicating that sound playing has ended. It will be this flag that the animation loop looks at and relies on in order to know whether to continue or to stop animation.

AsynchPlayer

This month's program is called AsynchPlayer. If you read last month's column, then much of the code will look familiar to you - though as you'll see ahead we'll be adding a few twists. Running AsynchPlayer results in the appearance of a menu bar and a window. Of significance in the menu bar is the Sound menu. This menu has a single item - Play and Move. Choosing this item causes the program to play a sound and move a picture. The sound is the distinctive noise of a helicopter flying, and the picture is that of a helicopter. When the Play and Move item is selected the helicopter appears and moves from right to left as the sound plays. When the sound stops, so does the animation. Figure 1 shows the helicopter as it crosses the window. After running the animation a few times, choose Quit from the File menu to end the program.


Figure 1. The AsynchPlayer window.

Creating the AsynchPlayer Resources

Start by opening your CodeWarrior development folder and creating a folder named AsynchPlayer. Launch ResEdit and create a new resource file named AsynchPlayer.rsrc inside the AsynchPlayer folder. Figure 2 shows the seven types of resources used by AsynchPlayer. If you've been keeping up with Getting Started articles, each type should be familiar to you.


Figure 2. The AsynchPlayer resources.

AsynchPlayer includes a single snd resource - as shown in Figure 2. This sound resource is a digitized sound obtained from an external source - it can't be created from within ResEdit. This month's project is available for download from MacTech's ftp site at ftp://ftp.mactech.com/src/mactech/volume15_1999/15.03.sit, and it includes a single system sound file named Helicopter. Use ResEdit to open that file, copy its one snd resource, and then paste that resource into the AsynchPlayer.rsrc file. From last month's column you know that ResEdit won't display anything useful about a sound resource (since it's difficult to graphically display a sound), so there's no need to double-click on the snd resource to peek at what's inside.

AsynchPlayer needs one picture resource in order to carry out the animation. If you're artistically inclined, or if you have a large clip art collection, you'll be able to come up with a helicopter picture. Otherwise use the Helicopter.PICT file included in project that can be downloaded from MacTech's ftp site at ftp://ftp.mactech.com/src/mactech/volume15_1999/15.03.sit. In any case, get the picture to the clipboard and paste it into the resource file. As shown in Figure 3, the resulting PICT resource should have an ID of 128.


Figure 3. The picture resource used in animation.

The one WIND resource is used to create the window that displays the moving helicopter. This window won't be movable, so you don't have to be too selective about what type of window you create. The program won't care where the window gets positioned on the screen, but it will be expecting the window to be about 500 pixels in width and 150 pixels in height - so do use those values. Make sure the WIND has an ID of 128.

The one ALRT and one DITL resource used by AsynchPlayer are the same ones used in the last several Getting Started examples. These resources are used in the display of an error-handling alert displayed by the program's DoError() routine.

Figure 4 shows the four MENU resources the AsynchPlayer program uses. After creating the MENU resources, create a single MBAR resource that includes the ID of each of the four menus.


Figure 4. The AsynchPlayer menu resources.

That completes the AsynchPlayer.rsrc file. Now save the file and quit ResEdit - we're ready to write some code.

Creating the AsynchPlayer Project

Create a new project by launching CodeWarrior and then choosing New Project from the File menu. Base the file on the MacOS:C_C++:MacOS Toolbox:MacOS Toolbox Multi-Target stationary. Uncheck the Create Folder check box before clicking the OK button. Give the project a name of AsynchPlayer.mcp, and make sure the AsynchPlayer folder is set as the project's destination.

Next, add the AsynchPlayer.rsrc file to the project window and remove the SillyBalls.rsrc file. Since the AsynchPlayer project doesn't use of any of the standard ANSI libraries, go ahead and remove the ANSI Libraries folder if you feel so inclined.

Now choose New from the File menu to create a new, empty source code window. Save it with the name AsynchPlayer.c. Choose Add Window from the Project menu to add this empty file to the project. Remove the SillyBalls.c placeholder file from the project window. Now get ready to type some source code. Or, save some work and download the entire AsynchPlayer project from MacTech's ftp site at ftp://ftp.mactech.com/src/mactech/volume15_1999/15.03.sit.

Walking Through the Source Code

Now, in its entirety - the AsynchPlayer code. AsynchPlayer begins with the usual parade of constant definitions. Many of the constants define resource IDs, including ksnd_ResID for the sound resource and kPICTResID for the picture.

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

#define kWINDResID            128
#define kMBARResID            128
#define kALRTResID            128
#define ksnd_ResID            12000
#define   kPICTResID          128

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

#define mApple                128
#define iAbout                1

#define mFile                 129
#define iQuit                 1

#define mSound                131
#define iMovePlay             1

AsynchPlayer needs several global variables, including the always-present gDone flag that tells the program that the user has elected to quit. The Boolean variable gCallbackExecuted tells the program whether a call to SndPlay() has just completed (true), or is still executing (false). The variable gSoundChannel is a sound channel pointer that we'll use to associate a callback routine with the execution of a call to SndPlay(). Variable gSoundHandle serves as a reference to the sound resource data that gets loaded to memory. The PicHandle variable gHelicopterPicture serves as a reference to the picture resource data that gets loaded to memory. Variable gPictRect is the bounding rectangle of the helicopter picture. The last four variables (gPictStartX, gPictStartY, gPictWidth, and gPictHeight) define the starting coordinates for the helicopter picture. Each time the user chooses Play and Move from the Sound menu, the picture begins its animation at these coordinates.

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

Boolean            gDone;
Boolean            gCallbackExecuted = false;
SndChannelPtr      gSoundChannel;
Handle             gSoundHandle = nil;
PicHandle          gHelicopterPicture;
Rect               gPictRect;
short              gPictStartX = 500;
short              gPictStartY = 35;
short              gPictWidth = 256;
short              gPictHeight = 82;

Next come the program's function prototypes.

/********************* functions *********************/
void               ToolBoxInit( void );
void               MenuBarInit( void );
void               InitWindow( void );
pascal void   SndChannelCallback( SndChannelPtr, SndCommand);
void               PlaySoundResource( void );
void               AnimateWhileSoundPlays( void );
void               EventLoop( void );
void               DoEvent( EventRecord *eventPtr );
void               HandleMouseDown( EventRecord *eventPtr );
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. Just as we did last month, a check is then made to ensure that the user has version 3.0 or later of the Sound Manager. That version of the Sound Manager holds a lot of sound-related goodies your program may want to take advantage of, so if the user doesn't have 3.0 or later, the application-defined routine DoError() posts a message and exits the program.

/************************ main ***********************/

void      main( void )
{
   NumVersion      theSndMgrVers;
   long               theResponse;

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

Before carrying on with the rest of the initializations, main() makes a call to the Toolbox function Gestalt() to see if the user's machine is equipped with a PowerPC processor. While it's possible to achieve asynchronous sound on a 68K-based Mac, the details of achieving that feat are out of the scope of this article. Apple no longer produces 68K-based Macs, owners of older Macs are upgrading to Power Macs, and a great deal of new Mac software relies on very fast processing power. With that in mind, expect to see support for 68K-based Macs waning in the future. If you need to support 68K-based machines for your own asynchronous sound-playing application, read up on the topic of the A5 World, and dig into the Sound volume of Inside Macintosh.

   Gestalt( gestaltSysArchitecture, &theResponse );
   if ( theResponse != gestaltPowerPC )
      DoError( "\pThis program only runs on Power Macs" );

   MenuBarInit();
   
   InitWindow();

   EventLoop();
}

The ToolBoxInit() and MenuBarInit() functions are the same as prior versions.

/******************** 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 );

   menu = GetMenuHandle( mApple );
   AppendResMenu( menu, 'DRVR' );
   
   DrawMenuBar();
}

InitWindow() creates a new window from the one WIND resource, then calls ShowWindow() to show the window and SetPort() to ensure that drawing takes place to this window. After that the PICT resource is loaded into memory and the returned picture handle is stored in the global variable gHelicopterPicture for later use during animation.

void InitWindow( void )
{
   WindowPtr   window;

   window = GetNewWindow( kWINDResID, nil, (WindowPtr)-1L );
   ShowWindow( window );
   SetPort( window );
  
   gHelicopterPicture = GetPicture( kPICTResID );
   if ( gHelicopterPicture == nil )
      DoError( "\pAttempt to load picture resource failed" );
}

Next we write the callback routine that's to be used by a call to SndPlay(). The format of the callback routine is: the pascal keyword, a return type of void, the function name, and a SndChannelPtr parameter and a SndCommand parameter. The body of the callback routine can include any code you want, but it typically just toggles the value a global variable from false to true. That's what we do here with the Boolean variable gCallbackExecuted.

pascal void SndChannelCallback(    SndChannelPtr theChannel, 
                                   SndCommand theCommand )
{
   gCallbackExecuted = true;
}

If gCallbackExecuted is set to false at some point before sound playing starts, then the program will know sound is playing and will animate - and continue to animate - until it sees that gCallbackExecuted takes on the value true. That happens when the sound ends and the system automatically invokes our SndChannelCallback() callback routine.

Now, the meat of the program - PlaySoundResource(). In last month's version of this sound resource playing routine, the following occurred: a sound resource was loaded, the resulting handle was locked, SndPlay() played the sound, the handle was unlocked, and the handle was released from memory. All of these actions again take place this month, but because the sound is now being played asynchronously we need to add a few more steps.

PlaySoundResource() begins with a call to the Toolbox routine NewSndCallBackProc(). This function creates a universal procedure pointer (UPP) to a callback function. In short, we've just created a pointer to our own application-defined function named SndChannelCallback(). This UPP will be used just ahead.

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

void PlaySoundResource( void )
{
   OSErr                  theErr;
   SndCallBackUPP      callBackUPP;
   SndCommand            theCommand;

   callBackUPP = NewSndCallBackProc( SndChannelCallback );

Next, a call to SndNewChannel() is made to create a new sound channel. When a sound channel is to be used to play asynchronous sound, a universal procedure pointer to a callback routine should be the last parameter. We've just created that UPP, so we're all set:

   theErr = SndNewChannel( &gSoundChannel, 0, 0, 
                                     callBackUPP );
  
   if ( theErr != noErr )
      DoError( "\pNew sound channel failed" );

Now the data from the sound resource is loaded to memory. We'll save the resulting handle in the global variable gSoundHandle:

   gSoundHandle = GetResource( 'snd ', ksnd_ResID );

   if ( gSoundHandle == nil )
      DoError( "\pAttempt to load sound resource failed" );

When a sound is playing asynchronously, just about any action can take place. Our very short AsynchPlayer example program doesn't do anything too tricky, so we can be reasonably sure nothing bad might take place during sound play. But a larger program could include code that closes an open resource file. If that file is the one that holds the sound resource that's being played, things could get ugly. So when playing a sound asynchronously it's wise to take the precaution of detaching the sound resource from its resource file. Even though the sound resource data has been loaded to memory, there could still be a dependency on information in the resource itself. Detaching the resource removes any dependency on the resource file.

   DetachResource( gSoundHandle );

Next, lock the sound handle - just as was done in last month's example. That prevents the sound data from getting moved about in memory as the sound plays.

   HLock( gSoundHandle );

Now set about playing the sound by calling SndPlay(). Here we're using SndPlay() for asynchronous sound playing so we need to provide our own sound channel pointer as the first parameter, a handle to the sound to play as the second parameter, and a value of true (for "yes, we're playing asynchronous sound") for the last parameter.

   theErr = SndPlay( gSoundChannel, 
                   (SndListHandle)gSoundHandle, true );
  
   if ( theErr != noErr )
      DoError( "\pPlaying of sound failed" );

Now, with sound playing started, we need to slip a sound command into the command queue of the sound channel that's just begun playing the sound. Earlier in this article you saw how to do that: define the fields of a SndCommand variable and call SndDoCommand() to place the command in a sound channel's queue. Here we define the sound command to be of the type callBackCmd. For this type of command both options (the param1 and param2 fields) are unused. The call to SndDoCommand() places theCommand in the sound channel pointed to by gSoundChannel. The third parameter value of false tells the Sound Manager to wait for an opening in the sound channel's queue if it is currently full (with room for 128 commands, an unlikely event).

   theCommand.cmd  = callBackCmd;
   theCommand.param1 = 0;
   theCommand.param2 = 0;
  
   theErr = SndDoCommand(gSoundChannel, &theCommand, false);

   if ( theErr != noErr )
      DoError( "\pSound command failed" );

Now that sound has started, it's also time to start the animation. We handle that in a separate application-defined function named AnimateWhileSoundPlays(). A call to that function ends the PlaySoundResource() code.

   AnimateWhileSoundPlays();
}

When sound starts, so does animation. For our simple example all we need to do to achieve an animated effect is to offset the picture boundary rectangle by one pixel in the horizontal direction. A call to OffsetRect() does just that. At each pass through the infinite while loop, gPictRect is repositioned one pixel to the left. This assumes, of course, that the sound is still playing. We'll know this to be the case if gCallbackExecuted remains false (the value the variable was initialized to when it was declared). As long as this flag is false (and note that its value is checked at each pass through the loop), animation continues.

void AnimateWhileSoundPlays( void )
{
   OSErr   theErr;
  
   while ( true )
   {
      if ( gCallbackExecuted == false )
      {
         OffsetRect( &gPictRect, -1, 0 );
         DrawPicture( gHelicopterPicture, &gPictRect );
      }

Once sound playing has completed, the system invokes the callback routine. That function sets gCallbackExecuted to true, which gets picked up on by the loop in AnimateWhileSoundPlays(). With the callback routine executed, it's time to wrap things up and end the animation loop. A call to HUnlock() unlocks the memory that holds the sound data, and a call to ReleaseResource() frees up this memory. We then dispose of the sound channel - a necessary step because we were the ones who allocated it. The global variable gSoundChannel is set to nil since it no longer points to valid data. The global flag gCallbackExecuted is set to false in preparation for the next call to SndPlay() (an act which occurs if the user again chooses Play and Move from the Sound menu). Finally, return breaks the otherwise infinite while loop and ends the AnimateWhileSoundPlays() function.

      else
      {
         HUnlock( gSoundHandle );
         ReleaseResource( gSoundHandle );
         gSoundHandle = nil;
   
         theErr = SndDisposeChannel( gSoundChannel, true );
      
         if ( theErr != noErr )
            DoError( "\pDisposing of sound channel failed" );

         gSoundChannel = nil;
         gCallbackExecuted = false;

         return ;         
      }
   } 
}

That just about does it for the new stuff. The remaining AsynchPlayer code is the basic stuff found in just about every Mac program. The only new code is a couple of minor additions to the code that handles a selection from the Sound menu.

/********************** 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;
   }
}

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

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

Here in HandleMenuChoice() we've added a switch case section for the handling of a selection from the Sound menu.

/******************* 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 Play and Move from the Sound menu, HandleSoundChoice() gets invoked. Before calling PlaySoundResource() to start the sound and animation, the global rectangle that holds the starting boundaries of the picture is reset. This places the picture at the far right of the window so it can begin its journey to the left side of the window.

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

void      HandleSoundChoice( short item )
{
   switch ( item )
   {
      case iMovePlay:
         SetRect(   &gPictRect, gPictStartX, gPictStartY,
                     gPictStartX + gPictWidth, 
                     gPictStartY + gPictHeight );
      
         PlaySoundResource();
         break;
   }
}

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

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

Running AsynchPlayer

Run AsynchPlayer by selecting Run from the Project menu. After the code compiles, a menu bar and an empty window appear. Choose Play and Move from the Sound menu to confirm that the sound that plays coincides with the animation that takes place. Depending on the speed of your Mac, the helicopter may or may not make it off the left edge of the window. If the helicopter does exit the window while the sound is still playing, you may want to make the window larger (and move the starting point of the picture farther to the right) so that you can verify that when the sound stops the animation ends as well. When you're satisfied that all is working as intended, choose Quit from the File menu to end the program.

Till Next Month...

The purpose of playing a sound asynchronously is to allow sound and animation to occur simultaneously. The purpose of the callback routine is to free your program from having to somehow predetermine the duration of a sound. When a sound ends playing, the callback routine in effect tells the program that this is the case. You can prove that the callback routine works by opening the standalone AsynchPlayer application itself from a resource editor such as ResEdit. Give the helicopter snd resource a different ID (any ID will do - you're just changing it so that the program won't use this resource). Now add a different snd resource to the program and give it an ID of 12000 - the ID of the snd resource the program is expecting to find. Save AsynchPlayer and close it. Now run AsynchPlayer and choose Play and Move from the Sound menu. Doing that moves the helicopter, but now the animation will be of a different length then before. Regardless of the length of the new sound, the animation ends when the sound ends.

AsynchPlayer demonstrates asynchronous sound playing using a sound resource. If you want to instead use a sound file, read last month's Getting Started article to learn about sound files and the SndStartFilePlay() routine, and then read the Sound volume of Inside Macintosh to learn about completion routines. Like the callback routine used with SndPlay(), the completion routine used with SndStartFilePlay() executes when a sound has completed playing. In fact, it turns out that the callback routine you just learned about is a type of completion routine.

Finally, you may want to try asynchronous sound playing with a different animation technique. Here, because the focus was on sound playing, we used the simple trick of repositioning a picture to achieve animation. Try changing the AnimateWhileSoundPlays() function so that it uses offscreen bitmaps to achieve smooth, flicker-free animation that doesn't obscure a window's background. You can read up on offscreen bitmaps in the Getting Started column in both the October and the December issues of last year.

There are plenty of other sound-related tricks and techniques to learn about in the Sound volume of Inside Macintosh. Go ahead and read up on sound, and experiment with the AsynchPlayer code until we cover a new topic next month...

 
AAPL
$112.65
Apple Inc.
+3.24
MSFT
$47.52
Microsoft Corpora
+1.78
GOOG
$511.10
Google Inc.
+6.21

MacTech Search:
Community Search:

Software Updates via MacUpdate

Mellel 3.3.7 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
ScreenFlow 5.0.1 - Create screen recordi...
Save 10% with the exclusive MacUpdate coupon code: AFMacUpdate10 Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your... Read more
Simon 4.0 - Monitor changes and crashes...
Simon monitors websites and alerts you of crashes and changes. Select pages to monitor, choose your alert options, and customize your settings. Simon does the rest. Keep a watchful eye on your... Read more
BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
ExpanDrive 4.2.1 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Adobe After Effects CC 2014 13.2 - Creat...
After Effects CC 2014 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous After Effects customer). After Effects CS6 is still available... Read more
Command-C 1.1.7 - Clipboard sharing tool...
Command-C is a revolutionary app which makes easy to share your clipboard between iOS and OS X using your local WiFi network, even if the app is not currently opened. Copy anything (text, pictures,... Read more
Tidy Up 4.0.2 - Find duplicate files and...
Tidy Up is a complete duplicate finder and disk-tidiness utility. With Tidy Up you can search for duplicate files and packages by the owner application, content, type, creator, extension, time... Read more
Typinator 6.3 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
GraphicConverter 9.5 - Graphics editor w...
GraphicConverter is an all-purpose image-editing program that can import 200 different graphic-based formats, edit the image, and export it to any of 80 available file formats. The high-end editing... Read more

Latest Forum Discussions

See All

Shift - Photo Filters Designed By You (...
Shift - Photo Filters Designed By You 1.0 Device: iOS Universal Category: Photography Price: $.99, Version: 1.0 (iTunes) Description: | Read more »
Elastic Drums (Music)
Elastic Drums 1.0 Device: iOS iPhone Category: Music Price: $3.99, Version: 1.0 (iTunes) Description: *** Introduction price 3,99$ instead of 7,99$ *** Elastic Drums is a music app with 6 channels of synthesized drum sounds, a step... | Read more »
Fireworks Simulator (Games)
Fireworks Simulator 1.0.8 Device: iOS Universal Category: Games Price: $.99, Version: 1.0.8 (iTunes) Description: *** 50% discount – For a short time only *** You can play Fireworks Simulator on these devices: - iPhone 5, 5s, 5c, 6,... | Read more »
Nicky's Gift (Games)
Nicky's Gift 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Everybody! Merry Christmas! There's 48 levels in the game. Let's go! Nicky's Gift | Read more »
The Hit List — Simply Powerful Tasks, To...
The Hit List — Simply Powerful Tasks, To-Dos, Projects, & Reminders 2.0 Device: iOS iPhone Category: Productivity Price: $9.99, Version: 2.0 (iTunes) Description: >> LAUNCH SPECIAL: The Hit List 2 for iPhone is ONLY $9.99... | Read more »
Mahjong Journey Review
Mahjong Journey Review By Jennifer Allen on December 18th, 2014 Our Rating: :: STEADY MATCHINGiPad Only App - Designed for the iPad Aimed at the more laid back gamer, Mahjong Journey isn’t for everyone, but those looking for some... | Read more »
Emoji Type - custom keyboard with predic...
Emoji Type - custom keyboard with predictive emojis 0.4.0 Device: iOS iPhone Category: Utilities Price: $.99, Version: 0.4.0 (iTunes) Description: Emoji Type is custom keyboard for iOS 8 that auto suggests emojis as you type. ABOUT... | Read more »
Game of the Year 2014 – 148Apps Staff Pi...
The end of 2014 is almost here, which can only mean one thing. Okay it can mean a lot of things, but in this specific context it means Game of the Year lists! Which is why the 148Apps staff have all picked their favorites from the past year. And why... | Read more »
UponPixels Review
UponPixels Review By Jennifer Allen on December 18th, 2014 Our Rating: :: CREATIVE TYPOGRAPHYUniversal App - Designed for iPhone and iPad Add cool typography and objects to your photos with the easy to use UponPixels.   | Read more »
The Vikings are Coming! CastleStorm’s Ne...
The Vikings are Coming! CastleStorm’s New Update Adds a Survival Mode Posted by Jessica Fisher on December 18th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

Save up to $400 on MacBooks with Apple Certif...
The Apple Store has Apple Certified Refurbished 2014 MacBook Pros and MacBook Airs available for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Save up to $300 on Macs, $30 on iPads with Ap...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
iOS and Android OS Targeted by Man-in-the-Mid...
Cloud services security provider Akamai Technologies, Inc. has released, through the company’s Prolexic Security Engineering & Research Team (PLXsert), a new cybersecurity threat advisory. The... Read more
KMI MIDI K-Board Great Gift for Amateur &...
The K-Board is a MIDI Nano keyboard for music creation for iPad, Android, And computers; the easiest way to make music with iPads & Android tablets, and Mac, Windows, or Linux computers. Ultra-... Read more
Amazon offers 15-inch 2.2GHz Retina MacBook P...
 Amazon.com has the 15″ 2.2GHz Retina MacBook Pro on sale for $1699 including free shipping. Their price is $300 off MSRP. Stock is limited, so act now if you’re interested. Read more
Holiday sales continue: MacBook Pros for up t...
 B&H Photo has new MacBook Pros on sale for up to $300 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $1699... Read more
Holiday sale: Mac minis available for up to $...
 B&H Photo has new 2014 Mac minis on sale for up to $80 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 1.4GHz Mac mini: $459 $40 off MSRP - 2.6GHz Mac mini: $629 $70 off... Read more
Google Search App For iOS Gets A Major Makeov...
Google has given iOS users an early Christmas present with a substantial update of it’s not-very-often-upgraded Google Search app. Google Search has been my go-to tool for Web searches since it was... Read more
ShopKeep Apple Pay And Chip Card Reader Avail...
ShopKeep, a cloud-based technology provider to more than 10,000 small business owners to manage retail shops and restaurants with iPads, has released its new Apple Pay and chip card reader. This... Read more
Holiday sale! 27-inch 5K iMac for $2299, save...
 B&H Photo has the 27″ 3.5GHz 5K iMac in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP, and it’s the lowest price available for... Read more

Jobs Board

Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.