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 Snd
s 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 Snd
s 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,
NSButton
s. 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
NSButton
s, 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
Snd
s 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
Snd
s 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 Snd
s 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 Snd
s 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 Snd
s
without incurring the cost of moving large sections of data in memory.
However, fragmented Snd
s 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:.
SndView
s 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.
SndView
To 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.