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 theSndNewChannel
function automatically allocates a sound channel record in the application's heap if passed a pointer to aNIL
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 toSndNewChannel
. 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;TheMyCreateSndChannel
function defined in Listing 2-1 first allocates memory for a sound channel record and then calls theSndNewChannel
function to attempt to allocate a channel. Note thatMyCreateSndChannel
checks the result code returned bySndNewChannel
to determine whether the function was able to allocate a channel. TheSndNewChannel
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 toSndNewChannel
, 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 constantstdQLength
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 theqLength
field to a considerably lower value, because resources created with theSndRecord
function (described in the chapter "Introduction to Sound on the Macintosh" in this book) contain only one sound command, thebufferCmd
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 setqLength
to 2, to allow for thebufferCmd
command and acallBackCmd
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.
The second parameter in the
- 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.
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
- If your application plays only sampled-sound resources, then you need only ensure that none of the sound resources specifies that it contains sampled-sound data. Then, when you create a sound channel, pass
sampledSynth
as the second parameter toSndNewChannel
so that the Sound Manager interprets the data in the sound resources correctly. Do not use theSndPlay
routine.- If your application must be able to play sampled-sound resources as well as resources that contain square-wave or wave-table data, ensure that all sound resources that your application uses specify their data type. (Sound resources created with the Sound Input Manager automatically specify that they contain sampled-sound data.) Then, when creating a channel in which you plan to play a sound resource, pass 0 as the second parameter to
SndNewChannel
, and then use the channel to play no more than one sound resource.- If you do not wish to modify your application's sound resources, and your application plays only sampled-sound resources, then you can play sounds with low-level Sound Manager routines, a technique described in "Playing Sounds Using Low-Level Routines" on page 2-61.
sampledSynth
as the second parameter toSndNewChannel
.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 theSndNewChannel
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 passNIL
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 withSndNewChannel
, 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.
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:
- 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.
myErr := SndNewChannel(mySndChan, sampledSynth, initStereo+initNoInterp, NIL);A call toSndNewChannel
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 anotEnoughHardwareErr
error. In general, you should pass 0 as the third parameter toSndNewChannel
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);ThereInitCmd
command accepts theinitNoInterp
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 areInitCmd
command with this constant, linear interpolation begins immediately. You can also passinitMono
,initChanLeft
, orinitChanRight
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 withSndNewChannel
, use theSndDisposeChannel
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 isTRUE
, the Sound Manager sends both aflushCmd
command and aquietCmd
command to the sound channel (usingSndDoImmediate
). 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 aquietCmd
command (usingSndDoCommand
) and waits untilquietCmd
is received by the channel before disposing of the channel. In this case, theSndDisposeChannel
function does not return until the channel has finished processing commands and the queue is empty.
Although the
- 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.
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 theMyCreateSndChannel
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, theSndDisposeChannel
function does not free the memory taken by the resource. You must call the Resource Manager'sReleaseResource
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. IfSndDoImmediate
returnsnoErr
when you passgetRateCmd
, the current sample rate of the channel is returned as aFixed
value in the location that is pointed to byparam2
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, theFixed
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 theparam2
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 therateCmd
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 therateCmd
command to double the frequency of a sound, it plays one octave higher and twice as fast. UsingrateCmd
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
andgetRateCmd
to pause a sampled sound that is currently playing. To do this, read the rate at which it is playing, issue arateCmd
command with a rate of 0, and then issue arateCmd
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 theparam1
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 theMySetAmplitude
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. ThegetAmpCmd
command is similar togetRateCmd
, except that the value returned is an integer. The value returned inparam2
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 thetimbreCmd
command. A sine wave is specified as 0 inparam1
and produces a very clear sound. A value of 254 inparam1
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.
To cause the Sound Manager to stop playing the sound in progress, send the
- Note
- If you have started a sound playing by using the
SndStartFilePlay
function, then you can stop play by using theSndStopFilePlay
function. See "Managing an Asynchronous Play From Disk" on page 2-52 for more details.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 issuequietCmd
by usingSndDoImmediate
. 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 aflushCmd
command and then aquietCmd
command. If you issue only aflushCmd
command, the sound currently playing is not stopped. If you issue only aquietCmd
command, the Sound Manager stops the current sound but continues with any other queued commands. (By callingflushCmd
beforequietCmd
, you ensure that no other queued commands are processed.)
- Note
- The Sound Manager sends a
quietCmd
command when your application calls theSndDisposeChannel
function. ThequietCmd
command is preceded by aflushCmd
command if thequietNow
parameter isTRUE
.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
orpauseCmd
.
The
- Note
- If you have started a sound playing by using the
SndStartFilePlay
function, then you can pause and resume play by using theSndPauseFilePlay
function. See "Managing an Asynchronous Play From Disk" on page 2-52 for more details.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 thepauseCmd
command. UnlikewaitCmd
,pauseCmd
suspends processing for an undetermined amount of time. Processing does not resume until the Sound Manager receives aresumeCmd
command for the specified channel.To issue
waitCmd
orpauseCmd
, you can use eitherSndDoImmediate
orSndDoCommand
, 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. TheresumeCmd
command, which is simply the opposite ofpauseCmd
, should be issued by usingSndDoImmediate
. NeitherwaitCmd
norpauseCmd
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 anemptyCmd
command. TheemptyCmd
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 issuingsyncCmd
commands. Theparam1
field of the sound command contains a count, and theparam2
field contains an arbitrary identifier. The Sound Manager keeps track of the count for each channel being synchronized. When the Sound Manager receives asyncCmd
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 thesyncCmd
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 theMySync1Chan
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 toMySync1Chan
. 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 theMySync1Chan
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 thesyncCmd
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.