Winter 92 - MAKING YOUR MACINTOSH SOUND LIKE AN ECHO BOX
MAKING YOUR MACINTOSH SOUND LIKE AN ECHO BOX
RICH COLLYER
Happy notes for sound buffs: As you'll see from the sample code provided on the
Developer CD Series disc, you can make your Macintosh play and record sounds at the
same time, simply by using double buffering to record into one buffer while playing a
second buffer, and then flipping between the buffers. If you want to take things a few
steps further, pull out elements of this code and tailor them to suit your own acoustic
needs.
We all know that the Macintosh is a sound machine, so to speak, but with a little
clever programming you can turn it into an echo box as well. The sample
2BufRecordToBufCmd included on theDeveloper CD Series disc is just a small
application (sans interface) that demonstrates one way to record sounds at the same
time that you're playing them. There are other ways to achieve the same goal, but my
purpose is to educate you about the Sound Manager, not to lead you down the definitive
road to becoming your own recording studio.
In addition to the main routine, 2BufRecordToBufCmd includes various setup routines
and a completion routine. For easy reading, I've left out any unnecessary code out of
this article.
CONSTANT COMMENTS
Before I get into the sample code itself, here are a few of the constants you'll run into
in the application.
GETTING A HANDLE ON IT
The kMilliSecondsOfSound constant is used to declare how many milliseconds of sound
the application should record before it starts to play back. The smaller the number of
milliseconds, the more quickly the sound is played back. This constant is used to
calculate the size of the 'snd ' buffer handles (just the data). Depending on the sound
effect you're after, kMilliSecondsOfSound can range from 50 milliseconds to 400,000
or so. If you set it below 50, you risk problems: there may not be enough time for the
completion routine to finish executing before it's called again. On the high end of the
range, only the application's available memory limits the size. The smaller the value,
of course, the faster the buffers fill up and play back, and the faster an echo effect
you'll get. A millisecond value of 1000 provides a one-second delay between record and
echo, which I've found is good for general use. You'll want to experiment to find the
effect you like. (Beware of feedback, both from your machine and from anyone who's in
close enough proximity to "enjoy" the experimentation secondhand.)
YOUR HEAD SIZE, AND OTHER #DEFINES
The next three constants (kBaseHeaderSize, kSynthSize, and kCmdSize) are used to
parse the sound header buffers in the routine FindHeaderSize. kBaseHeaderSize is the
number of bytes at the top of all 'snd ' headers that aren't needed in the application
itself. While the number of bytes isn't really ofinterest here, you need to parse the
header in order to find the part of the sound header that you'll pass to the bufferCmd.
How much you parse off the top is determined by the format of the header and the type
of file; for the purposes of this code, however, all you need to be concerned with are
the 'snd ' resources. The second constant, kSynthSize, is the size of one 'snth'. In the
calculations of the header, I find out how many 'snth's there are, and multiply that
number by kSynthSize. The last constant, kCmdSize, is the size of one command, which
is used in the same way as kSynthSize. (These equations are derived fromInside
Macintosh Volume VI, page 22-20.)
ERROR CHECKING WITH EXITWITHMESSAGE
2BufRecordToBufCmd includes error checking, but only as a placeholder for future
commercialization of the product. If the present code detects an error, it calls the
ExitWithMessage routine, which displays a dialog box that tells you more or less
where the error occurred and what the error was. Closing this dialog box quits the
application, at which point you have to start over again. Note that calling
ExitWithMessage at interrupt time could be fatal, since it uses routines that might
move memory. For errors that could occur at interrupt time, DebugStr is used instead.
USING THE SOUND INPUT DRIVER
Use of the sound input driver is fairly well documented inInside Macintosh Volume VI,
Chapter 22 (pages 22-58 through 22-68 and 22-92 through 22-99), but here's a
little overview of what 2BufRecordToBufCmd does at this point in the routine, and
why. When you use sound input calls at the low level (not using SndRecord or
SndRecordToFile), you need to open the sound input driver. This section of the code
just opens the driver, which the user selects via the sound cdev.
gError = SPBOpenDevice (kDefaultDriver, siWritePermission,
&gSoundRefNum);
To open the driver, you call SPBOpenDevice and pass in a couple of simple parameters.
The first parameter is a driver name. It doesn't really matter what the name of the
driver is; it simply needs to be the user-selected driver, so the code passes in nil
(which is what kDefaultDriver translates into). The constant siWritePermission
tells the driver you'd like read/write permission to the sound input driver. This will
enable the application to actually use the recording calls. The last parameter is the
gSoundRefNum. This parameter is needed later in the sample so that you can ask
specific questions about the driver that's open. The error checking is just to make sure
that nothing went wrong; if something did go wrong, the code goes to ExitWithMessage,
and then the sample quits.
gError = SPBSetDeviceInfo (gSoundRefNum, siContinuous,
(Ptr) &contOnOff);
Continuous recording is activated here to avoid a "feature" of the new Macintosh Quadra
700 and 900 that gives you a slowly increasing ramp of the sound input levels to their
normal levels each time you call SPBRecord. The result in 2BufRecordToBufCmd is a
pause and gradual increase in the sound volume between buffers as the buffers are
being played. Continuous recording gives you this ramp only on the first buffer, where
it's almost unnoticeable.
BUILDING 'SND ' BUFFERS
Now that the sound input driver is open, the code can get the information it needs to
build the 'snd ' buffers. As its name implies, 2BufRecordToBufCmd uses two buffers.
The reason is sound (no pun intended): The code basically uses a double-buffer method
to record and play the buffers. The code doesn't tell the machine to start to play the
sound until the recording completion routine has been called, so you don't have to
worry about playing a buffer before it has been filled with recorded data. The code
also does not restart the recording until the previous buffer has started to play.
INFORMATION, PLEASE
To build the sound headers, you need to get some information from the sound input
driver about how the sound data will be recorded and stored. That's the function of the
GetSoundDeviceInfo routine, which looks for information about the SampleRate (the
number of samples per second at which the sound is recorded), the SampleSize (the
sample size of the sound being recorded--8 bits per sample is normal), the
CompressionType (see "Putting on the Squeeze"), the NumberChannels(the number of
sound input channels, normally 1), and the DeviceBufferInfo (the size of the internal
buffers).
This code (minus the error checking) extracts these values from the sound input
driver.
gError = SPBGetDeviceInfo (gSoundRefNum, siSampleRate,
(Ptr) &gSampleRate);
gError = SPBGetDeviceInfo (gSoundRefNum, siSampleSize,
(Ptr) &gSampleSize);
gError = SPBGetDeviceInfo (gSoundRefNum, siCompressionType,
(Ptr) &gCompression);
gError = SPBGetDeviceInfo (gSoundRefNum, siNumberChannels,
(Ptr) &gNumberOfChannels);
gError = SPBGetDeviceInfo (gSoundRefNum, siDeviceBufferInfo,
(Ptr) &gInternalBuffer);