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...

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Chromium 45.0.2454.85 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. Version 45.0.2454.85: Note: Does not contain the "... Read more
OmniFocus 2.2.5 - GTD task manager with...
OmniFocus helps you manage your tasks the way that you want, freeing you to focus your attention on the things that matter to you most. Capturing tasks and ideas is always a keyboard shortcut away in... Read more
iFFmpeg 5.7.1 - Convert multimedia files...
iFFmpeg is a graphical front-end for FFmpeg, a command-line tool used to convert multimedia files between formats. The command line instructions can be very hard to master/understand, so iFFmpeg does... Read more
VOX 2.6 - Music player that supports man...
VOX is a beautiful music player that supports many filetypes. The beauty is in its simplicity, yet behind the minimal exterior lies a powerful music player with a ton of features and support for all... Read more
Box Sync 4.0.6567 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Carbon Copy Cloner 4.1.4 - Easy-to-use b...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
OmniGraffle Pro 6.3.1 - Create diagrams,...
OmniGraffle Pro helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use... Read more
Monosnap 3.1.2 - Versatile screenshot ut...
Monosnap lets you capture screenshots, share files, and record video and .gifs! Capture: Capture full screen, just part of the screen, or a selected window Make your crop area pixel perfect with... Read more
Alfred 2.7.2 - Quick launcher for apps a...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
Microsoft Remote Desktop 8.0.19 - Connec...
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

We're Sorry to Report that Moonrise...
Moonrise is a very promising-looking, Pokemon-esque monster collecting and battling game that we were really looking forward to reviewing, but unfortunately it looks like that's never going to happen. [Read more] | Read more »
The Latest Update for The Sims FreePlay...
Commerce has gotten a little more active with the newest update for The Sims FreePlay, making Sunset Mall more of a hangout than ever before. [Read more] | Read more »
This Week at 148Apps: August 24-28, 2015
The Apps of August With 148Apps How do you know what apps are worth your time and money? Just look to the review team at 148Apps. We sort through the chaos and find the apps you're looking for. The ones we love become Editor’s Choice, standing out... | Read more »
NASCAR in Real Racing 3? Sure, Why Not?
I have to give Firemonkeys credit - it's very cool of them to add NASCAR to Real Racing 3 via an update rather than making a separate game for it. But that's a different discussion for another time; for now let's sit back and enjoy driving in... | Read more »
The nuyu is an Inexpensive Activity Moni...
Today, Health o Meter nuyu has announced a series of health and fitness-related products, including the aforementioned activity monitor along with a wireless scale. All at a decent pricepoint, no less. [Read more] | Read more »
The Makers of Overkill are Trying Someth...
Craneballs, the studio responsible for the Overkill series, is taking a little break from all that violence (a little break) to bring us Cube Worm - a 3D take on one of the most classic PC/calculator games in existence. [Read more] | Read more »
The Sandbox Welcomes Cutethulu in its La...
Another month, another update to Pixowl's The Sandbox. This time players can say hello to "Cutethulu" - an adorable little rendition of perhaps the most well known (and infamous) of the Elder Gods. [Read more] | Read more »
ReBoard: Revolutionary Keyboard (Utilit...
ReBoard: Revolutionary Keyboard 1.0 Device: iOS Universal Category: Utilities Price: $1.99, Version: 1.0 (iTunes) Description: Do everything within the keyboard without switching apps! If you are in WhatsApp, how do you schedule a... | Read more »
Tiny Empire (Games)
Tiny Empire 1.1.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.1.3 (iTunes) Description: Launch cannonballs and blow tiny orcs into thousands of pieces in this intuitive fantasy-themed puzzle shooter! Embark on an... | Read more »
Astropad Mini (Productivity)
Astropad Mini 1.0 Device: iOS iPhone Category: Productivity Price: $4.99, Version: 1.0 (iTunes) Description: *** 50% off introductory price! ​*** Get the high-end experience of a Wacom tablet at a fraction of the price with Astropad... | Read more »

Price Scanner via MacPrices.net

eFileCabinet Announces SMB Document Managemen...
Electronic document management (EDM) eFileCabinet, Inc., a hosted solutions provider for small to medium businesses, has announced that its SecureDrawer and eFileCabinet Online services will be... Read more
WaterField Designs Unveils American-Made, All...
San Francisco’s WaterField Designs today unveiled their all-leather Cozmo 2.0 — an elegant attach laptop bag with carefully-designed features to suit any business environment. The Cozmo 2.0 is... Read more
Apple’s 2015 Back to School promotion: Free B...
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
128GB MacBook Airs on sale for $100 off MSRP,...
B&H Photo has 11″ & 13″ MacBook Airs with 128GB SSDs on sale for $100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 11″ 1.6GHz/128GB MacBook Air: $799.99, $100 off MSRP... Read more
13-inch 2.5GHz MacBook Pro (refurbished) avai...
The Apple Store has Apple Certified Refurbished 13″ 2.5GHz MacBook Pros available for $829, or $270 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.... Read more
27-inch 3.2GHz iMac on sale for $1679, save $...
B&H Photo has the 27″ 3.2GHz iMac on sale for $1679.99 including free shipping plus NY sales tax only. Their price is $120 off MSRP. Read more
Apple and Cisco Partner to Deliver Fast-Lane...
Apple and Cisco have announced a partnership to create a “fast lane” for iOS business users by optimizing Cisco networks for iOS devices and apps. The alliance integrates iPhone with Cisco enterprise... Read more
Apple offering refurbished 2015 13-inch Retin...
The Apple Store is offering Apple Certified Refurbished 2015 13″ Retina MacBook Pros for up to $270 (15%) off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple refurbished 2015 MacBook Airs available...
The Apple Store has Apple Certified Refurbished 2015 11″ and 13″ MacBook Airs (the latest models), available for up to $180 off the cost of new models. An Apple one-year warranty is included with... Read more
21-inch iMacs on sale for up to $120 off MSRP
B&H Photo has 21″ iMacs on sale for up to $120 off MSRP including free shipping plus NY sales tax only: - 21″ 1.4GHz iMac: $999.99 $100 off - 21″ 2.7GHz iMac: $1199.99 $100 off - 21″ 2.9GHz iMac... Read more

Jobs Board

*Apple* Desktop Analyst - KDS Staffing (Unit...
…field and consistent professional recruiting achievement. Job Description: Title: Apple Desktop AnalystPosition Type: Full-time PermanentLocation: White Plains, NYHot Read more
Simply Mac- *Apple* Specialist- Store Manag...
Simply Mac is the largest premier retailer for Apple products and solutions. We're looking for dedicated individuals with a passion to simplify and enhance the Read more
*Apple* Evangelist - JAMF Software (United S...
The Apple Evangelist is responsible for building and cultivating strategic relationships with Apple 's small and mid-market business development field teams. This 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* Desktop Analyst - KDS Staffing (Unit...
…field and consistent professional recruiting achievement. Job Description: Title: Apple Desktop AnalystPosition Type: Full-time PermanentLocation: White Plains, NYHot Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.