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

Bookends 12.8 - Reference management and...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Adobe Creative Cloud 4.0.0.185 - Access...
Adobe Creative Cloud costs $19.99/month for a single app, or $49.99/month for the entire suite. Introducing Adobe Creative Cloud desktop applications, including Adobe Photoshop CC and Illustrator CC... Read more
Default Folder X 5.1.4 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click on... Read more
Amazon Chime 4.1.5587 - Amazon-based com...
Amazon Chime is a communications service that transforms online meetings with a secure, easy-to-use application that you can trust. Amazon Chime works seamlessly across your devices so that you can... Read more
Persecond 1.0.9 - Timelapse video made e...
Persecond is the easy, fun way to create a beautiful timelapse video. Import an image sequence from any camera, trim the length of your video, adjust the speed and playback direction, and you’re done... Read more
CrossOver 16.2 - Run Windows apps on you...
CrossOver can get your Windows productivity applications and PC games up and running on your Mac quickly and easily. CrossOver runs the Windows software that you need on Mac at home, in the office,... Read more
MegaSeg 6.0.2 - Professional DJ and radi...
MegaSeg is a complete solution for pro audio/video DJ mixing, radio automation, and music scheduling with rock-solid performance and an easy-to-use design. Mix with visual waveforms and Magic... Read more
Apple iTunes 12.6 - Play Apple Music and...
Apple iTunes lets you organize and stream Apple Music, download and watch video and listen to Podcasts. It can automatically download new music, app, and book purchases across all your devices and... Read more
GraphicConverter 10.4 - $39.95
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
OpenEmu 2.0.5 - Open Source game-emulati...
OpenEmu is about to change the world of video game emulation, one console at a time... For the first time, the 'It just works' philosophy now extends to open source video game emulation on the Mac.... Read more

The Elder Scrolls: Legends is now availa...
| Read more »
Ticket to Earth beginner's guide: H...
Robot Circus launched Ticket to Earth as part of the App Store's indie games event last week. If you're not quite digging the space operatics Mass Effect: Andromeda is serving up, you'll be pleased to know that there's a surprising alternative on... | Read more »
Leap to victory in Nexx Studios new plat...
You’re always a hop, skip, and a jump away from a fiery death in Temple Jump, a new platformer-cum-endless runner from Nexx Studio. It’s out now on both iOS and Android if you’re an adventurer seeking treasure in a crumbling, pixel-laden temple. | Read more »
Failbetter Games details changes coming...
Sunless Sea, Failbetter Games' dark and gloomy sea explorer, sets sail for the iPad tomorrow. Ahead of the game's launch, Failbetter took to Twitter to discuss what will be different in the mobile version of the game. Many of the changes make... | Read more »
Splish, splash! The Pokémon GO Water Fes...
Niantic is back with a new festival for dedicated Pokémon GO collectors. The Water Festival officially kicks off today at 1 P.M. PDT and runs through March 29. Magikarp, Squirtle, Totodile, and their assorted evolved forms will be appearing at... | Read more »
Death Road to Canada (Games)
Death Road to Canada 1.0 Device: iOS Universal Category: Games Price: $7.99, Version: 1.0 (iTunes) Description: Get it now at the low launch price! Price will go up a dollar every major update. Update news at the bottom of this... | Read more »
Bean's Quest Beginner's Guide:...
Bean's Quest is a new take on both the classic platformer and the endless runner, and it's free on the App Store for the time being. Instead of running constantly, you can't stop jumping. That adds a surprising new level of challenge to the game... | Read more »
How to rake in the cash in Bit City
Our last Bit City guide covered the basics. Now it's time to get into some of the more advanced techniques. In the later cities, cash flow becomes much more difficult, so you'll want to develop some strategies if you want to complete each level.... | Read more »
PixelTerra (Games)
PixelTerra 1.1.1 Device: iOS Universal Category: Games Price: $.99, Version: 1.1.1 (iTunes) Description: The world of PixelTerra is quite dangerous so you need to build a shelter, find some food supply and get ready to protect... | Read more »
Tokaido™ (Games)
Tokaido™ 1.0 Device: iOS Universal Category: Games Price: $6.99, Version: 1.0 (iTunes) Description: Discover the digital adaptation of Tokaido, the boardgame phenomenon that has already sold more than 250,000 copies worldwide, and... | Read more »

Price Scanner via MacPrices.net

SSD Speeder RAM Disk SSD Life Extender App Fo...
Fehraltorf, Switzerland based B-Eng has announced they are making their SSD Speeder app for macOS publicly available for purchase on their website. SSD Speeder is a RAM disk utility that prevents... Read more
iPhone Scores Highest Overall in Smartphone D...
Customer satisfaction is much higher among smartphone owners who use their device to operate other connected home services such as smart thermostats and smart appliances, according to the J.D. Power... Read more
Swipe CRM Free Photo-Centric CRM Sales DEal C...
Swipe CRM LLC has introduced Swipe CRM: Visual Sales 1.0 for iPad, an app for creating, managing, and sharing visually stunning sales deals. Swipe CRM is targeted to small-and-medium creative... Read more
13-inch 2.0GHz Apple MacBook Pros on sale for...
B&H has the non-Touch Bar 13″ 2.0GHz MacBook Pros in stock today and on sale for $150 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.0GHz MacBook Pro Space Gray (... Read more
15-inch Touch Bar MacBook Pros on sale for up...
B&H Photo has the new 2016 15″ Apple Touch Bar MacBook Pros in stock today and on sale for up to $150 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.7GHz Touch Bar... Read more
Apple’s iPhone 6s Tops Best-Selling Smartphon...
In terms of shipments, the iPhone 6s from Apple bested all competitors for sales in 2016, according to new analysis from IHS Markit, a world leader in critical information, analytics and solutions.... Read more
Logitech Rugged Combo Protective iPad Case an...
Logitech has announced its Logitech Rugged Combo, Logitech Rugged Case, and Logitech Add-on Keyboard for Rugged Case for Apple’s new, more affordable $329 9.7-inch iPad, a complete solution designed... Read more
T-Mobile To Offer iPhone 7 and iPhone 7 Plus...
T-Mobile has announced it will offer iPhone 7 and iPhone 7 Plus (PRODUCT)RED Special Edition in a vibrant red aluminum finish. The introduction of this special edition iPhone celebrates Apple’s 10... Read more
9-inch 128GB iPad Pros on sale for $50-$70 of...
B&H Photo has 9.7″ 128GB Apple WiFi iPad Pros on sale for up to $70 off MSRP, each including free shipping. B&H charges sales tax in NY only: - 9″ Space Gray 128GB WiFi iPad Pro: $649 $50... Read more
27-inch iMacs on sale for up to $200 off MSRP...
B&H Photo has 27″ Apple iMacs on sale for up to $200 off MSRP, each including free shipping plus NY sales tax only: - 27″ 3.3GHz iMac 5K: $2099 $200 off MSRP - 27″ 3.2GHz/1TB Fusion iMac 5K: $... Read more

Jobs Board

*Apple* Retail - Multiple Positions- Chicago...
SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Fulltime aan de slag als shopmanager in een h...
Ben jij helemaal gek van Apple -producten en vind je het helemaal super om fulltime shopmanager te zijn in een jonge en hippe elektronicazaak? Wil jij werken in Read more
Starte Dein Karriere-Abenteuer in den Hauptst...
…mehrsprachigen Teams betreust Du Kunden von bekannten globale Marken wie Apple , Mercedes, Facebook, Expedia, und vielen anderen! Funktion Du wolltest schon Read more
*Apple* macOS Systems Integration Administra...
…most exceptional support available in the industry. SCI is seeking an Junior Apple macOS systems integration administrator that will be responsible for providing Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.