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.
Parsing Sound Resources and Sound Files
This section explains how you can parse sound resources and sound files to find the component of a sound resource or sound file that contains information about the sound. For sound resources, this information is stored in the sound header. In addition to obtaining information about a sound from a sound header, you might need a pointer to a sound header to use any of several low-level sound commands. For sound files, information is stored in the Form and Common Chunks. This section shows how you can find those chunks and extract information from them.
- Note
- The techniques shown in this section assume that you are familiar with the format of sound resources and sound files. See "Sound Storage Formats" beginning on page 2-73 for complete information on sound storage formats.
Obtaining a Pointer to a Sound Header
This section shows how you can obtain a pointer to a sound header stored in a sound resource. You can use this pointer to obtain information about the sound. You also need a pointer to a sound header to install a sampled sound as a voice in a channel (as described in "Installing Voices Into Channels" on page 2-43) and to play sounds using low-level sound commands (as described below and in the next section). You can use a technique similar to the one described in this section if you wish to obtain a pointer to wave-table data that is stored in a sound resource.Sound Manager versions 3.0 and later include the
GetSoundHeaderOffset
function that you can use to locate a sound header embedded in a sound resource. Listing 2-27 shows how to call theGetSoundHeaderOffset
function and then pass the returned offset to thebufferCmd
sound command, to play a sampled sound using low-level Sound Manager routines.Listing 2-27 Playing a sound resource
FUNCTION MyPlaySampledSound (chan: SndChannelPtr; sndHandle: Handle): OSErr; VAR myOffset: LongInt; mySndCmd: SndCommand; {a sound command} myErr: OSErr; BEGIN myErr := GetSoundHeaderOffset(sndHandle, myOffset); IF myErr = noErr THEN BEGIN HLock(sndHandle); mySndCmd.cmd := bufferCmd; {command is bufferCmd} mySndCmd.param1 := 0; {unused with bufferCmd} mySndCmd.param2 := LongInt(ORD4(sndHandle^) + myOffset); myErr := SndDoImmediate(chan, mySndCmd); END; MyPlaySampledSound := myErr; END;If theGetSoundHeaderOffset
function is not available but you still need to obtain a pointer to a sound header, you can use the functionMyGetSoundHeaderOffset
defined in Listing 2-28. The function defined there traverses a sound resource until it reaches the sound data. It returns, in theoffset
parameter, the offset in bytes from the beginning of a sound resource to the sound header.
Listing 2-28 Obtaining the offset in bytes to a sound header
- IMPORTANT
- The
GetSoundHeaderOffset
function is available in Sound Manager versions 3.0 and later. As a result, you'll need to use the techniques illustrated in Listing 2-28 only if you want your application to find a sound header when earlier versions of the Sound Manager are available.
FUNCTION MyGetSoundHeaderOffset (sndHdl: Handle; VAR offset: LongInt): OSErr; TYPE Snd1Header = {format 1 'snd ' resource header} RECORD format: Integer; {format of resource} numSynths: Integer; {number of data types} {synths, init option follow} END; Snd1HdrPtr = ^Snd1Header; Snd2Header = {format 2 'snd ' resource header} RECORD format: Integer; {format of resource} refCount: Integer; {for application use} END; Snd2HdrPtr = ^Snd2Header; IntPtr = ^Integer; {for type coercion} SndCmdPtr = ^SndCommand; {for type coercion} VAR myPtr: Ptr; {to navigate resource} myOffset: LongInt; {offset into resource} numSynths: Integer; {info about resource} numCmds: Integer; {info about resource} isDone: Boolean; {are we done yet?} myErr: OSErr; BEGIN {Initialize variables.} myOffset := 0; {return 0 if no sound header found} myPtr := Ptr(sndHdl^); {point to start of resource data} isDone := FALSE; {haven't yet found sound header} myErr := noErr; {Skip everything before sound commands.} CASE Snd1HdrPtr(myPtr)^.format OF firstSoundFormat: {format 1 'snd ' resource} BEGIN {skip header start, synth ID, etc.} numSynths := Snd1HdrPtr(myPtr)^.numSynths; myPtr := Ptr(ORD4(myPtr) + SizeOf(Snd1Header)); myPtr := Ptr(ORD4(myPtr) + numSynths * (SizeOf(Integer) + SizeOf(LongInt))); END; secondSoundFormat: {format 2 'snd ' resource} myPtr := Ptr(ORD4(myPtr) + SizeOf(Snd2Header)); OTHERWISE {unrecognized resource format} BEGIN myErr := badFormat; isDone := TRUE; END; END; {Find number of commands and move to start of first command.} numCmds := IntPtr(myPtr)^; myPtr := Ptr(ORD4(myPtr) + SizeOf(Integer)); {Search for bufferCmd or soundCmd to obtain sound header.} WHILE (numCmds >= 1) AND (NOT isDone) DO BEGIN IF (IntPtr(myPtr)^ = bufferCmd + dataOffsetFlag) OR (IntPtr(myPtr)^ = soundCmd + dataOffsetFlag) THEN BEGIN {bufferCmd or soundCmd found} {copy offset from sound command} myOffset := SndCmdPtr(myPtr)^.param2; isDone := TRUE; {get out of loop} END ELSE BEGIN {soundCmd or bufferCmd not found} {move to next command} myPtr := Ptr(ORD4(myPtr) + SizeOf(SndCommand)); numCmds := numCmds - 1; END; END; {WHILE} offset := myOffset; {return offset} MyGetSoundHeaderOffset := myErr; {return result code} END;TheMyGetSoundHeaderOffset
function defined in Listing 2-28 begins by initializing several variables, including a pointer that it sets to point to the beginning of the data contained in the sound resource. Then, after determining whether the sound resource is format 1 or format 2, the function skips data contained in the format 1'snd '
resource header or in the format 2'snd '
resource header, as appropriate.
After skipping information in the sound resource header,
- Note
- Do not confuse the format 1 or format 2
'snd '
header with the sound header theMyGetSoundHeaderOffset
function defined in Listing 2-28 is designed to find. A sound header contains information about the sampled-sound data stored in a sound resource; a sound resource header contains information about the format of the sound resource.MyGetSoundHeaderOffset
simply looks through all sound commands in the resource for abufferCmd
orsoundCmd
command, either of which must contain the offset from the beginning of the resource to the sound header in itsparam2
field. If the given sound resource contains no sound header (and thus no sampled-sound data), theMyGetSoundHeaderOffset
function returns an error and sets theoffset
variable parameter to 0.After using the
MyGetSoundHeaderOffset
function to obtain an offset to the sound header, you can easily obtain a pointer to a sound header. Note, however, that because a handle to a sound resource is contained in a relocatable block, you must lock the relocatable block before you obtain a pointer to a sound header, and you must not unlock it until you are through using the pointer. Listing 2-29 demonstrates how you can convert an offset to a sound header into a pointer to a sound header after locking a relocatable block.Listing 2-29 Converting an offset to a sound header into a pointer to a sound header
FUNCTION MyGetSoundHeader (sndHandle: Handle): SoundHeaderPtr; VAR myOffset: LongInt; {offset to sound header} myErr: OSErr; BEGIN HLockHi(sndHandle); {lock data in high memory} {compute offset to sound header} myErr := MyGetSoundHeaderOffset(sndHandle, myOffset); IF myErr <> noErr THEN MyGetSoundHeader := NIL {no sound header in resource} ELSE {compute address of sound header} MyGetSoundHeader := SoundHeaderPtr(ORD4(sndHandle^) + myOffset); END;TheMyGetSoundHeader
function defined in Listing 2-29 locks the sound handle you pass it in high memory and then attempts to find an offset to the sound header in the sound handle. If theMyGetSoundHeaderOffset
function defined in Listing 2-28 returns an offset of 0, thenMyGetSoundHeader
returns aNIL
pointer to a sound header; otherwise, it returns a pointer that remains valid as long as you do not unlock the sound handle.The
MyGetSoundHeader
function returns a pointer to a sampled sound header even if the sound header is actually an extended sound header or a compressed sound header. Thus, before accessing any other fields of the sound header, you should test theencode
field of the sound header to determine what type of sound header it is. Then, if the sound header is, for example, an extended sound header, cast the sampled sound header to an extended sound header. Then you can access any of the fields of the extended sound header. For an example of this technique, see Listing 2-16 on page 2-44.Playing Sounds Using Low-Level Routines
Once you obtain a pointer to a sampled sound header, you can use thebufferCmd
sound command to play a sound without using the high-level Sound Manager routines. Many sampled-sound resources includebufferCmd
commands, so the high-level Sound Manager routines often issue thebufferCmd
command indirectly. Thus, you might in some cases be able to make your application slightly more efficient by issuing thebufferCmd
command directly. Also, you might issue abufferCmd
command directly if you want the Sound Manager to ignore other parts of a sound resource.Finally, you might issue
bufferCmd
commands directly if you want your application to be able to play a large sound resource without loading the entire resource at once. By issuing several successivebufferCmd
commands, you can play a large sound resource using a small buffer. In this case, each buffer must contain a sampled sound header. In most cases, the sound will play smoothly, without audible gaps. It's generally easier, however, to play large sampled sounds from disk by using the play-from-disk routines or theSndPlayDoubleBuffer
function. See "Managing Double Buffers" on page 2-147 for complete details.
The pointer in the
- Note
- Using the
bufferCmd
command to play several consecutive compressed samples on the Macintosh Plus, the Macintosh SE, or the Macintosh Classic is not guaranteed to work without an audible pause or click.param2
field of abufferCmd
command is the location of a sampled sound header. AbufferCmd
command is queued in the channel until the preceding commands have been processed. If thebufferCmd
command is contained within an'snd '
resource, the high bit of the command must be set. If the sound was loaded in from an'snd '
resource, your application is expected to unlock this resource and allow it to be purged after using it. Listing 2-30 shows how your application can play a sampled sound stored in a resource using thebufferCmd
command.Listing 2-30 Playing a sound using the
bufferCmd
command
FUNCTION MyLowLevelSampledSndPlay (chan: SndChannelPtr; sndHandle: Handle): OSErr; CONST kWaitIfFull = TRUE; {wait for room in queue?} VAR mySndHeader: SoundHeaderPtr; mySndCmd: SndCommand; {a sound command} BEGIN mySndHeader := MyGetSoundHeader(sndHandle); WITH mySndCmd DO BEGIN cmd := bufferCmd; {command is bufferCmd} param1 := 0; {unused with bufferCmd} param2 := LongInt(mySndHeader); {pointer to sound header} END; IF mySndHeader <> NIL THEN MyLowLevelSampledSndPlay := SndDoCommand(chan, mySndCmd, NOT kWaitIfFull) ELSE MyLowLevelSampledSndPlay := badFormat; END;For theMyLowLevelSampledSndPlay
function defined in Listing 2-30 to play a sound, the channel passed to it must already be configured to play sampled-sound data. Otherwise, the function returns abadChannel
result code. Also, because thebufferCmd
command works asynchronously, you might want to associate a callback procedure with the sound channel when you create the channel. For more information on playing sounds asynchronously, see "Playing Sounds Asynchronously" on page 2-46.You can use the
bufferCmd
command to handle compressed sound samples in addition to sounds that are not compressed. To expand and play back a buffer of compressed samples, you pass the Sound Manager abufferCmd
command whereparam2
points to a compressed sound header.To play sampled sounds that are not compressed, pass
bufferCmd
a standard or extended sound header. The extended sound header can be used for stereo sampled sounds. The standard sampled sound header is used for all other noncompressed sampled sounds.Finding a Chunk in a Sound File
Sound files are not as tightly structured as sound resources. As explained in "Sound Files" on page 2-81, the chunks in a sound file can appear in any order, except that the Form Chunk is always first. Most information about a sampled sound stored in a sound file is contained in the Common Chunk. Thus, to be able to access this information, you must be able to find a particular kind of chunk in a sound file. Listing 2-31 defines a procedure that you can use to find the location of the first chunk of a specified type beginning at the chunk you specify.
Listing 2-31 Finding a chunk in a sound file
- IMPORTANT
- The techniques illustrated in this section are provided primarily to help you understand the structure of sound files. Most sound-producing applications don't need to parse sound files.
FUNCTION MyFindChunk (myFile: Integer; {file reference number} myChunkSought: ID; {ID of chunk sought} startPos: LongInt; {file position to start at} VAR chunkFPos: LongInt) {file position of found chunk} : OSErr; VAR myLength: LongInt; {number of bytes to read} myChunkHeader: ChunkHeader; {characteristics of chunk} found: Boolean; {flag variable} myErr: OSErr; {error from File Manager calls} BEGIN found := FALSE; {initialize flag variable} {set file mark at start} myErr := SetFPos(myFile, fsFromStart, startPos); {Search file's chunks for desired chunk ID.} WHILE (NOT found) AND (myErr = noErr) DO BEGIN {check current chunk} myLength := SizeOf(myChunkHeader); {Load chunk header.} myErr := FSRead(myFile, myLength, @myChunkHeader); IF myErr = noErr THEN {chunk header loaded okay} IF myChunkHeader.ckID = myChunkSought THEN BEGIN found := TRUE; {chunk has been found} {find position in file} myErr := GetFPos(myFile, chunkFPos); {compute chunk's start position} chunkFPos := chunkFPos - SizeOf(myChunkHeader); END ELSE BEGIN {move to next chunk} IF myChunkHeader.ckID = ID(FormID) THEN {Adjust Form Chunk's size to size of formType field.} myChunkHeader.ckSize := SizeOf(ID); IF myChunkHeader.ckSize MOD 2 = 1 THEN {Compensate for pad byte.} myChunkHeader.ckSize := myChunkHeader.ckSize + 1; myErr := SetFPos(myFile, fsFromMark, myChunkHeader.ckSize); END; END; {WHILE} MyFindChunk := myErr; END;TheMyFindChunk
function defined in Listing 2-31 accepts four parameters. ThemyFile
parameter is the file reference number of an open sound file. (For information on file reference numbers, see Inside Macintosh: Files.) In themyChunkSought
parameter, you pass the ID of the type of chunk you wish to find. For example, you might passID(FormID)
to find the Form Chunk. The third parameter,startPos
, is the file position at whichMyFindChunk
should start searching for a chunk. This file position must be the beginning of a chunk. To start at the beginning of a file, specify 0. Finally, if theMyFindChunk
function is successful, it returns in thechunkFPos
parameter the file position of the first chunk of the specified type that it found. If the function is unsuccessful, it returns the appropriate File Manager result code (such as an end-of-file error) and thechunkFPos
parameter is undefined.The
MyFindChunk
function works by looking at each chunk of the sound file, beginning at the file positionstartPos
and checking to see if the chunk is of the type sought. If a chunk matches, theMyFindChunk
function returns the file position of the start of the chunk; otherwise, the function moves onto the next chunk. For each chunk, theMyFindChunk
function reads in the chunk header, checks for a match, and then moves to the next chunk.The
MyFindChunk
function moves from one chunk to the next by identifying the size of the current chunk, not including the chunk header, from theckSize
field of the chunk header. Whenever you parse sound files, you should always use theckSize
field of the chunk header to determine the size of a chunk if the size of the chunk could vary in size. TheMyFindChunk
function adjusts the value in theckSize
field before advancing to the next chunk in two cases. First, theckSize
field for the Form Chunk reflects the size of the entire sound file, so this function changes it to the size of theformType
field so that the function does not skip the file's local chunks. Second, if theckSize
field is odd, 1 byte is added because the number of bytes in a chunk is always even.After using the
MyFindChunk
function defined in Listing 2-31, you might still need to read the data contained in a chunk into memory. For example, you might read in the Form and Common Chunks to obtain information about a sound file. Listing 2-32 uses theMyFindChunk
function to find a chunk in a sound file, allocates an appropriately sized block of memory for that chunk, and reads the chunk into that block.Listing 2-32 Loading a chunk from a sound file
FUNCTION MyGetChunkData (myFile: Integer; {file reference number} myChunkSought: ID; {ID of chunk sought} startPos: LongInt): {file position to start at} Ptr; {pointer to data or NIL} VAR myFPos: LongInt; {position in file} myLength: LongInt; {number of bytes to read} myChunkHeader: ChunkHeader; {characteristics of a chunk} myChunkData: Ptr; {pointer to chunk data} myErr: OSErr; BEGIN myChunkData := NIL; {initialize variable} myErr := MyFindChunk(myFile, myChunkSought, startPos, myFPos); IF myErr = noErr THEN {move to start of chunk} myErr := SetFPos(myFile, fsFromStart, myFPos); IF myErr = noErr THEN BEGIN {determine how much data to copy} myLength := SizeOf(ChunkHeader); myErr := FSRead(myFile, myLength, @myChunkHeader); IF myChunkHeader.ckID = ID(FormID) THEN myChunkHeader.ckSize := SizeOf(ID); {don't return local chunks} myLength := myChunkHeader.ckSize + SizeOf(ChunkHeader); IF myErr = noErr THEN {return to chunk's start} myErr := SetFPos(myFile, fsFromStart, myFPos); END; IF myErr = noErr THEN BEGIN {read chunk data into RAM} myChunkData := NewPtr(myLength); IF myChunkData <> NIL THEN myErr := FSRead(myFile, myLength, myChunkData); END; IF myErr <> noErr THEN IF myChunkData <> NIL THEN DisposePtr(myChunkData); MyGetChunkData := myChunkData; END;TheMyGetChunkData
function defined in Listing 2-32 attempts to find a chunk in a file. If it finds the chunk, it reads the chunk header to determine the chunk's size, and if the chunk is the Form Chunk, adjusts the chunk size so that the sound file's local chunks are not included in the chunk size. Then the function attempts to allocate memory for the chunk and read the chunk into the memory. If a problem occurs at any time, the function simply returnsNIL
.
- Note
- The format of a sound file might not be the same as its operating-system type. In particular, a file might have an operating-system type
'AIFC'
but be formatted as an AIFF file because the sampled-sound data contained in the file is noncompressed.