Using Double Buffers
The play-from-disk routines make extensive use of the
application if you wish to bypass the normal play-from-disk routines. You
might want to do this if you wish to maximize the efficiency of your application
can specify your own doubleback procedure (that is, the algorithm used to
switch back and forth between buffers) and customize several other buffering
parameters.
only if you require very fine control over double buffering.
(into which the double- buffered data is to be written) and a pointer to a sound
myErr = SndPlayDoubleBuffer ( mySndChan, &myDoubleHeader);
The dbhBufferPtr array contains pointers to two records of type
Sound Manager switches until all the sound data has been sent into the buffers should both already contain a nonzero number of frames of data.
The following two sections illustrate how to fill out these data structures,
create your two buffers, and define a doubleback procedure to refill the
buffers when they become empty.
Setting Up Double Buffers
two buffers to dbBufferReady, and then fill out a record of type
illustrates how you might accomplish these tasks.
// Listing: Setting up double buffers
// Assuming inclusion of MacHeaders
#include <Sound.h>
#include <Memory.h>
// Constants used in routine
#define kDoubleBufferSize 4096 // size of each buffer (in bytes)
// variables used by doubleback proc
typedef struct LocalVars {
long bytesTotal; // total number of samples
long bytesCopied; // number of samples copied to buffer
Ptr dataPtr; // pointer to sample of copy } LocalVars, *LocalVarsPtr;
// prototype routine like this prior to calling it
// this function uses SndPlayDoubleBuffer to play the sound specified
{
LocalVars myVars;
short i;
// Prototype for DoubleBackProc
// Error handling routine
// set up myVars with initial information
myVars. bytesTotal = sndHeader->length;
myVars. bytesCopied = 0; // no samples copied yet
myVars.dataPtr = &sndHeader-> sampleArea[0];
// pointer to first sample
// set up SndDoubleBufferHeader
doubleHeader.dbhNumChannels = 1; // one channel
doubleHeader.dbhSampleSize = 8; // 8-bit samples
doubleHeader.dbhCompressionID = 0; // no compression
doubleHeader.dbhPacketSize = 0; // no compression
doubleHeader.dbhSampleRate = sndHeader-> sampleRate;
doubleHeader.dbhDoubleBack = &MyDoubleBackProc;
// initialize both buffers
for (i = 0; i <= 1; i++) {
// get memory for double buffer
if ( !doubleBuffer ) {
return MemError();
}
doubleBuffer->dbNumFrames = 0; // no frames yet
doubleBuffer->dbFlags = 0; // buffer is empty
doubleBuffer->dbUserInfo[0] = (long)&myVars;
// fill buffer with samples
MyDoubleBackProc(chan, doubleBuffer);
// store buffer pointer in header
doubleHeader.dbhBufferPtr[i] = doubleBuffer;
}
// start the sound playing
if ( err ) {
DoError(err);
return err;
}
// wait for the sound to complete by watching the channel status
do {
} while (! status.scChannelBusy);
// dispose double-buffer memory}
for ( i = 0; i <= 1; i++)
}
The function DBSndPlay takes two parameters, a pointer to a sound channel
and a pointer to a sound header. It reads the sound header to determine the
characteristics of the sound to be played (for example, how many samples are
to be sent into the sound channel). Then DBSndPlay fills in the fields of the
double-buffer header, creates two buffers, and starts the sound playing. The
doubleback procedure MyDoubleBackProc is defined in the next section.
Writing a Doubleback Procedure
The dbhDoubleBack field of a double-buffer header specifies the address of a doubleback procedure, an application- defined procedure that is called when the
double buffers are switched and the exhausted buffer needs to be refilled. The
doubleback procedure should have this format:
pascal void MyDoubleBackProc (SndChannelPtr chan, SndDoubleBufferPtr
exhaustedBuffer);
The primary responsibility of the doubleback procedure is to refill an
exhausted buffer of samples and to mark the newly filled buffer as ready for
processing. Thecode below illustrates how to define a doubleback procedure.
Note that the sound-channel pointer passed to the doubleback procedure is not
used in this procedure.
This doubleback procedure extracts the address of its local variables from the
dbUserInfo field of the double-buffer record passed to it. These variables are
used to keep track of how many total bytes need to be copied and how many
bytes have been copied so far. Then the procedure copies at most a buffer-full
of bytes into the empty buffer and updates several fields in the double- buffer
record and in the structure containing the local variables. Finally, if all the
bytes to be copied have been copied, the buffer is marked as the last buffer.
Note: Because the doubleback procedure is called at interrupt time,
it cannot make any calls that move memory either directly or
in directly. (Despite its name, the BlockMove procedure does not cause blocks of memory to move or be purged, so you can safely call
it in your doubleback procedure, as illustrated in the Listing below.)
Defining a doubleback procedure
// Assuming inclusion of MacHeaders
#include <Sound.h>
// Constants used in routine
#define kDoubleBufferSize 4096 // size of each buffer (in bytes)
// variables used by doubleback proc
typedef struct LocalVars {
long bytesTotal; // total number of samples
long bytesCopied; // number of samples copied to buffer
Ptr dataPtr; // pointer to sample of copy } LocalVars, *LocalVarsPtr;
// Prototype the routine like this prior to calling it
// Note this routine must take pascal style calling conventions
// since it will be called from the toolbox
pascal void MyDoubleBackProc(SndChannelPtr, SndDoubleBufferPtr);
pascal void MyDoubleBackProc (SndChannelPtr chan,SndDoubleBufferPtr
doubleBuffer)
{
LocalVarsPtr myVarsPtr;
long bytesToCopy;
// get pointer to my local variables
myVarsPtr = (LocalVarsPtr) doubleBuffer->dbUserInfo[0];
// get number of bytes left to copy
bytesToCopy = myVarsPtr-> bytesTotal - myVarsPtr-> bytesCopied;
// If the amount left is greater than double-buffer size,
// then limit the number of bytes to copy to the size of the buffer.
if ( bytesToCopy > kDoubleBufferSize )
bytesToCopy = kDoubleBufferSize;
// copy samples to double buffer
BlockMove( myVarsPtr->dataPtr, & doubleBuffer->dbSoundData[0], bytesToCopy);
// store number of samples in buffer and mark buffer as ready
doubleBuffer->dbNumFrames = bytesToCopy;
doubleBuffer->dbFlags = doubleBuffer->dbFlags | dbBufferReady;
// update data pointer and number of bytes copied
myVarsPtr->dataPtr = myVarsPtr->dataPtr + bytesToCopy;
myVarsPtr-> bytesCopied = myVarsPtr-> bytesCopied + bytesToCopy;
// If all samples have been copied, then this is the last buffer.
if ( myVarsPtr-> bytesCopied == myVarsPtr-> bytesTotal )
doubleBuffer->dbFlags = doubleBuffer->dbFlags | dbLastBuffer;
}