Previous Book Contents Book Index Next

Inside Macintosh: Sound /
Chapter 2 - Sound Manager / Using the Sound Manager


Legacy Documentclose button

Important: Inside Macintosh: Sound is deprecated as of Mac OS X v10.5. For new audio development in Mac OS X, use Core Audio. See the Audio page in the ADC Reference Library.

Managing Sound Channels

To use most of the low-level Sound Manager routines, you must specify a sound channel that maintains a queue of commands. Also, to take advantage of the full capabilities of the high-level Sound Manager routines, including asynchronous sound play, you must allocate your own sound channels. This section explains how your application can allocate, dispose of, and use its own sound channels.

This section first describes how you can allocate and dispose of sound channels. Then it explains how you can manipulate sounds playing in sound channels, stop sounds playing in sound channels, and pause and restart the execution of sounds in sound channels.

Allocating Sound Channels

Usually, you do not need to worry about allocating memory for sound channels because the SndNewChannel function automatically allocates a sound channel record in the application's heap if passed a pointer to a NIL sound channel. SndNewChannel also internally allocates memory for the sound channel's queue of sound commands. For example, the following lines of code request that the Sound Manager open a new sound channel for playing sampled sounds:

mySndChan := NIL;
myErr := SndNewChannel(mySndChan, sampledSynth, 0, NIL);
If you are concerned with managing memory yourself, you can allocate your own memory for a sound channel record and pass the address of that memory as the first parameter to SndNewChannel. By allocating a sound channel record manually, you not only obtain control over the allocation of the sound channel record, but you can specify the size of the queue of sound commands that the Sound Manager internally allocates. Listing 2-1 illustrates one way to do this.

Listing 2-1 Creating a sound channel

FUNCTION MyCreateSndChannel (synth: Integer; initOptions: LongInt;
                              userRoutine: ProcPtr; 
                              queueLength: Integer): SndChannelPtr;
VAR
   mySndChan:  SndChannelPtr;    {pointer to a sound channel}
   myErr:      OSErr;
BEGIN
   {Allocate memory for sound channel.}
   mySndChan := SndChannelPtr(NewPtr(Sizeof(SndChannel)));
   IF mySndChan <> NIL THEN
   BEGIN
      mySndChan^.qLength := queueLength;  {set number of commands in queue}
      {Create a new sound channel.}
      myErr := SndNewChannel(mySndChan, synth, initOptions, userRoutine);
      IF myErr <> noErr THEN
      BEGIN                               {couldn't allocate channel}
         DisposePtr(Ptr(mySndChan));      {free memory already allocated}
         mySndChan := NIL;                {return NIL}
      END
      ELSE
         mySndChan^.userInfo := 0;        {reset userInfo field}
   END;
   MyCreateSndChannel := mySndChan;       {return new sound channel}
END;
The MyCreateSndChannel function defined in Listing 2-1 first allocates memory for a sound channel record and then calls the SndNewChannel function to attempt to allocate a channel. Note that MyCreateSndChannel checks the result code returned by SndNewChannel to determine whether the function was able to allocate a channel. The SndNewChannel function might not be able to allocate a channel if there are so many channels open that allocating another would put too much strain on the CPU. Also, SndNewChannel might fail if memory is low. (In addition to the memory for a sound channel record that is passed in the first parameter to SndNewChannel, the function must internally allocate memory in which to store sound commands.)

If you allocate memory for a sound channel record, you should specify the size of the queue of sound commands by assigning a value to the qLength field of the sound channel record you allocate. You can use the constant stdQLength to obtain a standard queue of 128 sound commands, or you can provide a value of your own.

CONST
   stdQLength           = 128;   {default size of a sound channel}
If you know that your application will play only resources containing sampled sound, you might set the qLength field to a considerably lower value, because resources created with the SndRecord function (described in the chapter "Introduction to Sound on the Macintosh" in this book) contain only one sound command, the bufferCmd command, which specifies that a buffer of sound should be played. For example, if your application uses a sound channel only to play a single sampled sound asynchronously, you can set qLength to 2, to allow for the bufferCmd command and a callBackCmd command that your application issues manually, as described in "Playing Sounds Asynchronously" on page 2-46. By using a smaller than standard queue length, your application can conserve memory.

Note
The number of sound commands in a channel should be an integer greater than 0. If you open a channel with a 0-length queue, most of the Sound Manager routines will return a badChannel result code.
IMPORTANT
In general, however, you should let the Sound Manager allocate sound channel records for you. The amount of memory you might save by allocating your own is usually negligible.
The second parameter in the SndNewChannel function specifies the kind of data you want to play on that channel. You can specify one of the following constants:

CONST
   squareWaveSynth   = 1;     {square-wave data}
   waveTableSynth    = 3;     {wave-table data}
   sampledSynth      = 5;     {sampled-sound data}
In some versions of system software prior to system software version 7.0 (including system software version 6.0.7), high-level Sound Manager routines do not work properly with sound resources that specify the sound data type twice. This might happen if a resource specifies that a sound consists of sampled-sound data and an application does the same when creating a sound channel. This might also happen if an application uses the same sound channel to play several sound resources that contain different kinds of sound data. There are several solutions to this problem that you can use if you must maintain compatibility with old versions of system software:

Note that this problem does not occur with sound files, because sound files always contain sampled-sound data and thus do not explicitly declare their data type. As a result, when creating a channel in which you plan to play a sound file, pass sampledSynth as the second parameter to SndNewChannel.

The third parameter in the SndNewChannel function specifies the initialization parameters to be associated with the new channel. These are discussed in the following section. The fourth parameter in the SndNewChannel function is a pointer to a callback procedure. If your application produces sounds asynchronously or needs to be alerted when a command has completed, you can specify a callback procedure by passing the address of that procedure in the fourth parameter and then by installing a callback procedure into the sound channel. If you pass NIL as the fourth parameter, then no callback procedure is associated with the channel. See "Playing Sounds Asynchronously" on page 2-46 for more information on setting up and using callback procedures.

Initializing Sound Channels

When you first create a sound channel with SndNewChannel, you can request that the channel have certain characteristics as specified by a sound channel initialization parameter. For example, to indicate that you want to allocate a channel capable of producing stereo sound, you might use the following code:

myErr := SndNewChannel(mySndChan, sampledSynth, initStereo, NIL);
These are the currently recognized constants for the sound channel initialization parameter.

CONST
   initChanLeft      = $0002;    {left stereo channel}
   initChanRight     = $0003;    {right stereo channel}
   waveInitChannel0  = $0004;    {wave-table channel 0}
   waveInitChannel1  = $0005;    {wave-table channel 1}
   waveInitChanne12  = $0006;    {wave-table channel 2}
   waveInitChannel3  = $0007;    {wave-table channel 3}
   initMono          = $0080;    {monophonic channel}
   initStereo        = $00C0;    {stereo channel}
   initMACE3         = $0300;    {3:1 compression}
   initMACE6         = $0400;    {6:1 compression}
   initNoInterp      = $0004;    {no linear interpolation}
   initNoDrop        = $0008;    {no drop-sample conversion}
See "Channel Initialization Parameters" beginning on page 2-91 for a complete description of these constants.

Note
Some Macintosh computers play only the left channel of stereo sounds out the internal speaker. Other machines (for example, the Macintosh SE/30 and Macintosh IIsi) mix both channels together before sending a signal to the internal speaker. You can use the Gestalt function to determine if a particular machine mixes both left and right channels to the internal speaker. All Macintosh computers except the Macintosh SE and the Macintosh Plus, however, play stereo signals out the headphone jack.
The initialization parameters are additive. To initialize a channel for stereo sound with no linear interpolation, simply pass an initialization parameter that is the sum of the desired characteristics, as follows:

myErr := SndNewChannel(mySndChan, sampledSynth, 
                        initStereo+initNoInterp, NIL);
A call to SndNewChannel is really only a request that the Sound Manager open a channel having the desired characteristics. It is possible that the parameters requested are not available. In that case, SndNewChannel returns a notEnoughHardwareErr error. In general, you should pass 0 as the third parameter to SndNewChannel unless you know exactly what kind of sound is to be played.

You can alter certain initialization parameters, even while a channel is actively playing a sound, by issuing the reInitCmd command. For example, you can change the output channel from left to right, as shown in Listing 2-2.

Listing 2-2 Reinitializing a sound channel

VAR
   mySndCmd:         SndCommand;
   mySndChan:        SndChannelPtr;
   myErr:            OSErr;
.
.
.
mySndCmd.cmd := reInitCmd;
mySndCmd.param1 := 0;                        {unused}
mySndCmd.param2 := initChanRight;            {new init parameter}
myErr := SndDoImmediate(mySndChan, mySndCmd);
The reInitCmd command accepts the initNoInterp constant to toggle linear interpolation on and off; it should be used with noncompressed sounds only. If an noncompressed sound is playing when you send a reInitCmd command with this constant, linear interpolation begins immediately. You can also pass initMono, initChanLeft, or initChanRight to pan to both channels, to the left channel, or to the right channel. This affects only monophonic sounds. The Sound Manager remembers the settings you pass and applies them to all further sounds played on that channel.

Releasing Sound Channels

To dispose of a sound channel that you have allocated with SndNewChannel, use the SndDisposeChannel function. SndDisposeChannel requires two parameters, a pointer to the channel that is to be disposed and a Boolean value that indicates whether the channel should be flushed before disposal. Here's an example:

myErr := SndDisposeChannel(mySndChan, TRUE);
Because the second parameter is TRUE, the Sound Manager sends both a flushCmd command and a quietCmd command to the sound channel (using SndDoImmediate). This removes all commands from the sound channel and stops any sound already in progress. Then the Sound Manager disposes of the channel.

If the second parameter is FALSE, the Sound Manager simply queues a quietCmd command (using SndDoCommand) and waits until quietCmd is received by the channel before disposing of the channel. In this case, the SndDisposeChannel function does not return until the channel has finished processing commands and the queue is empty.

WARNING
If you dispose of a channel currently playing from disk, then your completion routine will still execute, but will receive a pointer to a sound channel that no longer exists. Thus, you should stop a play from disk before disposing of a channel. See "Managing an Asynchronous Play From Disk" on page 2-52 for more information on completion routines.
Although the SndDisposeChannel function always releases memory reserved for sound commands, SndDisposeChannel cannot release memory associated with a sound channel record if you have allocated that memory yourself. For example, if you use the MyCreateSndChannel function defined in Listing 2-1 to create a sound channel, you must dispose first of the sound channel and then of the memory occupied by the sound channel record, as illustrated in Listing 2-3.

Listing 2-3 Disposing of memory associated with a sound channel

FUNCTION MyDisposeSndChannel (sndChan: SndChannelPtr; quietNow: Boolean): 
                                 OSErr;
VAR
   myErr:   OSErr;
BEGIN
   myErr := SndDisposeChannel(sndChan, quietNow);  {dispose of channel}
   DisposePtr(Ptr(sndChan));                       {dispose of channel ptr}
   MyDisposeSndChannel := myErr;
END;
If you have played a sound resource through a channel, the SndDisposeChannel function does not free the memory taken by the resource. You must call the Resource Manager's ReleaseResource function to do so, or, if you have detached a resource from a resource file, you could free the memory by making the handle unlocked and purgeable. Note that if you play a sound resource asynchronously, you should not release the memory occupied by the resource until the sound finishes playing or the sound might not play properly. For information on releasing a sound resource after playing a sound asynchronously, see "Playing Sounds Asynchronously" on page 2-46.

IMPORTANT
In Sound Manager versions 3.0 and later, you can play sounds in any number of sound channels. In earlier Sound Manager versions, however, only one kind of sound can be played at one time. This results in several important restrictions on your application. In Sound Manager version 2 and earlier, you should create sound channels just before playing sounds. Once the sound is completed, you should dispose of the channel. If your application is switched out and does not release a sound channel, then other applications may be unable to open sound channels. In particular, the system alert sound might not be heard and the user might not be notified of important system occurrences. In general, while it is acceptable to issue a number of sound commands to the same sound channel, it's not a good idea to play more than one sampled sound on the same sound channel.

Manipulating a Sound That Is Playing

The Sound Manager provides a number of sound commands that you can use to change some of the characteristics of sounds that are currently playing. For example, you can alter the rate at which a sampled sound is played back, thereby lowering or increasing the pitch of the sound. You can also pause or stop a sound that is currently in progress. See "Pausing and Restarting Sound Channels" on page 2-29 for information on how to pause the processing of a sound channel.

You can use the getRateCmd command to determine the rate at which a sampled sound is currently playing. If SndDoImmediate returns noErr when you pass getRateCmd, the current sample rate of the channel is returned as a Fixed value in the location that is pointed to by param2 of the sound command. (As usual, the high bit of that value returned is not interpreted as a sign bit.) Values that specify sampling rates are always interpreted relative to the 22 kHz rate. That is, the Fixed value $00010000 indicates a rate of 22 kHz. The value $00020000 indicates a rate of 44 kHz. The value $00008000 indicates a rate of 11 kHz.

To modify the pitch of a sampled sound currently playing, use the rateCmd command. The current pitch is set to the rate specified in the param2 field of the sound command. Listing 2-4 illustrates how to halve the frequency of a sampled sound that is already playing. Note that sending the rateCmd command before a sound plays has no effect.

Listing 2-4 Halving the frequency of a sampled sound

FUNCTION MyHalveFreq (mySndChan: SndChannelPtr): OSErr;
VAR
   myRate:     LongInt;          {rate of sound play}
   mySndCmd:   SndCommand;       {a sound command}
   myErr:      OSErr;
BEGIN
   {Get the rate of the sample currently playing.}
   mySndCmd.cmd := getRateCmd;   {the command is getRateCmd}
   mySndCmd.param1 := 0;         {unused}
   mySndCmd.param2 := LongInt(@myRate);
   myErr := SndDoImmediate(mySndChan, mySndCmd);

   IF myErr = noErr THEN
   BEGIN
      {Halve the sample rate.}
      mySndCmd.cmd := rateCmd;   {the command is rateCmd}
      mySndCmd.param1 := 0;      {unused}
      mySndCmd.param2 := FixDiv(myRate, $00020000);
      myErr := SndDoImmediate(mySndChan, mySndCmd);
   END;
   MyHalveFreq := myErr;
END;
When you halve the frequency of a sampled sound using the technique in Listing 2-4, the sound will play one octave lower than before. In addition, the sound will play twice as slowly as before. Likewise, if you use the rateCmd command to double the frequency of a sound, it plays one octave higher and twice as fast. Using rateCmd in this way is like pressing the fast forward button on a tape player while the play button remains depressed.

You can also use rateCmd and getRateCmd to pause a sampled sound that is currently playing. To do this, read the rate at which it is playing, issue a rateCmd command with a rate of 0, and then issue a rateCmd command with the previous rate when you want the sound to resume playing.

To change the amplitude (or loudness) of the sound in progress, issue the ampCmd command. (See Listing 2-5 for an example.) If no sound is currently playing, ampCmd sets the amplitude of the next sound. Specify the desired new amplitude in the param1 field of the sound command as a value in the range 0 to 255.

Listing 2-5 Changing the amplitude of a sound channel

PROCEDURE MySetAmplitude (chan: SndChannelPtr; myAmp: Integer);
VAR
   mySndCmd:   SndCommand;    {a sound command}
   myErr:      OSErr;
BEGIN
   IF chan <> NIL THEN
   BEGIN
      WITH mySndCmd DO
      BEGIN
         cmd := ampCmd;       {the command is ampCmd}
         param1 := myAmp;     {desired amplitude}
         param2 := 0;         {ignored}
      END;
      myErr := SndDoImmediate(chan, mySndCmd);
      IF myErr <> noErr THEN
         DoError(myErr);
   END;
END;
If your application has an option that allows users to turn off sound output, you could call the MySetAmplitude procedure on all open channels to set the amplitude of all channels to 0. Note that the Sound control panel allows the user to adjust the sound from 0 (softest) to 7 (loudest). This value is independent of the values used for amplitudes of sounds playing in channels, and the Sound Manager uses the Sound control panel value jointly with the amplitude of a sound channel to determine how loudly to play a sound. Sounds with low frequencies sound softer than sounds with high frequencies even if the sounds play at the same amplitude. If the amplitude of a sound is 0, the sound hardware produces no sound; however, when the value set in the Sound control panel is 0, sound might still play, depending on the amplitude.

You can use the getAmpCmd command to determine the current amplitude of a sound in progress. The getAmpCmd command is similar to getRateCmd, except that the value returned is an integer. The value returned in param2 is in the range 0-255. Listing 2-6 shows an example:

Listing 2-6 Getting the amplitude of a sound in progress

VAR
   myAmp:   Integer;
BEGIN
   mySndCmd.cmd := getAmpCmd;
   mySndCmd.param1 := 0;                     {unused}
   mySndCmd.param2 := LongInt(@myAmp);
   myErr := SndDoImmediate(mySndChan, mySndCmd);
END;
To modify the timbre of a sound defined using by square-wave data, use the timbreCmd command. A sine wave is specified as 0 in param1 and produces a very clear sound. A value of 254 in param1 represents a modified square wave and produces a buzzing sound. To avoid a bug in some versions of the Sound Manager, you should not use the value 255. You should change the timbre before playing the sound.

Stopping Sound Channels

The Sound Manager allows you both to stop a sound currently in progress in a channel and to remove all pending sound commands from a channel.

Note
If you have started a sound playing by using the SndStartFilePlay function, then you can stop play by using the SndStopFilePlay function. See "Managing an Asynchronous Play From Disk" on page 2-52 for more details.
To cause the Sound Manager to stop playing the sound in progress, send the quietCmd command. Here's an example:

mySndCmd.cmd := quietCmd;              {the command is quietCmd}
mySndCmd.param1 := 0;                  {unused}
mySndCmd.param2 := 0;                  {unused}

{stop the sound now playing}
myErr := SndDoImmediate(mySndChan, mySndCmd, FALSE);
To bypass the command queue, you should issue quietCmd by using SndDoImmediate. Any sound commands that are already in the sound channel remain there, however, and further sound commands can be queued in that channel.

If you wish to flush a sound channel without disturbing any sounds already in progress, issue the flushCmd command. Here's an example:

mySndCmd.cmd := flushCmd;              {the command is flushCmd}
mySndCmd.param1 := 0;                  {unused}
mySndCmd.param2 := 0;                  {unused}

{flush the channel}
myErr := SndDoImmediate(mySndChan, mySndCmd, FALSE);
If you want to stop all sound production by a particular sound channel immediately, you should issue a flushCmd command and then a quietCmd command. If you issue only a flushCmd command, the sound currently playing is not stopped. If you issue only a quietCmd command, the Sound Manager stops the current sound but continues with any other queued commands. (By calling flushCmd before quietCmd, you ensure that no other queued commands are processed.)

Note
The Sound Manager sends a quietCmd command when your application calls the SndDisposeChannel function. The quietCmd command is preceded by a flushCmd command if the quietNow parameter is TRUE.

Pausing and Restarting Sound Channels

If you want to pause command processing in a particular channel, you can use either of two sound commands, waitCmd or pauseCmd.

Note
If you have started a sound playing by using the SndStartFilePlay function, then you can pause and resume play by using the SndPauseFilePlay function. See "Managing an Asynchronous Play From Disk" on page 2-52 for more details.
The waitCmd command suspends all processing in a channel for a specified number of half-milliseconds. Here's an example:

mySndCmd.cmd := waitCmd;               {the command is waitCmd}
mySndCmd.param1 := 2000;               {1-second wait duration}
mySndCmd.param2 := 0;                  {unused}

{pause the channel}
myErr := SndDoImmediate(mySndChan, mySndCmd, FALSE);
To pause the processing of commands in a sound channel for an unspecified duration, use the pauseCmd command. Unlike waitCmd, pauseCmd suspends processing for an undetermined amount of time. Processing does not resume until the Sound Manager receives a resumeCmd command for the specified channel.

To issue waitCmd or pauseCmd, you can use either SndDoImmediate or SndDoCommand, depending on whether you want the suspension of sound channel processing to begin immediately or when the Sound Manager reaches that command in the normal course of reading commands from a sound channel. The resumeCmd command, which is simply the opposite of pauseCmd, should be issued by using SndDoImmediate. Neither waitCmd nor pauseCmd stops any sound that is currently playing; these commands simply stop further processing of commands queued in the sound channel.

Note
If no other commands are pending in the sound channel after a resumeCmd command, the Sound Manager sends an emptyCmd command. The emptyCmd command is sent only by the Sound Manager and should not be issued by your application.

Synchronizing Sound Channels

You can synchronize several different sound channels by issuing syncCmd commands. The param1 field of the sound command contains a count, and the param2 field contains an arbitrary identifier. The Sound Manager keeps track of the count for each channel being synchronized. When the Sound Manager receives a syncCmd command for a certain channel, it decrements the count for each channel having the given identifier, including the newly synchronized channel. Command processing resumes on a channel when the count becomes 0. Thus, if you know how many channels you need to synchronize, you can synchronize them all by arranging for all of their counts to become zero simultaneously. Listing 2-7 illustrates the use of the syncCmd command.

Listing 2-7 Adding a channel to a group of channels to be synchronized

PROCEDURE MySync1Chan (chan: SndChannelPtr; count: Integer;
                        identifier: LongInt);
VAR
   mySndCmd:   SndCommand;       {a sound command}
   myErr:      OSErr;
BEGIN
   WITH mySndCmd DO
   BEGIN
      cmd := syncCmd;            {the command is syncCmd}
      param1 := count;
      param2 := identifier;      {ID of group to be synchronized}
   END;
   myErr := SndDoImmediate(chan, mySndCmd);
   IF myErr <> noErr THEN
      DoError(myErr);
END;
For example, to synchronize three channels, first create the channels and then call the MySync1Chan procedure defined in Listing 2-7 for the first channel with a count equal to 4, for the second channel with a count equal to 3, and for the third channel with a count equal to 2, using the same arbitrary identifier for each call to MySync1Chan. Then fill all channels with appropriate sound commands. (For example, you might send commands that will cause the same sequence of notes to be produced on all three synchronized channels.) Finally, call the MySync1Chan procedure one final time, passing any of the three channels and a count of 1. By that time, all of the other channels will have counts of 1, and all counts will become 0 simultaneously, thus initiating synchronized play.

Note
The syncCmd command is intended to make it easy to synchronize sound channels. You can use the syncCmd command to start multiple channels of sampled sound playing simultaneously, but if you require precise synchronization of sampled-sound channels, you might achieve better results with the Time Manager, which is described in Inside Macintosh: Processes.

Previous Book Contents Book Index Next

© Apple Computer, Inc.
2 JUL 1996