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

Latest Forum Discussions

See All

Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links... | Read more »
Price of Glory unleashes its 1.4 Alpha u...
As much as we all probably dislike Maths as a subject, we do have to hand it to geometry for giving us the good old Hexgrid, home of some of the best strategy games. One such example, Price of Glory, has dropped its 1.4 Alpha update, stocked full... | Read more »
The SLC 2025 kicks off this month to cro...
Ever since the Solo Leveling: Arise Championship 2025 was announced, I have been looking forward to it. The promotional clip they released a month or two back showed crowds going absolutely nuts for the previous competitions, so imagine the... | Read more »
Dive into some early Magicpunk fun as Cr...
Excellent news for fans of steampunk and magic; the Precursor Test for Magicpunk MMORPG Crystal of Atlan opens today. This rather fancy way of saying beta test will remain open until March 5th and is available for PC - boo - and Android devices -... | Read more »
Prepare to get your mind melted as Evang...
If you are a fan of sci-fi shooters and incredibly weird, mind-bending anime series, then you are in for a treat, as Goddess of Victory: Nikke is gearing up for its second collaboration with Evangelion. We were also treated to an upcoming... | Read more »
Square Enix gives with one hand and slap...
We have something of a mixed bag coming over from Square Enix HQ today. Two of their mobile games are revelling in life with new events keeping them alive, whilst another has been thrown onto the ever-growing discard pile Square is building. I... | Read more »
Let the world burn as you have some fest...
It is time to leave the world burning once again as you take a much-needed break from that whole “hero” lark and enjoy some celebrations in Genshin Impact. Version 5.4, Moonlight Amidst Dreams, will see you in Inazuma to attend the Mikawa Flower... | Read more »
Full Moon Over the Abyssal Sea lands on...
Aether Gazer has announced its latest major update, and it is one of the loveliest event names I have ever heard. Full Moon Over the Abyssal Sea is an amazing name, and it comes loaded with two side stories, a new S-grade Modifier, and some fancy... | Read more »
Open your own eatery for all the forest...
Very important question; when you read the title Zoo Restaurant, do you also immediately think of running a restaurant in which you cook Zoo animals as the course? I will just assume yes. Anyway, come June 23rd we will all be able to start up our... | Read more »
Crystal of Atlan opens registration for...
Nuverse was prominently featured in the last month for all the wrong reasons with the USA TikTok debacle, but now it is putting all that behind it and preparing for the Crystal of Atlan beta test. Taking place between February 18th and March 5th,... | Read more »

Price Scanner via MacPrices.net

AT&T is offering a 65% discount on the ne...
AT&T is offering the new iPhone 16e for up to 65% off their monthly finance fee with 36-months of service. No trade-in is required. Discount is applied via monthly bill credits over the 36 month... Read more
Use this code to get a free iPhone 13 at Visi...
For a limited time, use code SWEETDEAL to get a free 128GB iPhone 13 Visible, Verizon’s low-cost wireless cell service, Visible. Deal is valid when you purchase the Visible+ annual plan. Free... Read more
M4 Mac minis on sale for $50-$80 off MSRP at...
B&H Photo has M4 Mac minis in stock and on sale right now for $50 to $80 off Apple’s MSRP, each including free 1-2 day shipping to most US addresses: – M4 Mac mini (16GB/256GB): $549, $50 off... Read more
Buy an iPhone 16 at Boost Mobile and get one...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering one year of free Unlimited service with the purchase of any iPhone 16. Purchase the iPhone at standard MSRP, and then choose... Read more
Get an iPhone 15 for only $299 at Boost Mobil...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering the 128GB iPhone 15 for $299.99 including service with their Unlimited Premium plan (50GB of premium data, $60/month), or $20... Read more
Unreal Mobile is offering $100 off any new iP...
Unreal Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering a $100 discount on any new iPhone with service. This includes new iPhone 16 models as well as iPhone 15, 14, 13, and SE... Read more
Apple drops prices on clearance iPhone 14 mod...
With today’s introduction of the new iPhone 16e, Apple has discontinued the iPhone 14, 14 Pro, and SE. In response, Apple has dropped prices on unlocked, Certified Refurbished, iPhone 14 models to a... Read more
B&H has 16-inch M4 Max MacBook Pros on sa...
B&H Photo is offering a $360-$410 discount on new 16-inch MacBook Pros with M4 Max CPUs right now. B&H offers free 1-2 day shipping to most US addresses: – 16″ M4 Max MacBook Pro (36GB/1TB/... Read more
Amazon is offering a $100 discount on the M4...
Amazon has the M4 Pro Mac mini discounted $100 off MSRP right now. Shipping is free. Their price is the lowest currently available for this popular mini: – Mac mini M4 Pro (24GB/512GB): $1299, $100... Read more
B&H continues to offer $150-$220 discount...
B&H Photo has 14-inch M4 MacBook Pros on sale for $150-$220 off MSRP. B&H offers free 1-2 day shipping to most US addresses: – 14″ M4 MacBook Pro (16GB/512GB): $1449, $150 off MSRP – 14″ M4... Read more

Jobs Board

All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.