|
This Technote is directed at
application developers who have code that calls
functions that are not in the Carbon Sound Manager
and who want to move that code over to Carbon.
[Jun 18 2002]
|
The loss of the APIs
These sound functions are not in Carbon:
SndControl
SndStartFilePlay
SndPauseFilePlay
SndStopFilePlay
SndPlayDoubleBuffer
MACEVersion
Comp3to1
Exp1to3
Comp6to1
Exp1to6
AudioGetVolume
AudioSetVolume
AudioGetMute
AudioSetMute
AudioSetToDefaults
AudioGetInfo
AudioGetBass
AudioSetBass
AudioGetTreble
AudioSetTreble
AudioGetOutputDevice
AudioMuteOnEvent
SndRecordToFile
SPBRecordToFile
These SndCommand numbers are not supported
by the Carbon Sound Manager:
initCmd
freeCmd
totalLoadCmd
loadCmd
freqDurationCmd
restCmd
freqCmd
ampCmd
timbreCmd
getAmpCmd
waveTableCmd
phaseCmd
rateCmd
continueCmd
doubleBufferCmd
getRateCmd
sizeCmd /*obsolete command*/
convertCmd /*obsolete MACE
command*/
If you have code that relies on any of the above calls or
SndCommands , you will have to rewrite it if you
want to make your application Carbon compatible. This Note
will describe how to duplicate the functionality of these
APIs with Carbon-compatible code.
Back to top
Why they were removed
The low-level calls were removed because there are better,
more compatible, ways of accomplishing what these calls
accomplished. In some cases, the calls had ceased to be
useful so their removal should not affect any modern code
base.
The high-level calls, SndStartFilePlay ,
SPBRecordToFile , etc., were removed because
their functionality is largely subsumed by QuickTime. Using
QuickTime in place of these routines should increase the
functionality of your program (for instance, you will be
able to play a more varied list of sound files) without
adding much work or code to your existing code base.
The SndCommands were removed because they
either operate on non-wave data which is no long supported,
or there are newer SndCommands that supercede
their functionality.
Back to top
What they did and how they can be replaced
This is a brief description of what these functions did
in the past and how they can be replaced in Carbon. For a
complete description, see Inside
Macintosh: Sound.
SndControl
- In Sound Manager 2.0 this returned information
about the sound hardware.
- This call was made obsolete by Sound Manager 3.0
and should be replaced by calls to
Gestalt .
SndStartFilePlay
- Starts a file playing from disk. This function is
basically a wrapper around
SndPlayDoubleBuffer .
- Can be mimicked with QuickTime.
SndPauseFilePlay
- Toggles the playing state of a file that is being
played by
SndStartFilePlay .
- When using QuickTime, use QuickTime's control
commands.
SndStopFilePlay
- Stops a sound that is being played by
SndStartFilePlay .
- When using QuickTime, use QuickTime's control
commands.
SndPlayDoubleBuffer
- Plays a sound by first playing one buffer of audio
and then a second.
- Can be mimicked with
bufferCmd and
callBackCmd , or to some extent,
QuickTime.
MACEVersion
- Gets the version of the MACE
compressor/decompressor.
- This call should never be needed. The current
version of MACE is 1.0.2 and it hasn't changed in
years.
Comp3to1
- Compresses a sound with MACE 3:1.
- Can be replaced with the appropriate calls to the
SoundConverter APIs.
Exp1to3
- Decompresses a sound compressed with MACE
3:1.
- Can be replaced with the appropriate calls to the
SoundConverter APIs.
Comp6to1
- Compresses a sound with MACE 6:1.
- Can be replaced with the appropriate calls to the
SoundConverter APIs.
Exp1to6
- Decompresses a sound compressed with MACE
6:1.
- Can be replaced with the appropriate calls to the
SoundConverter APIs.
AudioGetVolume
- Called by the Sound Manager to get an output
component's volume.
- Can be replaced with
SoundComponentGetInfo and the
siHardwareVolume selector.
AudioSetVolume
- Called by the Sound Manager to set an output
component's volume.
- Can be replaced with
SoundComponentSetInfo and the
siHardwareVolume selector.
AudioGetMute
- Called by the Sound Manager to get the mute state
of an output component.
- Can be replaced with
SoundComponentGetInfo and the
siHardwareMute selector.
AudioSetMute
- Called by the Sound Manager to set the mute state
of an output component.
- Can be replaced with
SoundComponentSetInfo and the
siHardwareMute selector.
AudioSetToDefaults
- Called by the Sound Manager to return an output
component to its defaults.
- No replacement.
AudioGetInfo
- Called by the Sound Manager to get information
about an output component.
- Can be replaced with calls to
SoundComponentGetInfo and the appropriate
selectors.
AudioGetBass
- Called by the Sound Manager to get an output
component's bass volume.
- Can be replaced with
SoundComponentGetInfo and the
siHardwareBass selector.
AudioSetBass
- Called by the Sound Manager to set an output
component's bass volume.
- Can be replaced with
SoundComponentSetInfo and the
siHardwareBass selector.
AudioGetTreble
- Called by the Sound Manager to get an output
component's treble volume.
- Can be replaced with
SoundComponentGetInfo and the
siHardwareTreble selector.
AudioSetTreble
- Called by the Sound Manager to set an output
component's treble volume.
- Can be replaced with
SoundComponentSetInfo and the
siHardwareTreble selector.
AudioGetOutputDevice
- Not documented.
- No replacement.
AudioMuteOnEvent
- Not documented.
- No replacement.
SndRecordToFile
- Records sound data to a file in a synchronous
operation.
- Can be mimicked with QuickTime using the Sequence
Grabber Component.
SPBRecordToFile
- Records sound data to a file, optionally doing it
asynchronously.
- Can be mimicked with appropriate calls
SPBRecord and PBWriteAsync
for the asynchronous case. QuickTime Sequence Grabber
Component can also be used to replace it.
initCmd
- Not documented.
- No replacement.
freeCmd
- Not documented.
- No replacement.
totalLoadCmd
- Sent using the obsolete
SndControl
function, it reported the total CPU load factor for
all existing sound activity.
- No replacement, but it was not accurate or useful
starting with Sound Manager 3.1.
loadCmd
- Sent using the obsolete
SndControl
function, it reported the percentage of CPU processing
power that the sound channel would require.
- No replacement, but it was not accurate or useful
starting with Sound Manager 3.1.
freqDurationCmd
- Play the note specified in
param2 for
the duration specified in param1 .
- Replace the pitch shift functionality with
rateMultiplierCmd .
freqCmd
- Change the frequency (or pitch) of a sound. Could
be used to loop a sampled-sound data sound installed
with a
soundCmd .
- Replace the pitch shift and duration functionality
with
rateMultiplierCmd . You can no longer
loop a sound. To loop a sound play it over and over
again using bufferCmd and
callBackCmd .
restCmd
- Rest a channel for a specified duration.
- No replacement as square wave data sound and wave
table data sound are not supported by the Carbon Sound
Manager.
ampCmd
- Change the amplitude (or loudness) of a
sound.
- Use
volumeCmd instead.
timbreCmd
- Change the timbre (or tone) of a sound currently
being defined using square-wave data.
- No replacement as square wave data sound and wave
table data sound are not supported by the Carbon Sound
Manager.
getAmpCmd
- Determine the current amplitude (or loudness) of a
sound.
- Use
volumeCmd instead.
waveTableCmd
- Install a wave table as a voice in the specified
channel.
- No replacement as wave table data sound are not
supported by the Carbon Sound Manager.
phaseCmd
- Not documented.
- No replacement.
rateCmd
- Set the rate of a sampled sound that is currently
playing, thus effectively altering its pitch and
duration.
- Use
rateMultiplierCmd instead.
continueCmd
- Not documented.
- No replacement.
doubleBufferCmd
- This is the command used by
SndPlayDoubleBuffer .
- This is the command used by
SndPlayDoubleBuffer . No direct
replacement. You can use bufferCmd and
callBackCmd to simulate, see Replacing
SndPlayDoubleBuffer below.
getRateCmd
- Determine the sample rate of the sampled sound
currently playing.
- Use
getRateMultiplierCmd
instead.
sizeCmd /*obsolete command*/
- Not documented.
- No replacement.
convertCmd /*obsolete MACE command*/
- Not documented.
- No replacement.
Back to top
Replacing SndPlayDoubleBuffer
Replacing SndPlayDoubleBuffer is a bit
tricky because of the subtle ways it does its work.
Replacing it a simple stream of bufferCmd s and
callBackCmd s does not do exactly the same thing
for code that was expecting the real
SndPlayDoubleBuffer .
This is because the real SndPlayDoubleBuffer
call is a single command in the sound channel's queue, a
doubleBufferCmd , but the replacement is two
commands. Furthermore, the doubleBufferCmd
stays in the channel's queue until the sound is done
playing, but the bufferCmd and
callBackCmd are constantly being added and
removed. This is an issue for any commands that might be
waiting in the queue after the doubleBufferCmd
and the code that relies on those commands being after the
doubleBufferCmd . This doesn't immediately kill
the prospect of simulating SndPlayDoubleBuffer ,
but it means that the work is a little more complicated for
the simulating code.
The first issue is that information about the currently
playing sound will need to be kept for each sound channel.
This information is kept by the Sound Manager when using the
real SndPlayDoubleBuffer .
// Structs
struct PerChanInfo {
QElemPtr qLink; /* next queue entry */
short qType; /* queue type = 0 */
short stopping;
#if DEBUG
OSType magic;
#endif
SndCallBackUPP usersCallBack;
SndDoubleBufferHeader theParams;
CmpSoundHeader soundHeader;
};
typedef struct PerChanInfo PerChanInfo;
typedef struct PerChanInfo * PerChanInfoPtr;
// Globals
Boolean gNMRecBusy;
NMRecPtr gNMRecPtr;
QHdrPtr gFreeList;
Ptr gSilenceTwos;
Ptr gSilenceOnes;
|
The queue structure is used to keep track of the
per-channel sound information, such as the format of the
sound, the parameters to SndPlayDoubleBuffer ,
the callback function that was originally in the sound
channel (before the simulating code users it for its own
use) and some housekeeping information. This queue structure
will allow us to enqueue the channel information using
PBEnqueue at interrupt time so that at task
time we can dispose of the per-channel structure and
associated memory.
The simulation has to set up its simple state machine and
the first buffer of sound has to be played. That code looks
like this:
// This function is only callable at system task time.
// Note: CarbonSndPlayDoubleBuffer calls NewPtrClear,
// which is only callable at system task time,
// this means that CarbonSndPlayDoubleBuffer itself
// is only callable at system task time.
OSErr CarbonSndPlayDoubleBuffer (SndChannelPtr chan,
SndDoubleBufferHeaderPtr theParams) {
OSErr err;
CompressionInfo compInfo;
PerChanInfoPtr perChanInfoPtr;
SndCommand playCmd;
SndCommand callBack;
if (chan == nil) {
err = badChannel;
goto exit;
}
if (theParams == nil) {
err = paramErr;
goto exit;
}
if (gFreeList == nil) {
// This can't ever be disposed since we don't know when
// we might need to use it (at interrupt time)
gFreeList = (QHdrPtr)NewPtrClear (sizeof (QHdr));
err = MemError ();
if (noErr != err) goto exit;
}
if (gSilenceOnes == nil) {
short i;
// This can't ever be disposed since we don't know when
// we might need to use it (at interrupt time)
gSilenceOnes = NewPtr (kBufSize);
err = MemError ();
if (noErr != err) goto exit;
for (i = 0; i < kBufSize; i++) {
gSilenceOnes[i] = (char)0x80;
}
}
if (gSilenceTwos == nil) {
// This can't ever be disposed since we don't know when
// we might need to use it (at interrupt time)
gSilenceTwos = NewPtrClear (kBufSize);
err = MemError ();
if (noErr != err) goto exit;
}
if (gNMRecPtr == nil) {
// This can't ever be disposed since we don't know when
// we might need to use it (at interrupt time)
gNMRecPtr = (NMRecPtr)NewPtr (sizeof (NMRec));
err = MemError ();
if (noErr != err) goto exit;
// Set up our NMProc info that will dispose of most
// (but not all) of our memory
gNMRecPtr->qLink = nil;
gNMRecPtr->qType = 8;
gNMRecPtr->nmFlags = 0;
gNMRecPtr->nmPrivate = 0;
gNMRecPtr->nmReserved = 0;
gNMRecPtr->nmMark = nil;
gNMRecPtr->nmIcon = nil;
gNMRecPtr->nmSound = nil;
gNMRecPtr->nmStr = nil;
gNMRecPtr->nmResp = NewNMProc (NMResponseProc);
gNMRecPtr->nmRefCon = 0;
}
perChanInfoPtr = (PerChanInfoPtr)NewPtr (sizeof (PerChanInfo));
err = MemError ();
if (noErr != err) goto exit;
// Init basic per channel information
perChanInfoPtr->qLink = nil;
perChanInfoPtr->qType = 0; // not used
perChanInfoPtr->stopping = 0;
#if DEBUG
perChanInfoPtr->magic = 'SANE';
#endif
perChanInfoPtr->theParams = *theParams;
// Have to remember the user's callback function from the sound because
// we are going to overwrite it with our own callback function.
perChanInfoPtr->usersCallBack = chan->callBack;
// Set up the sound header for the bufferCmd that will be used to play
// the buffers passed in by the SndPlayDoubleBuffer call.
perChanInfoPtr->soundHeader.samplePtr =
(Ptr)(theParams->dbhBufferPtr[0]->dbSoundData);
perChanInfoPtr->soundHeader.numChannels =
theParams->dbhNumChannels;
perChanInfoPtr->soundHeader.sampleRate =
theParams->dbhSampleRate;
perChanInfoPtr->soundHeader.loopStart = 0;
perChanInfoPtr->soundHeader.loopEnd = 0;
perChanInfoPtr->soundHeader.encode = cmpSH;
perChanInfoPtr->soundHeader.baseFrequency = kMiddleC;
perChanInfoPtr->soundHeader.numFrames =
(unsigned long)theParams->dbhBufferPtr[0]->dbNumFrames;
// perChanInfoPtr->soundHeader.AIFFSampleRate = 0; // unused
perChanInfoPtr->soundHeader.markerChunk = nil;
perChanInfoPtr->soundHeader.futureUse2 = nil;
perChanInfoPtr->soundHeader.stateVars = nil;
perChanInfoPtr->soundHeader.leftOverSamples = nil;
perChanInfoPtr->soundHeader.compressionID =
theParams->dbhCompressionID;
perChanInfoPtr->soundHeader.packetSize =
(unsigned short)theParams->dbhPacketSize;
perChanInfoPtr->soundHeader.snthID = 0;
perChanInfoPtr->soundHeader.sampleSize =
(unsigned short)theParams->dbhSampleSize;
perChanInfoPtr->soundHeader.sampleArea[0] = 0;
// Is the sound compressed? If so, we need to treat
// theParams as a SndDoubleBufferHeader2Ptr.
if (theParams->dbhCompressionID != 0 ) {
// Sound is compressed
err = GetCompressionInfo (theParams->dbhCompressionID,
((SndDoubleBufferHeader2Ptr)theParams)->dbhFormat,
theParams->dbhNumChannels,
theParams->dbhSampleSize,
&compInfo);
if (noErr != err) goto exitDispose;
perChanInfoPtr->soundHeader.format = compInfo.format;
} else {
// Sound is not compressed
perChanInfoPtr->soundHeader.format = kSoundNotCompressed;
}
playCmd.cmd = bufferCmd;
playCmd.param1 = 0; // unused
playCmd.param2 = (long)&perChanInfoPtr->soundHeader;
callBack.cmd = callBackCmd;
callBack.param1 = 0; // which buffer to fill, 0 buffer, 1, 0, ...
callBack.param2 = (long)perChanInfoPtr;
// Install our callback function pointer straight into
// the sound channel structure
if (gCarbonSndPlayDoubleBufferCallBackUPP == nil) {
gCarbonSndPlayDoubleBufferCallBackUPP =
NewSndCallBackProc (CarbonSndPlayDoubleBufferCallBackProc);
}
chan->callBack = gCarbonSndPlayDoubleBufferCallBackUPP;
if (gCarbonSndPlayDoubleBufferCleanUpUPP == nil) {
#if !TARGET_API_MAC_CARBON
gCarbonSndPlayDoubleBufferCleanUpUPP =
NewSndCallBackProc (CarbonSndPlayDoubleBufferCleanUpProc);
#endif
}
err = SndDoCommand (chan, &playCmd, true);
if (noErr != err) goto exitDispose;
err = SndDoCommand (chan, &callBack, true);
if (noErr != err) goto exitDispose;
exit:
return err;
exitDispose:
if (perChanInfoPtr != nil)
DisposePtr ((Ptr)perChanInfoPtr);
goto exit;
}
|
In Carbon there is no UPP for the
SndDoubleBackProc , but that's OK. Since all
code in Carbon is PowerPC, and this code will be compiled
into the calling program (therefore not needing to worry
about CFM<->Mach-O calling conventions) the
SndDoubleBackProc will just be treated as a
regular C function pointer.
The callback function that tells the user's code to
refill the now empty buffer and begins playing the alternate
buffer looks like this:
static pascal void CarbonSndPlayDoubleBufferCallBackProc
(SndChannelPtr theChannel, SndCommand * theCallBackCmd) {
SndDoubleBufferHeaderPtr theParams;
SndDoubleBufferPtr emptyBuf;
SndDoubleBufferPtr nextBuf;
PerChanInfoPtr perChanInfoPtr;
SndCommand playCmd;
perChanInfoPtr = (PerChanInfoPtr)(theCallBackCmd->param2);
#if DEBUG
if (perChanInfoPtr->magic != 'SANE')
DebugStr("\pBAD in CarbonSndPlayDoubleBufferCallBackProc");
#endif
if (perChanInfoPtr->stopping == true) goto exit;
theParams = &(perChanInfoPtr->theParams);
// The buffer that just played and needs to be filled
emptyBuf = theParams->dbhBufferPtr[theCallBackCmd->param1];
// Clear the ready flag
emptyBuf->dbFlags ^= dbBufferReady;
// This is the buffer to play now
nextBuf = theParams->dbhBufferPtr[!theCallBackCmd->param1];
// Check to see if it is ready, or if we have to wait a bit
if (nextBuf->dbFlags & dbBufferReady) {
perChanInfoPtr->soundHeader.numFrames = (unsigned long)nextBuf->dbNumFrames;
perChanInfoPtr->soundHeader.samplePtr = (Ptr)(nextBuf->dbSoundData);
// Flip the bit telling us which buffer is next
theCallBackCmd->param1 = !theCallBackCmd->param1;
// If this isn't the last buffer, call the user's fill routine
if (!(nextBuf->dbFlags & dbLastBuffer)) {
#if TARGET_API_MAC_CARBON
// Declare a function pointer to the user's double back proc
void (*doubleBackProc)(SndChannel*, SndDoubleBuffer*);
// Call user's double back proc
doubleBackProc = (void*)theParams->dbhDoubleBack;
(*doubleBackProc) (theChannel, emptyBuf);
#else
CallSndDoubleBackProc (theParams->dbhDoubleBack, theChannel, emptyBuf);
#endif
} else {
// Call our clean up proc when the last buffer finishes
theChannel->callBack = gCarbonSndPlayDoubleBufferCleanUpUPP;
}
} else {
// We have to wait for the buffer to become ready.
// The real SndPlayDoubleBuffer would play a short bit of silence
// waiting for the user to read the audio from disk,
// so that's what we do here.
#if DEBUG
DebugStr ("\p buffer is not ready!");
#endif
// Play a short section of silence so that we can check the
// ready flag again
if (theParams->dbhSampleSize == 8) {
perChanInfoPtr->soundHeader.numFrames =
(UInt32)(kBufSize / theParams->dbhNumChannels);
perChanInfoPtr->soundHeader.samplePtr = gSilenceOnes;
} else {
perChanInfoPtr->soundHeader.numFrames =
(UInt32)(kBufSize / (theParams->dbhNumChannels *
(theParams->dbhSampleSize / 8)));
perChanInfoPtr->soundHeader.samplePtr = gSilenceTwos;
}
}
// Insert our callback command
InsertSndDoCommand (theChannel, theCallBackCmd);
// Play the next buffer
playCmd.cmd = bufferCmd;
playCmd.param1 = 0;
playCmd.param2 = (long)&(perChanInfoPtr->soundHeader);
InsertSndDoCommand(theChannel, &playCmd);
exit:
return;
}
|
There is a further callback function that runs only once
the application has signaled that we have played the last
buffer of its data. This function queues the per-channel
sound information so that the Notification Manager callback
can dispose of the per-channel memory.
static pascal void CarbonSndPlayDoubleBufferCleanUpProc
(SndChannelPtr theChannel, SndCommand * theCallBackCmd) {
PerChanInfoPtr perChanInfoPtr;
perChanInfoPtr = (PerChanInfoPtr)(theCallBackCmd->param2);
#if DEBUG
if (perChanInfoPtr->magic != 'SANE') DebugStr("\pBAD in
CarbonSndPlayDoubleBufferCleanUpProc");
#endif
// Put our per channel data on the free queue so we can
// clean up later
Enqueue ((QElemPtr)perChanInfoPtr, gFreeList);
// Have to put the user's callback proc back so
// they get called when the next buffer finishes
theChannel->callBack = perChanInfoPtr->usersCallBack;
// Have to install our Notification Manager routine so that
// we can clean up the gFreeList
if (!OTAtomicSetBit (&gNMRecBusy, 0)) {
NMInstall (gNMRecPtr);
}
}
|
The next issue is that because the simulating code will
not be called once the sound has finished, it doesn't have a
good opportunity to clean up after itself because it will be
called at interrupt time when it wants to dispose of the
associated memory for the completed sound. This can be
partially alleviated by using the Notification Manager to
dispose of most of the memory, but there is no easy way to
clean up the Notification Manager record that is allocated
for this.
The Notification Manager callback code looks like
this:
static pascal void NMResponseProc (NMRecPtr nmReqPtr) {
PerChanInfoPtr perChanInfoPtr;
OSErr err;
NMRemove (nmReqPtr);
gNMRecBusy = false;
do {
perChanInfoPtr = (PerChanInfoPtr)gFreeList->qHead;
if (nil != perChanInfoPtr) {
err = Dequeue ((QElemPtr)perChanInfoPtr, gFreeList);
if (noErr == err) {
DisposePtr ((Ptr)perChanInfoPtr);
}
}
} while (nil != perChanInfoPtr && noErr == err);
}
|
Because existing code will assume that once it has called
SndPlayDoubleBuffer that it can install a
callBackCmd without affecting the playing of
the sound, the code replacing
SndPlayDoubleBuffer must insert its
bufferCmd s and callBackCmd s at the
head of the sound queue. This means directly manipulating
the sound channel's command queue.
The code to do that looks like:
static void InsertSndDoCommand (SndChannelPtr chan, SndCommand * newCmd) {
if (-1 == chan->qHead) {
chan->qHead = chan->qTail;
}
if (1 <= chan->qHead) {
chan->qHead--;
} else {
chan->qHead = chan->qLength - 1;
}
chan->queue[chan->qHead] = *newCmd;
}
|
This also means that SndDoImmediate must be
wrapped so as to allow the original code to use the
quietCmd as it always has. Here is code that
shows how to wrap SndDoImmediate to make sure
that a quietCmd stops the sound and calls the
correct function for any callBackCmd that might
have been installed after the call to
SndPlayDoubleBuffer .
// Remember this routine could be called at interrupt time,
// so don't allocate or deallocate memory.
OSErr MySndDoImmediate (SndChannelPtr chan, SndCommand * cmd) {
PerChanInfoPtr perChanInfoPtr;
// Is this being called on one of the sound channels we are manipulating?
// If so, we need to pull our callback out of the way so
// that the user's commands run
if (nil != gFreeList && gCarbonSndPlayDoubleBufferCallBackUPP ==
chan->callBack) {
if (quietCmd == cmd->cmd || flushCmd == cmd->cmd) {
// We know that our callBackCmd is the first item in the queue
// if this is our channel
perChanInfoPtr = (PerChanInfoPtr)
(chan->queue[chan->qHead].param2);
#if DEBUG
if (perChanInfoPtr->magic != 'SANE')
DebugStr("\pBAD in MySndDoImmediate");
#endif
perChanInfoPtr->stopping = true;
Enqueue ((QElemPtr)perChanInfoPtr, gFreeList);
if (! OTAtomicSetBit (&gNMRecBusy, 0)) {
NMInstall (gNMRecPtr);
}
chan->callBack = perChanInfoPtr->usersCallBack;
}
}
return (SndDoImmediate (chan, cmd));
}
|
Back to top
SndStartFilePlay
If you use SndStartFilePlay to play sound
resources (resources of type 'snd ' ) using
limited amounts of memory (rather than loading the entire
sound into memory and playing it), your only solution is to
write a wrapper around the
CarbonSndPlayDoubleBuffer code using
ReadPartialResource to extract only a portion
of the sound resource at a time.
QuickTime doesn't give you the option of playing a
resource handle whose data hasn't been completely loaded.
QuickTime will play a sound resource, but it expects that
the resource has been fully loaded into memory.
If you use SndStartFilePlay to play sound
files from disk, this is probably a good fit for QuickTime.
You can even have QuickTime start playing the sound from a
specific time, just like SndStartFilePlay ,
though you have to poll to know when the sound is done,
there is no QuickTime callback for this information.
The code to have QuickTime play a file from disk looks
like:
OSErr err;
Movie theSound;
short fileRefNum;
err = OpenMovieFile (&theSpec, &fileRefNum, fsRdPerm);
if (noErr == err) {
err = NewMovieFromFile (&theSound, fileRefNum, 0, nil,
newMovieActive, nil);
}
if (noErr == err) {
GoToBeginningOfMovie (theSound);
}
if (noErr == err) {
StartMovie (theSound);
}
if (noErr == err) {
while (!IsMovieDone (theSound) {
MoviesTask (theSound, 0);
}
}
|
This code will open a file on disk, pointed to by a
FSSpec , and play it synchronously from the
beginning of the sound to the end.
SndStartFilePlay required you to already have
the file open and pass it a file reference number, so you
probably already have a FSSpec to the file you
want to play.
If you want to start the sound at some place other than
the start, you would use QuickTime's
SetMovieTime function to set the current time
in the movie.
If you want to play the sound asynchronously, then you
will need to make theSound a global and in your
main event loop call MoviesTask about every
quarter of a second to keep the movie running with any
glitches. What this implies is that if you cannot call
MoviesTask , for instance, because your user is
holding down the mouse button and you are inside a call to
TrackDrag , then the sound will stop. If that is
unacceptable, then you will probably want to convert to
using the CarbonSndPlayDoubleBuffer code which
does not require task time to continue playing a sound.
Back to top
SndRecordToFile and
SPBRecordToFile
If you use SndRecordToFile to record sound
to disk, the easiest way to convert to Carbon is to use
QuickTime. The following code shows how you would use
QuickTime's Sequence Grabber to record audio in a
synchronous manner.
SGChannel sgSoundChan;
ComponentInstance sgSoundComp;
short numChannels,
sampleSize;
OSType compressionType,
inputSource;
err = SGInitialize (sgSoundComp);
if (err == noErr) {
err = SGNewChannel (sgSoundComp, SoundMediaType, &sgSoundChan);
}
if (err == noErr) {
err = SGSetChannelUsage (sgSoundChan, seqGrabRecord);
}
if (err == noErr) {
err = SGSetSoundInputRate (sgSoundChan, sampleRate);
}
if (err == noErr) {
err = SGSetSoundInputParameters
(sgSoundChan, sampleSize, numChannels, compressionType);
}
if (err == noErr) {
err = SPBSetDeviceInfo
(SGGetSoundInputDriver (sgSoundChan),
siOSTypeInputSource, &inputSource);
}
if (err == noErr) {
err = SGSoundInputDriverChanged (sgSoundChan);
}
if (err == noErr) {
err = SGSetDataOutput (sgSoundComp, &theSpec, seqGrabToDisk);
}
if (err == noErr) {
err = SGStartRecord (sgSoundComp);
}
if (err == noErr) {
EventRecord event;
Boolean done = false;
while (!done && err == noErr) {
WaitNextEvent (mDownMask | keyDownMask, &event, 6, nil);
err = SGIdle (sgSoundComp);
switch (event.what) {
case mouseDown:
case keyDown:
done = true;
break;
}
}
err = SGStop (sgSoundComp);
}
if (sgSoundComp != nil) {
err = CloseComponent (sgSoundComp);
}
|
If you would prefer to use the Sequence Grabber's user
interface to have the user configure the recording (which is
recommended if you don't have your own interface already)
then you can skip the calls to
SGSetSoundInputRate ,
SGSetSoundInputParameters , and
SPBSetDeviceInfo with the
siOSTypeInputSource selector, and
SGSoundInputDriverChanged and replace them all
with a single call to SGSettingsDialog .
If you want to record asynchronously (something that
SndRecordToFile will not do but
SPBRecordToFile would), then you will need to
make sgSoundComp a global and call
SGIdle from your main event loop at least once
every quarter of a second.
One nice feature of using the Sequence Grabber to record
is that it will record in any compression format currently
installed on the Mac. It does not limit you to only MACE 3:1
and MACE 6:1 compression.
Back to top
Summary
Many of the Sound Manager functions not in Carbon were
not used and so their loss will not cause any difficulties.
For the other functions, QuickTime and a little extra Sound
Manager Carbon compatible code is all that is required.
Since QuickTime is very easy to use (at least in these
cases) and has most of the functionality of the non-Carbon
Sound Manager calls, converting your code to use QuickTime
when converting the rest of your code to Carbon should not
be difficult.
For those applications that made heavy use of
SndPlayDoubleBuffer and required its interrupt
driven nature to deliver uninterrupted audio, the
CarbonSndPlayDoubleBuffer and associated
functions allow you to do this with a minimum of change to
your existing code base.
Back to top
References
Carbon
Developer Documentation
Technote 1108: Unknown Sound
Features
Technote 1048: Some Sound Advice:
Getting the Most Out of the Sound Manager
Inside
Macintosh: Sound
Back to top
Downloadables
|
Acrobat version of this Note (96K).
|
Download
|
|
CarbonSndPlayDoubleBuffer code
|
Download
|
Back to top
|