The SndKit lets you access the sound hardware with a minimum of effort. Recording and playback of sound are particularly easy; the software manages data buffering, communication with the UNIX devices, synchronization with the operating system, and other such necessities. It's designed to accommodate both casual use of sound effects as well as detailed examination and manipulation of sound data.
The SndKit consists of three main classes:
Snd, SndView, and
SndMeter.
Snd ClassThe Snd class provides a number of methods that let you
access, modify, and perform sound data. The methods fall into four
categories:
Locating and storing sounds
Recording and playback
Editing
Sound data manipulation
While a Snd object uses the
SndSoundStruct structure to represent its
sound, you only need to be familiar with this structure if you're
directly manipulating sound data.
Each Snd object represents a single sound. The Snd class
provides four ways to install a sound in a Snd object. You
can:
Record a sound using the CODEC microphone input.
Read sound data from a soundfile or Mach-O sound segment.
Retrieve a sound from the pasteboard.
Sound recording (and playback) is described in the next section.
Described here are the methods that let you read sounds from a
soundfile or Mach-O segment and retrieve them from the pasteboard. As
a shortcut to finding sounds, the Snd class provides a global naming
mechanism that lets you identify and locate sounds by name.
Also described here are methods that let you store your sound by writing it to a soundfile or placing it on the pasteboard.
Soundfiles are files on a disk that contain sound data. By
convention, soundfile names are given a “.snd” extension.
To read a soundfile into a Snd object, simply create the object and
send it the readSoundfile:
message:
#import <SndKit/SndKit.h> /* you must import this file */ . . . Snd *aSound = [[Snd alloc] init]; /* create a Snd object */ int result = [aSound readSoundfile: @"KneeSqueak.snd"]; /* read a file */ |
The data in the named soundfile is read into the
Snd object. The given file name is a
complete UNIX pathname and must include the extension; in the example,
the soundfile is searched for in the current working directory. Like
many of the Snd methods, readSoundfile: returns an error code; the
complete list of errors codes is given in the description of the
SNDSoundError() C function in
SndKit
Function References. Success is indicated by the code
SND_ERR_NONE.
These two operations, initializing a
Snd object and reading a soundfile, are
combined in the initFromSoundfile:
method:
Snd *aSound = [[Snd alloc] initFromSoundfile: @"KneeSqueak.snd"]; |
The method returns nil if the
soundfile isn't found or if it can't be read. You can read a new
soundfile into an existing Snd object at any
time; the object's old sound is discarded.
Apple provides a number of short sound effects (useful as system beeps) that are stored in the directory /System/Library/Sounds. You can audition a soundfile by running the sndplay program from a Terminal shell window. For example:
sndplay /System/Library/Sounds/Frog.aiff |
Writing a soundfile from the data in a sound object is done by invoking the writeSoundfile: method:
[mySound writeSoundfile: @"FleaSigh.snd"]; |
Even if the Snd object contains
fragmented data, the data in the soundfile will be compact. However,
the Snd object's data will remain
fragmented.
Placing a Snd object on the pasteboard
lets you copy its data between running applications. To place a
Snd on the pasteboard, invoke the writeToPasteboard method:
[mySound writeToPasteboard]; |
The object's data is compacted (if it's fragmented) and copied. The copy is then placed on the pasteboard.
To read data from the pasteboard into a Snd, invoke the
initFromPasteboard: method:
id mySound = [[Snd alloc] initFromPasteboard]; |
The sound data currently on the pasteboard is copied into the receiver of the message. Since the pasteboard can contain only one sound at a time, the method doesn't require an argument to further identify the sound. If there isn't a sound on the pasteboard, initFromPasteboard returns nil.
The Snd class maintains an
application-wide list of named Snd objects
called the named Sound list. The addName:Sound: class method lets you name a
Snd object and add it to the named Sound
list:
/* Add a Snd to the named Sound list. */
id namedSound = [Snd addName: @"PopTop" sound: mySound];
/* Check for failure. */
if (namedSound == nil)
. . . |
The names in the named Sound list are unique; if you try to add
a Snd by a name that's already in use, the
effort is denied and nil is
returned.
You can also name a Snd and place it on
the named Sound list by sending setName: to the object:
id namedSound = [mySound setName: @"RedRover"]; |
setName: can be used to change
the name of a Snd that's already on the named
Sound list.
The name method retrieves a
Snd object's name, whether given in a setName: message or through the addName:sound: method.
Named Snds are visible to your entire
application. To retrieve a named Snd and
load a copy of its data into a new Snd
object, invoke the findSoundFor:method:
id newRedButton = [Snd findSoundFor: @"RedButton"]; |
If findSoundFor: fails to find
the Snd in the named Sound list, it gives its
argument (the Snd name) a “.snd”
suffix and looks for a soundfile in these directories (in
order):
~/Library/Sounds/
/LocalLibrary/Sounds/ (on NeXTStep)
/NextLibrary/Sounds/ (on NeXTStep)
/Library/Sounds/ (on MacOS X)
/System/Library/Sounds/ (on MacOS X)
(~ represents the user's home directory.)
A Snd found through findSoundFor: is automatically added to the
named Sound list.
To remove a named Snd from the named
Sound list, invoke removeSoundForName:, passing the name of the
object that you want to remove. Removing a named
Snd neither frees the
Snd nor changes the object's notion of its
name (which it stores as an instance variable).
Identifying and locating Snds through
the named Sound list is generally the most efficient way to access
sound data. The data in a named Snd is
shared by all the objects that retrieve it.
To record a sound into a Snd object,
simply create the object and send it the record message:
id mySound = [[Snd alloc] init]; int errorCode = [mySound record]; |
Currently, the record method always records from the CODEC microphone input. The method returns immediately while the recording is performed by a background thread.
The value returned by record
indicates the success or failure of the attempt to begin
recording; SND_ERR_NONE indicates success.
The recording continues until the Snd
object receives the stop message or
until the Snd object can accommodate no more
data. By default, the receiver of the record message is always set to accommodate ten
minutes of 8 kHz mu-law sound (the type of sound data sent from the
CODEC). You can set the size of the Snd
object, prior to recording, to specify a different recording length.
This is done through the setDataSize:dataFormat:samplingRate:channelCount:infoSize:
method.
To play a sound, send the play
message to the Snd object:
int errorCode = [mySound play]; |
Like recording, playback is performed by a background thread and
the play method returns an error
code. Playback continues until the entire sound is played or until
the Snd object that initiated the playback
receives the stop message.
A single Snd object can only perform
one recording or playback operation at a time, thus the function of
the stop method is never ambiguous:
If the Snd is playing, stop stops the playback; if it's recording, it
stops the recording.
You can temporarily suspend a playback or recording by sending
the pause message to a
Snd object. Like stop, the pause message halts whatever activity the
Snd is currently engaged in; however, unlike
stop, the
Snd doesn't forget where it was. This allows
the resume message to cause the
Snd to continue its activity from the place
at which it was paused.
The record, play, pause,
resume, and stop methods (and the analogous action methods
described in the next section) should only be used if you have a
running NSApplication object. To create a
command-line program (similar to sndrecord or
sndplay), you can use methods to create
Snd objects and read sound data, but you
should use the C functions SNDStartRecording(),
SNDStartPlaying(), and
SNDStop() to perform the
Snd.
The Snd class methods record:, play:, pause:,
resume:, and stop: are designed to be used as part of the
target/action mechanism described in the
Openstep/Cocoa
Concepts manual. Briefly, this mechanism lets you assign a
selected message (the action) and an object id (the target) to a
NSControl object such that when the user acts
on the NSControl, the action message is sent to
the target object. In the following example, the three methods are
assigned as action messages to three different
NSControl objects―in this case,
NSButtons. The same Snd
object is assigned as the NSButton's
target:
/* Create a Snd object ... */
id mySound = [[Snd alloc] init];
/* ... and three NSButtons. */
id recordButton = [[NSButton alloc] init],
playButton = [[NSButton alloc] init],
stopButton = [[NSButton alloc] init];
/* Set the action messages. */
[recordButton setAction: @selector(record:)];
[playButton setAction: @selector(play:)];
[stopButton setAction: @selector(stop:)];
/* Set the targets. */
[recordButton setTarget: mySound];
[playButton setTarget: mySound];
[stopButton setTarget: mySound]; |
In response to the user's clicking the different
NSButtons, the Snd
object starts recording, starts playing, or stops one of these
operations.
A Snd can have a delegate object. A
Snd's delegate receives, asynchronously, the
following messages as the Snd records or
plays:
willPlay: is sent
just before the Snd begins playing.
didPlay: is sent when
the Snd finishes playing.
willRecord: is sent just before recording.
didRecord: is sent after recording.
hadError: is sent if playback or recording generates an error.
To set a Snd's delegate object, invoke the setDelegate: method:
[mySound setDelegate:SoundDelegate]; |
A message is sent to the delegate only if the delegate implements the method that the message invokes.
The Snd class defines methods that
support cut, copy, and paste operations for sampled sound data:
copySamples:at:count:
replaces the Snd's data with a copy of a
portion of the data in its first argument, which must also be a
Snd object.
insertSamples:at:
inserts a copy of the first argument's sound data into the receiving
Snd object.
deleteSamplesAt:count: deletes a portion of the
Snd's data.
These methods all return SNDSoundError()
type error codes (recall that SND_ERROR_NONE
indicates success).
![]() | The operations described here are also implemented in a
more convenient form in the |
Deleting a portion of a Snd's data is
direct; you simply invoke deleteSamplesAt:count:. For example:
/* Delete the beginning of mySound. */ int eCode = [mySound deleteSamplesAt:0 count:1000]; |
The first 1000 samples are deleted from the receiver of the message. The first argument specifies the beginning of the deletion in samples from the beginning of the data (counting from sample 0); the second argument is the number of samples to delete.
Copying a portion of one Snd and pasting it into
another―or into itself, for that matter―requires the use
of both copySamples:at:count and
insertSamples:at:. In the following
example, the beginning of mySound is
repeated:
/* Create a stutter at the beginning of mySound. */
id tmpSound = [[Snd alloc] init];
int errorCode = [tmpSound copySamples: mySound at: 0 count: 1000];
if (errorCode == SND_ERROR_NONE)
errorCode = [mySound insertSamples: tmpSound at: 0];
[tmpSound free]; |
First, the data in tmpSound is completely replaced by a copy of the first 1000 samples in mySound. Note that the copySamples:at:count method doesn't remove any data from its first argument, it simply copies the specified range of samples from the first argument into the receiver. Next, tmpSound is prepended to mySound, creating a repetition of the first 1000 samples in mySound. The insertSamples: method inserts a copy of the argument into the receiver. Thus, the argument can be freed after inserting.
The two Snd objects involved in the
insertSamples:at: method (the
receiver and the first argument) must be compatible: They must have
the same format, sampling rate, and channel count. If possible, the
data that's inserted into the receiver of insertSamples:at: is automatically converted to
be compatible with the data already in the receiver (see the
description of the SNDConvertSound() C function
in SndKit Function References for a
list of the conversions that are supported). An error code indicating
that the insertion failed is returned if the two
Snds aren't compatible or if the inserted
data can't be converted.
Replacing is like copying and pasting, except that a region of
the pasted-into Snd is destroyed to
accommodate the new data. In the following example, the beginning of
oneSound is replaced with a copy of
the beginning of twoSound:
/* Replace the beginning of oneSound with that of twoSound. */
int tmpCode = [tmpSound copySamples:twoSound at:0 count:1000];
int inCode;
if (tmpCode == SND_ERROR_NONE) {
int oneCode = [oneSound deleteSamplesAt:0 count:1000];
if (oneCode == SND_ERROR_NONE)
inCode = [oneSound insertSamples:tmpSound at:0]; }
[tmpSound free];
/* Check inCode before performing further manipulations. */
. . . |
The editing methods described above only work on
Snds that contain sampled data. The
isEditable method is provided to
quickly determine whether a Snd object can be
edited. The method returns YES if the object can be edited, NO if it
can't.
The compatibleWith: method
takes a Snd object as its argument and
returns YES if the argument and the receiver are compatible. (The
method also returns YES if one of the objects is empty; in other
words, it's OK to insert samples into an empty object.) This method
is useful prior to invoking the insertSound:at: method.
Another handy method is sampleCount, which returns the number of
sample frames contained in the receiver. A
sample frame is a channel-independent count of the samples in a
Snd. For example, sending sampleCount to a two-channel
Snd that contains three seconds worth of data
returns the same value as sending it to a one-channel
Snd that also contains three seconds of data
(given that the two Snds have the same
sampling rate), even though the two-channel
Snd actually contains twice as much
data.
The Snd class defines three more
editing methods:
copy returns a new
Snd object that's a copy of the
receiver.
copySound: takes a
Snd object as an argument and replaces the
data in the receiver with the data in its argument. Since the entire
range of data in the receiver is replaced, it needn't be editable, nor
must the two Snds be
compatible.
deleteSamples can
only be sent to an editable Snd. It deletes
the receiver's sound data.
A Snd's data is normally contiguous in
memory. However, when you edit a Snd object,
its data can become fragmented, or discontiguous. Fragmentation is
explained in the description of the
SndSoundStruct, earlier in this chapter.
Briefly, fragmentation lets you edit Snds
without incurring the cost of moving large sections of data in memory.
However, fragmented Snds can be less
efficient to play. The needsCompacting and compactSamples methods are provided to
determine if a Snd is fragmented and to
compact it. Note that compacting a large Snd
that has been mercilessly fragmented can take a noticeably long
time.
SndView ClassThe SndView class provides a mechanism
for displaying the sound data contained in a
Snd object. While
SndView inherits from the
Application Kit's
NSView class, it implements a number of methods
that are also defined in Snd, such as
play:, record:, and stop:. In addition, it implements editing
methods such as cut:, copy:, and paste:.
SndViews are designed to be used within
a NSScrollView. While you can create a
SndView without placing it in a
NSScrollView, its utility―particularly
as it's used to display a large Snd―is
limited.
SndViewTo display a sound, you create a new
SndView with a particular frame, give it a
Snd object to display (through setSound:), and then send the display message to the
SndView:
/* Create a new SndView object. */ id mySoundView = [[SndView alloc] initFrame: &svRect]; /* Set its Snd object. */ [mySoundView setSound:mySound]; /* Display the Snd object's sound data. */ [mySoundView display]; |
In the example, svRect is a previously
defined NSRect. If autodisplaying is turned
on (as set through NSView's setAutodisplay: method), you needn't send the
display message; simply setting the
Snd will cause the
SndView to be displayed.
For most complete sounds, the length of the
Snd's data in samples is greater than the
horizontal length of the SndView in display
units. The SndView employs a reduction
factor to determine the ratio of samples to display units and plots
the minimum and maximum amplitude values of the samples within that
ratio. For example, a reduction factor of 10.0 means that the minimum
and maximum values among the first ten samples are plotted in the
first display unit, the minimum and maximum values of the next ten
samples are displayed in the second display unit and so on. You can
set the reduction factor through the setReductionFactor: method.
Changing the reduction factorchanges the time scale of the object. As you increase the
reduction factor, more “sound-per-inch” is displayed. Of
course, since more samples are used in computing the average
amplitude, the resolution in a SndView with a
heightened reduction factor is degraded. Conversely, reducing the
reduction factor displays fewer samples per display unit but with an
improved resolution. You should be aware that changing the reduction
factor on a large sound can take a noticeably long time.
SndView DimensionsIn a SndView, time runs from left to
right; amplitude is represented on the y-axis, with 0.0 amplitude in
the (vertical) center. When you set a
SndView's Snd, the
amplitude data that's displayed is automatically scaled to fit within
the given height of the SndView.
The manner in which a SndView's
horizontal dimension is computed depends on the object's autoscale flag. If autoscaling is turned off,
the length of a SndView's frame is resized to
fit the length of the Snd object's data while
maintaining a constant reduction factor. In other words, a
SndView that's displaying a
Snd that contains 10000 samples will be twice
as long as one with a Snd that contains 5000
samples, given the same reduction factor in either
SndView.
Whenever the displayed data changes, due to editing or
recording, the SndView is resized to fit the
length of the new data. This is particularly useful in a
SndView that's inside a
NSScrollView: The
NSScrollView determines the portion of data
that's actually displayed, while the SndView
maintains a constant time scale. Changing the reduction factor with
autoscaling turned off causes the SndView to
zoom in or out on the displayed data.
You can enable autoscaling by sending the message:
/* Enable autoscale. */ [mySoundView setAutoscale:YES]; |
With autoscale enabled, the
SndView's frame size is maintained regardless
of the length of the SndView's
Snd data. Instead, the reduction
factor is recomputed so the length of
the data will fit within the frame. When autoscaling is on, invoking
setReductionFactor: has no effect.
A SndView can display a sound as a
continuous waveform, such as you would see on an oscilloscope, or as
an outline of its maximum and minimum amplitudes. You set a
SndView's display mode by sending it the
setDisplayMode: message with one of
the following SndKit constants as an
argument:
Table 2-3. Display Mode Constants
| Constant | Meaning |
|---|---|
SK_DISPLAY_WAVE | Waveform display |
SK_DISPLAY_MINMAX | Amplitude outline display |
Waveform display is the default.
SndView SelectionThe SndView class provides a selection
mechanism. You can selectively enable the selection mechanism for
each SndView object by sending the setEnabled:YES message. When you drag in an
enabled SndView display, the selected region
is highlighted. The method getSelection:size: returns, by reference, the
number of the first sample and the number of samples in the
selection.