August 92 - THE ASYNCHRONOUS SOUND HELPER
THE ASYNCHRONOUS SOUND HELPER
BRYAN K. ("BEAKER") RESSLER
In system software version 6.0.7 and later, the Sound Manager has impressive sound
input and output capabilities that are largely untapped by the existing body of
application software. This article presents a code module called the Asynchronous
Sound Helper that's designed to make asynchronous sound input and output easily
accessible to the application programmer, yet provide an interface flexible enough to
facilitate extensive application features.
Of all the Managers inInside Macintosh, the Sound Manager may be the winner of the
Most Startling Metamorphosis contest. On the earliest Macintosh computers, sound was
produced by direct calls to a Sound Driver, as described inInside Macintosh Volume II.
Later, with the advent of system software version 4.1 and the more powerful
sound-generation hardware of the Macintosh SE and Macintosh II, the Sound Driver
was superseded by a fairly buggy initial implementation of the Sound Manager, which
was first documented inInside Macintosh Volume V. The new Sound Manager presented a
problem for developers, because there was a large installed base of Macintosh 128K,
512K, 512K enhanced, and Plus computers that didn't have the ROMs or system
software to support the Sound Manager. At this point, all but the heartiest developers
decided the tradeoffs for including sound in a non-sound-related application were too
severe.
By the time version 6.0.7 rolled around, many of the details of the Sound Manager had
changed, and sound input support was added. In fact, the Sound Manager in 6.0.7 and
System 7 is relatively stable. So if you've been waiting for the right moment to add
sound support to your application, the moment has arrived.
The Macintosh Sound Manager acts as a buffer between your application and the
complexities of the sound hardware (see Figure 1). Sounds are produced by
sendingsound commands to a sound channel . The sound channel sends the commands
through asynthesizer that knows how to control the audio hardware. Among the Sound
Manager's current 38 commands are operations such as playing simple frequencies,
playing complex recorded sounds, and changing sound volume. The Sound Manager also
allows you to record new sounds if the appropriate hardware is available. Recording is
performed through a sound input driver.
Figure 1The Sound Manager
Sound playback and recording through the Sound Manager can be
performedsynchronously or asynchronously . When you make a synchronous call to the
Sound Manager, the function doesn't return control to your application until the entire
operation (sound playback, for instance) is complete. In general, it's easy to use the
Sound Manager to play or record sound synchronously. Asynchronous calls return
control immediately to your application and perform their operations in the
background, which makes asynchronous operations somewhat trickier. Many
developers feel that there are too many details to make asynchronous sound worthwhile
in an application not specifically oriented toward sound. However, with sound input
devices becoming more common, the market impetus to add sound is growing.
This article presents the Asynchronous Sound Helper, a code module designed to take
much of the heartburn out of asynchronous sound input and output. The goals of Helper,
as we'll be calling it from now on, are threefold:
• Provide a straightforward and uncomplicated interface for asynchronous
sound I/O specifically tailored toward common application requirements.
• Encourage developers to include support for sound as a standard type of
data, just like text or graphics.
• Function as a tutorial on how to perform asynchronous sound input and
output using the Sound Manager.
Helper provides two-tiered support--"easy" calls for basic operations and
"advanced" calls for more complex operations. You choose which calls to use depending
on your application's specific needs and user interface. For simple asynchronous
recording and playback, only a few routines are required. Or go all out and use Helper
routines to easily provide a "sound palette" with tape-deck-like controls for your
application.
To top it off, the overhead for Helper is fairly small. The code compiles to about 4K,
and it adds 86 bytes of global data to your application. At run time, it uses around 4K
in your application's heap. Helper uses clean Sound Manager techniques--nothing
skanky that might cause compatibility problems in the future.
HOOKING UP WITH HELPER
First let's take a quick look at how Helper works and how your application uses it.
We'll leave the details for later.
Before you can use Helper you need to add a global Boolean flag to your application
--the attention flag. At initialization time, your application calls Helper's
initialization routine and provides theaddress of the attention flag. In its main event
loop, your application checks the value of the attention flag and, if true, calls Helper's
idle routine.
Because Helper's main function is to spawn asynchronous sound tasks, communication
between your application and Helper is carried out on an as-needed basis. Here are the
basic phases of communication for a typical sound playback sequence (the numbers
correspond to Figure 2).
1. Your application tells Helper to play some sound.
2. Helper uses the Sound Manager to allocate a sound channel and begins
asynchronous playback of your sound.
3. The application goes on its merry way, with the sound playing
asynchronously in the background.
4. The sound completes playback. Helper has set up a sound command that
causes Helper to be informed immediately upon completion of playback (this
occurs at interrupt time). At that time, Helper sets the application's global
attention flag.
5. The next time through your application's event loop, the application
notices that the attention flag is set and calls SHIdle to free up the sound
channel.
Figure 2 Application-Sound Manager Interface
When your application terminates, it calls Helper's kill routine. Helper's method of
communication with the application minimizes processing overhead. By using the
attention flag scheme, your application calls Helper's idle routine only when it's
really necessary. This could be important in game and multimedia applications where
CPU bandwidth is pushed to the limit.
HELPER'S INTERFACE
Now let's take a look at the interfaces and the basic uses of the routines provided by
Helper. Later we'll go into more detail about how the routines work and how to use
them.
INITIALIZATION, IDLE, AND TERMINATION
pascal OSErr SHInitSoundHelper(Boolean *attnFlag, short numChannels);
pascal void SHIdle(void);
pascal void SHKillSoundHelper(void);
SHInitSoundHelper initializes Helper. It allocates memory, so you should call it near
the beginning of your application. The application passes to SHInitSoundHelper the
address of the Boolean attention flag that Helper uses to inform the application when it
needs attention.
SHIdle performs various cleanup tasks. Call SHIdle when the attention flag goes true.
At application termination, call SHKillSoundHelper. It stops current recording and
playback and deallocates Helper's memory.
EASY SOUND OUTPUT
pascal OSErr SHPlayByID(short resID, long *refNum);
pascal OSErr SHPlayByHandle(Handle sound, long *refNum);
pascal OSErr SHPlayStop(long refNum);
pascal OSErr SHPlayStopAll(void);
SHPlayByID and SHPlayByHandle provide an easy way to begin asynchronous sound
playback. These routines return a reference number via the refNum parameter. This
reference number can be used to stop playback and can be used with the advanced
routines described later. If you intend to simply trigger a sound that you want to run to
completion (like a gunshot sound in a game), you can pass nil for the refNum
parameter, thereby ignoring the reference number.
To stop a given sound or stop all playback, use SHPlayStop or SHPlayStopAll.
ADVANCED SOUND OUTPUT
pascal OSErr SHPlayPause(long refNum);
pascal OSErr SHPlayContinue(long refNum);
pascal SHPlayStat SHPlayStatus(long refNum);
pascal OSErr SHGetChannel(long refNum, SndChannelPtr *channel);
If you want more control over the playback process, these routines will be of interest.
SHPlayPause pauses the playback of a sound, like the pause button on a tape deck.
SHPlayContinue continues playback of a sound that was previously paused. Use
SHPlayStatus to find out the status of a sound-- finished, playing, or paused. If you
want to send commands directly to a sound channel that was allocated by Helper, use
SHGetChannel. You might want to send sound commands in your application, for
example, to play continuous looped background music.
EASY SOUND INPUT
pascal OSErr SHRecordStart(short maxK, OSType quality,
Boolean *doneFlag);
pascal OSErr SHGetRecordedSound(Handle *theSound);
pascal OSErr SHRecordStop(void);
These are the three basic routines for recording sound through a sound input device. To
begin asynchronous sound recording, use SHRecordStart. The application passes the
address of a Boolean--a recording-completed flag--that tells the application when the
recording is complete. Once complete, the application calls SHGetRecordedSound to
retrieve a sound handle. The handle is suitable for playback with SHPlayByHandle or to
be written out as a 'snd ' resource. To stop recording immediately (as with the stop
button on a tape recorder), use SHRecordStop.
ADVANCED SOUND INPUT
pascal OSErr SHRecordPause(void);
pascal OSErr SHRecordContinue(void);
pascal OSErr SHRecordStatus(SHRecordStatusRec *recordStatus);
To pause recording, use SHRecordPause. To continue previously paused recording, use
SHRecordContinue. Use SHRecordStatus to get information about the status of
recording. This status information includes the current input level (which could be
used to draw a tape-deck-likelevel meter), the amount of sound that's been recorded
(with respect to the maximum), and whether the recording is finished, recording, or
paused.
HELPER'S DATA STRUCTURES
Helper uses three internal data structures to keep track of recording and playback.
SndChannel channel; // Our sound channel.
long refNum; // Our Helper reference number.
Handle sound; // The sound we're playing.
Fixed rate; // Sampled sound playback rate.
char handleState;// The handle state for this handle.
Boolean inUse; // Tells whether this SHOutRec is in use.
Boolean paused; // Tells whether this sound is paused.
The SHOutputVars record contains an array of SHOutRec records. The numOutRecs field
tells how many are in the array. These records, one for each allocated channel, hold
information about currently playing sounds. They're reused when sounds have
completed. The SHOutputVars record also keeps track of the next available output
reference number, in the field nextRef. The reference numbers are unique (modulo
2,147,483,647).
long inRefNum; // Sound Manager's input device refNum.
SPB inPB; // The input parameter block.
Handle inHandle; // The handle we're recording into.
short headerLength; // The length of the sound's header.
Boolean recording; // Tells that we're actually recording.
Boolean recordComplete; // Tells that recording is complete.
OSErr recordErr; // Error, if error terminated recording.
short numChannels; // Number of channels for recording.
short sampleSize; // Sample size for recording.
Fixed sampleRate; // Sample rate for recording.
OSType compType; // Compression type for recording.
Boolean *appComplete; // Tells caller when recording is done.
Boolean paused; // Tells that recording has been paused.
An SHOutRec record's first field, channel, contains the actual Sound Manager
SndChannel used to play the sound. The sound reference number associated with this
sound (the one passed back to the application) is stored in the refNum field. A handle to
the sound we're playing is stored in the sound field. The rate field holds the sample
playback rate of sampled sounds, which is used when pausing sampled sounds. The
handleState field contains the original handle state (derived via a call to SHGetState),
so Helper can reset the handle's state after playback is complete. The inUse field tells
whether a given SHOutRec is in use by a playing sound (as opposed to available for
reuse). Finally, the paused flag lets Helper remember when a sound has been paused.
long inRefNum; // Sound Manager's input device refNum.
SPB inPB; // The input parameter block.
Handle inHandle; // The handle we're recording into.
short headerLength; // The length of the sound's header.