December 95 - Sound Secrets
Sound Secrets
Kip Olson
The Sound Manager is one powerful multimedia tool for the Macintosh, but no one has
ever accused it of being too obvious. This article explores some of the more subtle
Sound Manager features, showing some simple ways to improve your application's use
of sound. A sample application demonstrates features such as volume overdrive and
easy continuous sound.
The Sound Manager has a long and distinguished career on the Macintosh. First released
in 1987, it was completely revised in 1993 with the release of Sound Manager 3.0.
The introduction of Sound Manager 3.1 in the summer of 1995 brought native
PowerPC performance, making the Sound Manager one of the most powerful
multimedia tools around. However, getting the most out of the Sound Manager often
means wading through many pages of Inside Macintosh: Sound.
This article pulls together valuable information about the Sound Manager, focusing on
some of its little-known features that will ease your development of multimedia
applications. The tips and techniques come straight from the Sound Manager
development team at Apple and cover diverse areas of developer interest, including
• parsing sound resources
• displaying compression names
• maximizing performance
• adjusting volume
• controlling pitch
• playing continuous sounds
• compressing audio
Two of these topics, controlling pitch and compressing audio, require the use of Sound
Manager 3.1, which is included on this issue's CD. You'll also find the SoundSecrets
application and its source code on the CD. SoundSecrets demonstrates many of the
techniques described in the article. To get the most out of this article, you should be
familiar with the Sound Manager command interface and concepts such as sound
channels, as described in Inside Macintosh: Sound.
So, let's get started unlocking some of those sound secrets!
FIND WHAT YOU'RE LOOKING FOR
On the Macintosh, sounds can be stored in a variety of formats, including 'snd '
resources, AIFF (Audio Interchange File Format) files, and QuickTime movies.
Applications often need to read these files directly and extract their sound data, which
can be a daunting task, especially when you begin to deal with some of the new
compressed sound formats introduced in Sound Manager 3.1 -- for example, IMA 4:1.
Fortunately, Sound Manager 3.0 introduced a couple of routines to help you navigate
these tricky waters -- GetSoundHeaderOffset and GetCompressionInfo. Let's take a look
at these routines, and put them to work with an example of parsing an 'snd ' resource
taken from the SoundSecrets application.
The 'snd ' resource format is described fully in Inside Macintosh: Sound, so we won't go
into detail here, except to say that embedded in the resource is a sound header and the
audio samples themselves. Finding this embedded sound header is the job of
GetSoundHeaderOffset. It takes a handle to an arbitrary 'snd ' resource and returns the
offset of the sound header data structure within that handle.
However, once you find the sound header, your work is not complete; you must
determine which of the three possible sound header structures it is. In the
SoundSecrets application, the sound header is represented as a union of the three
structures SoundHeader, ExtSoundHeader, and CmpSoundHeader. The encodefield in
these structures determines which union member to use when examining the header.
After you've extracted the appropriate information from the sound header, you can use
the GetCompressionInfo routine to determine the sound format and the compression
settings. GetCompressionInfo fills out and returns a CompressionInfo record, which
contains the OSType format of the sound, samples per packet, bytes per packet, and
bytes per sample. You can use these fields to convert between samples, frames, and
bytes.
For a thorough discussionof GetCompressionInfo, see the Macintosh
Technical Note "GetCompressionInfo()" (SD 1).*
As shown in Listing 1, the SoundSecrets application uses GetSoundHeaderOffset to find
the sound header structure, and then uses a case statement based on theencode field to
extract the useful information from each type of header. The SoundSecrets application
calculates the number of samples in the sound using information returned by
GetCompressionInfo.
Listing 1. Getting information from the sound header
typedef union {
SoundHeader s; // Plain sound header
CmpSoundHeader c; // Compressed sound header
ExtSoundHeader e; // Extended sound header
} CommonSoundHeader, *CommonSoundHeaderPtr;
OSErr ParseSnd(Handle sndH, SoundComponentData *sndInfo,
CompressionInfo *compInfo, unsigned long *headerOffsetResult,
unsigned long *dataOffsetResult)
{
CommonSoundHeaderPtr sh;
unsigned long headerOffset, dataOffset;
short compressionID;
OSErr err;
// Use GetSoundHeaderOffset to find the offset of the sound header
// from the beginning of the sound resource handle.
err = GetSoundHeaderOffset((SndListHandle) sndH,
(long *) &headerOffset);
if (err != noErr)
return (err);

// Get pointer to the sound header using this offset.
sh = (CommonSoundHeaderPtr) (*sndH + headerOffset);
dataOffset = headerOffset;
// Extract the sound information based on encode type.
switch (sh->s.encode) {
case stdSH: // Standard sound header
sndInfo->sampleCount = sh->s.length;
sndInfo->sampleRate = sh->s.sampleRate;
sndInfo->sampleSize = 8;
sndInfo->numChannels = 1;
dataOffset += offsetof(SoundHeader, sampleArea);
compressionID = notCompressed;
break;
case extSH: // Extended sound header
sndInfo->sampleCount = sh->e.numFrames;
sndInfo->sampleRate = sh->e.sampleRate;
sndInfo->sampleSize = sh->e.sampleSize;
sndInfo->numChannels = sh->e.numChannels;
dataOffset += offsetof(ExtSoundHeader, sampleArea);
compressionID = notCompressed;
break;
case cmpSH: // Compressed sound header
sndInfo->sampleCount = sh->c.numFrames;
sndInfo->sampleRate = sh->c.sampleRate;
sndInfo->sampleSize = sh->c.sampleSize;
sndInfo->numChannels = sh->c.numChannels;
dataOffset += offsetof(CmpSoundHeader, sampleArea);
compressionID = sh->c.compressionID;
sndInfo->format = sh->c.format;
break;
default:
return (badFormat);
break;
}
// Use GetCompressionInfo to get the data format of the sound and
// the compression information.
compInfo->recordSize = sizeof(CompressionInfo);
err = GetCompressionInfo(compressionID, sndInfo->format,
sndInfo->numChannels, sndInfo->sampleSize, compInfo);
if (err != noErr)
return (err);
// Store the sound data format and convert frames to samples.
sndInfo->format = compInfo->format;
sndInfo->sampleCount *= compInfo->samplesPerPacket;
// Return offset of header and audio data.
*headerOffsetResult = headerOffset;
*dataOffsetResult = dataOffset;
return (noErr);
}
CHOOSE THE RIGHT NAME
Now that you've extracted the sound settings from an 'snd ' resource, the next thing
you'll want to do is display this information to the user of your application. Settings
like sample rate and sample size are easy to display, but what if the sound is
compressed? All you've got is an OSType to describe the compressed sound data format,
and not too many users are going to get much out of seeing something like 'MAC3'
displayed on their screen.
Fortunately, the Sound Manager makes it easy for you to find a string to display that
does make sense. Using the Component Manager, you can look up the name of the audio
codec used to expand the compressed sound, and use this name to describe the
compression format to the user.
This is done with the Component Manager routine FindNextComponent, which is passed
a ComponentDescription record. By setting the componentType field of this record to
kSoundDecompressor, the componentSubType field to the OSType of the compressed
sound data format, and the remaining fields to 0, you can search for the sound
component that will decompress the sound. Once you have the component, you can use
GetComponentInfo to obtain the component name, which is the descriptive string that
makes sense to the user. The routine from SoundSecrets shown in Listing 2 finds the
name of any compressed sound format.
Listing 2. Finding the name of a compressed sound format
OSErr GetCompressionName(OSType compressionType,
Str255 compressionName)
{
ComponentDescription cd;
Component component;
Handle componentName;
OSErr err;
// Look for decompressor component.
cd.componentType = kSoundDecompressor;
cd.componentSubType = compressionType;
cd.componentManufacturer = 0;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
component = FindNextComponent(nil, &cd);
if (component == nil) {
err = siInvalidCompression;
goto FindComponentFailed;
}
// Create handle for name.
componentName = NewHandle(0);
if (componentName == nil) {
err = MemError();
goto NewNameFailed;
}
// Get name from the Component Manager.
err = GetComponentInfo(component, &cd, componentName, nil, nil);