Copyright ©1995 by NeXT Computer, Inc.  All Rights Reserved.

NXSoundDevice



Inherits From: Object
Conforms To: NXSoundParameters
Declared In: soundkit/NXSoundDevice.h



Class Description

NXSoundDevice is an abstract superclass; each subclass represents a sound input or output device. The  Sound Kit provides two subclasses of NXSoundDevice:

NXSoundIn represents sound input.
NXSoundOut represents sound output.

The utility of NXSoundDevice is invested in these subclasses; the NXSoundDevice class itself simply defines methods that are common to them.  In addition, you can't create useful subclasses of NXSoundDevice yourself.

Many applications needn't bother with NXSoundDevice and its subclasses; in general, the methods provided by the Sound class are sufficicient for applications that record and play sounds.  However, while Sound objects are easy to use, NXSoundDevices give you greater of sound resources.  The primary advantages of NXSoundDevice objects are that you can:

Reserve sound devices for exclusive use by your application.
Specify the host computer of the device that you want to use.
Specify the size and number of the sound data buffers used by the device.


Initializing and Reserving a Sound Device

You initialize an NXSoundDevice by connecting it to a sound driver.  The init method connects it to the sound driver on the local host; initOnHost: lets you access the sound driver on some other machine.  A single NXSoundDevice can be connected to only one sound driver (one host) at a time.

Underneath the two NXSoundDevice subclasses lie specific sound driver devices:  NXSoundIn represents the sound-in device, and NXSoundOut represents the sound-out device.  You can connect any number of NXSoundOut and NXSoundIn instances to the same sound driver; in other words, you can initialize them on the same host.

You can reserve a sound device for exclusive use by a particular NXSoundDevice object through the setReserved: method. While you have it reserved, no other application, nor any other NXSoundDevice within your application, can use your sound device.  If it's unreserved, the sound device is shared:  For sound-in, this means that more than one application can get a copy of the same recording.  For sound-out, more than one application can play sounds at the same time (see the NXSoundOut class description for more on mixing sounds during playback).

Warning: The device reservation mechanism only ensures that no other application will be able to share a device with you--it doesn't prevent the device from being stolen altogether.  If you reserve a device, and then a competing application comes along and reserves the same device, this second reservation will win and you'll be left with memories.
Devices and Streams
By themselves, NXSoundDevice objects are of limited use.  An NXSoundOut object, for example, lets you manipulate the sound-out device, but it doesn't provide data to the device, thus the object isn't capable of making any sound.  For this, you need to connect an NXPlayStream object to the NXSoundOut.  (This is done by passing the NXSoundOut as the argument to NXPlayStream's initOnDevice: method.)  Similarly, to record sound you must connect an NXRecordStream to an NXSoundIn object.
NXPlayStream and NXRecordStream inherit from NXSoundStream, an abstract superclass that defines methods for its subclasses in much the same way that NXSoundDevice embodies the common functionality of NXSoundIn and NXSoundOut. You can connect more than one NXSoundStream to the same NXSoundDevice, but the NXRecordStream/NXSoundIn, NXPlayStream/NXSoundOut marriages must be honored; you can't connect an NXPlayStream to an NXSoundIn, for example.
An NXSoundDevice can pause, resume, and abort all the NXSoundStreams that are connected to it.  Each NXSoundDevice controls only its own NXSoundStreams; for example, if you initialize two NXSoundOut objects on the same host and connect a pair of NXPlayStreams to each (for a total of four NXPlayStreams), sending a pauseStreams: message to one of the NXSoundOuts will pause only the two streams that are connected to it--the other two streams are unaffected, even though both NXSoundOut objects are connected to the same sound driver device.  This ability--to independently control groups of sound streams--is the only reason for creating multiple NXSoundDevices for the same driver device.  You should never otherwise need to create more than one NXSoundOut and one NXSoundIn per host machine.
Sound Device Parameters
Many of the attributes of an NXSoundDevice object are set through NXSoundParameters protocol methods.  See the NXSoundParameters protocol specification for descriptions of parameter-setting and -getting methods.  There are three parameter keys that are common to the two NXSoundDevice subclasses:

Common NXSoundDevice Parameter Keys

NX_SoundDeviceBufferSize
NX_SoundDeviceBufferCount
NX_SoundDeviceDetectPeaks

The NXSoundOut parameter keys are these:

NXSoundOut Parameter Keys

NX_SoundDeviceRampUp
NX_SoundDeviceRampDown
NX_SoundDeviceInsertZeros
NX_SoundDeviceDeemphasize
NX_SoundDeviceMuteSpeaker
NX_SoundDeviceMuteHeadphone
NX_SoundDeviceMuteLineOut
NX_SoundDeviceOutputLoudness
NX_SoundDeviceOutputAttenuationStereo
NX_SoundDeviceOutputAttenuationLeft
NX_SoundDeviceOutputAttenuationRight
NX_SoundDeviceMonitorAttenuation

And these are the NXSoundIn keys:

NXSoundIn Parameter Keys

NX_SoundDeviceAnalogInputSource
NX_SoundDeviceInputGainStereo
NX_SoundDeviceInputGainLeft
NX_SoundDeviceInputGainRight

Warning: Setting the values of any of these parameters affects the underlying device.  In other words, all NXSoundOut objects that are initialized on a particular host will always have the same set of values for these three parameters; a change to one of the objects affects all the others.  Similary are all NXSoundIn objects affected by changes to a single NXSoundIn.
Some of the parameter value-retrieving methods defined by NXSoundDevice are meant to be used to configure the NXSoundStream objects that you attach to a particular NXSoundDevice.  Specifically, methods are provided that let you ask for the sampling rates, channel counts, and data formats (encodings) that are supported by the underlying hardware.  See the "Setting up streams" method type for a list of these methods.
Sound Buffers
The sound-in and sound-out devices parcel sound data into equally sized "device buffers" and then send these buffers to the sound hardware.  The size (in bytes) of these buffers is controlled by the NX_SoundDeviceBufferSize parameter; the largest a device buffer can be is a page of virtual memory, as given by the global variable vm_page_size.  By default, sound-in buffers are 256 bytes and sound-out buffers are a page of virtual memory.
You can also specify the number of device buffers, through the NX_SoundDeviceBufferCount parameter.  By default, both sound-in and sound-out use four buffers.  Device buffers are sent as soon as they're filled; by maintaining more than one buffer, the sound driver is able to "run ahead," filling the extra buffers while the first one is being played (or emptied, in the case of recording).  This provides a sort of cushion, allowing the buffer-filling process (which is run in a background thread) to procede in jerks and starts while sound is played back (or recorded) without interruption.
Note that if you resize or renumber the  device buffers, all may be for nought unless you also adjust the size of the "stream buffers" that you enqueue on the NXSoundStream that you're using.  For the best fit, the stream buffers should be no larger than the device buffer size times the device buffer count.
The buffer attributes for a particular device are reset to their defaults when there are no active streams connected to the device. Because of this, you should always connect an NXSoundStream to your NXSoundDevice and open the stream (through NXSoundStream's activate method) before setting the device buffer size and count.
Peak Detection
You can set an NXSoundDevice to detect the peak, or maximum absolute amplitude, within   each device buffer of data.  To enable this feature, you must set the boolean NX_SoundDeviceDetectPeaks parameter to YES.
You can retrieve the most recent peak amplitudes (in stereo) through the getPeakLeft:right: method.  The peak values returned by the method are normalized to fall within (0.0, 1.0), where 0.0 is no amplitude and 1.0 is the maximum amplitude supported by the data format.  Old peak data is thrown away as the most recent peaks are detected--if you want an on-going and thorough chart of the peaks, you must query for this data promptly and consistently while sound is recording or playing.  Typically, you set up a timed entry to invoke getPeakLeft:right: at a frequency that matches the rate at which device buffers are transfered.
The NXSoundDevice itself doesn't perform the peak detection--it's done by the underlying device.  This has a particular significance for sound-out:  The data that's returned by getPeakLeft:right: is the peak amplitude of all sounds that are being played, not just those NXPlayStreams that are connected to the queried NXSoundOut object.
Sound Driver Reply Messages
As it's processing a sound stream, the sound driver sends Mach messages to a reply port that's managed by the NXSoundDevice class object.  Each message contains either a status report--whether a sound has started, completed, aborted, and so on--or, in the case of sound-in, a buffer of recorded sound data.
The NXSoundDevice class object interprets each of these driver messages and sends a corresponding Objective C message to the delegate of the appropriate NXSoundStream object (as described in the class specifications of NXSoundStream, NXPlayStream, and NXRecordStream).  You can repress these messages by lowering the device's "thread threshold," as explained in the setThreadThreshold: method.
If you want the reply messages to be delivered as promptly as possible, you should create a separate thread in which the messages are received.  This is done by sending setUseSeparateThread:YES to the NXSoundDevice class object.  The thread threshold setting has no effect if you're using a separate thread.
Although using a separate thread increases responsiveness to the sound driver, it may degrade overall system performance, thus degrading the synchronization between sound and graphics (for example).  Also, if you're using a separate thread, the NXSoundStream delegate methods that are ultimately invoked mustn't invoke methods or call functions that aren't re-entrant, nor should they cause code to be sent to the PostScript interpreter--in other words, they mustn't draw.
To have an effect, setUseSeparateThread: and setThreadThreshold: must be invoked before any NXSoundDevice objects are initialized.
Sound Device Timeout
The NXSoundDevice and NXSoundStream methods that communicate with the underlying driver do so by sending Mach messages to the driver.  Such a method sends the Mach message and then waits for a reply from the driver; the method doesn't return--and your application hangs--until the driver responds.  On a local host this usually isn't a concern--if your application is running, then the sound driver is running.  However, if your application is using the sound driver on a remote host, this assurance is less certain; for example, if the remote host is powered off, then your application will hang when a driver-accessing method is invoked.
You can specify the maximum amount of time to wait for the sound driver to respond through NXSoundDevice's setTimeout: class method.  The method sets, in milliseconds, the time limit for all sound devices.   If the sound driver doesn't respond to a Mach message within the given amount of time, the method that caused the message to be sent is forced to return with the error code NX_SoundDeviceErrorTimeout (the sound device error codes are listed in the section "Types and Constants" under the heading "NXSoundDeviceError").
Although you can set the time limit to an excruciatingly specific interval, it's perhaps better thought of as acting as a boolean that determines whether your application can hang forever or not.  The default setting (NX_SOUNDDEVICE_TIMEOUT_MAX) is extremely, perhaps frustratingly, patient.  A more urgent setting, say ten seconds or so, will ensure that your application won't hang forever, while allowing enough time for even the sleepiest driver to wake up.
The Sound Kit methods that access the sound driver, and so are liable to the time limit, are those that return an NXSoundDeviceError value (except for NXSoundDriver's lastError), plus these methods:

Method Class
isReserved NXSoundDevice
pauseStreams: NXSoundDevice
resumeStreams: NXSoundDevice
abortStreams: NXSoundDevice
clipCount NXSoundDevice
pause: NXSoundStream
resume: NXSoundStream
abort: NXSoundStream
bytesProcessed NXSoundStream



Instance Variables

None declared in this class.



Adopted Protocols

NXSoundParameters boolValueForParameter:
floatValueForParameter:
getParameters:count:
getValues:count:forParameter:
intValueForParameter:
isParameterPresent:
removeParameter:
setParameter:toBool:
setParameter:toFloat:
setParameter:toInt:



Method Types

Initializing and freeing an NXSoundDevice
init
initOnHost:
free
Using a separate thread + replyThread
+ isUsingSeparateThread
+ setThreadThreshold:
+ setUseSeparateThread:
+ threadThreshold
Examining ports devicePort
+ replyPort
streamOwnerPort
Identifying the host computer host
Configuring the object isReserved
setReserved:
setParameters:
parameters
name
+ setTimeout:
+ timeout
Retrieving peak amplitudes getPeakLeft:right:
clipCount
Setting up streams acceptsContinuousStreamSamplingRates
getStreamChannelCountsLimit:
getStreamSamplingRates:low:high:
getStreamSamplingRates:count:
getStreamDataEncodings:count:
Controlling streams abortStreams:
pauseStreams:
resumeStreams:
Handling errors lastError
+ textForError:



Class Methods

isUsingSeparateThread
+ (BOOL)isUsingSeparateThread

Returns YES if the NXSoundDevice is using a separate thread to process messages from the driver to the reply port; otherwise, returns NO.

See also:  + setUseSeparateThread:



replyPort
+ (port_t)replyPort

Returns the port to which the sound driver sends reply messages.  You can't set this port yourself, and you normally don't need to note its identity; this method is provided so you can pass the reply port as an argument to a function such as port_status().



replyThread
+ (cthread_t)replyThread

Returns the thread in which messages from the sound driver are sent to the reply port.  If the NXSoundDriver isn't using a separate thread for these messages, this returns NO_CTHREAD.  The cthread_t type is defined in mach/cthreads.h.

See also:  + setUseSeparateThread:



setThreadThreshold:
+ setThreadThreshold:(int)threshold

Sets the threshold against which the application's current event threshold is compared as messages arrive from the driver.  If threshold is higher than the event threshold, the message is delivered to the reply port (and so a message is sent to the delegate of the appropriate NXSoundStream), otherwise the message is ignored.  By default, threshold is NX_MODALRESPTHRESHOLD, as defined in appkit/Application.h.  If the NXSoundDevice is using a separate thread to receive driver messages, this method has no effect (messages are always received).

This method does nothing and returns nil if your application contains any initialized NXSoundDevice objects.  Otherwise it returns self.

See also:  + threadThreshold, + isUsingSeparateThread:



setTimeout:
+ setTimeout:(unsigned int)milliseconds

Sets the length of time, in milliseconds, that all sound devices will wait for a method that communicates with the sound driver to return, as explained in the class description, above.  Returns self.

See also:  + timeout



setUseSeparateThread:
+ setUseSeparateThread:(BOOL)flag

If flag is YES, the NXSoundDevice class object will use a separate thread for processing messages from the sound driver.  The NXSoundDevice class object interprets these Mach messages and sends corresponding Objective-C messages to the delegate of the appropriate NXSoundStream instance.  If flag is NO, the sound driver messages are processed in the application's main thread.

This method does nothing and returns nil if your application contains any initialized NXSoundDevice objects.  Otherwise it returns self.

See also:  + isUsingSeparateThread



textForError:
+ (const char *)textForError:(NXSoundDeviceError)errorCode

Returns a localized string that corresponds to errorCode.  The sound device error codes are listed in the "Constants and Types" section.

See also:  lastError



threadThreshold
+ (int)threadThreshold

Returns the threshold that's used to determine whether messages from the sound driver are ignored.  The default is NX_MODALRESPTHRESHOLD, as defined in appkit/Application.h.

See also:  + setThreadThreshold:



timeout
+ (unsigned int)timeout

Returns the amount of time, in milliseconds, that driver-accessing methods are allowed to hang before being forced to return, as explained in the class description, above.  The default is NX_SOUNDDEVICE_TIMEOUT_MAX (essentially forever).

See also:  + setTimeout:



Instance Methods

abortStreams:
abortStreams:sender

Aborts all streams that are connected to the NXSoundDevice.  You should check the return value of lastError after invoking this method to see if an error occurred.  Returns self.

See also:  abort: (NXSoundStream), lastError



acceptsContinuousStreamSamplingRates
(BOOL)acceptsContinuousStreamSamplingRates

Returns YES if the underlying hardware accepts sampling rate values that are continuous (in other words, that are non-discrete). You use this method to determine the type of sampling rate values that you can set in a NXSoundParameters object that's applied to an NXSoundStream that's attached to this NXSoundDevice.

See also:  getStreamSamplingRates:count:, getStreamSamplingRatesLow:high:



clipCount
(unsigned int)clipCount

Returns the number of sample frames that have been clipped since the activation of the oldest connected stream.  Clipping occurs when the amplitude of a sample is great than the greatest representable value for this device.  The clip count is reset to 0 when the last stream is deactivated.

See also:   getPeakLeft:right:



devicePort
(port_t)devicePort

Returns the port that the NXSoundDevice uses to communicate with the sound driver.  You can't set this port yourself, and you normally don't need to note its identity; this method is provided so you can pass the port as an argument to a function such as port_status().



free
free

Deallocates the NXSoundDevice's ports and frees the object.  If the NXSoundDevice had reserved the underlying sound device, it's made available again.

See also:  setReserved:



getStreamChannelCountLimit
(unsigned int)getStreamChannelCountLimit

Returns the maximum number of channels of sound data that are accepted by the underlying hardware.  To play a sound, you must set the channel count of any NXSoundStream object that's attached to this NXSoundDevice to a value not greater than that returned by this method.

See also:  getStreamSamplingRates:count:, getStreamSamplingRatesLow:high:, getStreamDataEncodings:count:



getStreamDataEncodings:count:
(NXSoundDeviceError)getStreamDataEncodings:
(const NXSoundParameterTag **)encodings
count:(unsigned int *)numEncodings

Returns, by reference in encodings, a list of the data encoding values that are accepted by the underlying hardware.  The encodings are represented by paramater value tags; the number of encodings in the list is returned by reference in numEncodings.  You then set an NXSoundStream object's encoding to a value plucked from the list.

See also:   getStreamChannelCountLimit:, getStreamSamplingRatesLow:high:, getStreamSamplingRates:count:



See also:



getStreamSamplingRates:count:
(NXSoundDeviceError)getStreamSamplingRates:(const float **)rates
count:(unsigned int *)numRates

Returns, by reference in rates, a list of the discrete sampling rates that are accepted by the underlying hardware.  The number of sampling rates in the list is returned by reference in numRates.  You then set an NXSoundStream object's sampling rate to a value plucked from the list.  You invoke this method only if the NXSoundDevice doesn't accept continuous sampling rate values (as determined by the acceptsContinuousStreamSamplingRates method).  If it accepts continuous values, use the getStreamSamplingRatesLow:high: method instead of this one.

See also:  acceptsContinuousStreamSamplingRates, getStreamSamplingRatesLow:high:, getStreamChannelCountLimit:, getStreamDataEncodings:count:



getStreamSamplingRates:low:high:
(NXSoundDeviceError)getStreamSamplingRatesLow:(float *)lowRate
high:(float *)highRate

Returns, by reference, the lowest and highest sampling rates that are accepted by the underlying hardware.  You then set an NXSoundStream object's sampling rate to a value in the returned range.  You invoke this method only if the NXSoundDevice accepts continuous sampling rate values (as determined by the acceptsContinuousStreamSamplingRates method).  If it only accepts discrete values, use the getStreamSamplingRates:count: method to return this information.

See also:  acceptsContinuousStreamSamplingRates, getStreamSamplingRates:count:, getStreamChannelCountLimit:, getStreamDataEncodings:count:



getPeakLeft:right:
(NXSoundDeviceError)getPeakLeft:(float *)leftAmp right:(float *)rightAmp

Returns the most recent peak amplitudes detected by the NXSoundDevice's underlying sound device.  For stereo sounds, peaks are detected independently for the two channels and returned by reference in leftAmp and rightAmp.  For monophonic sounds, the same value is returned in both arguments.  The peak values returned in the arguments are normalized to fall within (0.0, 1.0), where 0.0 is no amplitude and 1.0 is the maximum amplitude supported by the data format.  See the class description for more information on peak detection.  An error code is returned.

See also:  clipCount (NXSoundOut)



host
(const char *)host

Returns the name of the computer on which the NXSoundDevice was initialized, or nil if it's the local host.

See also:  initOnHost:



init
init

Initializes the NXSoundDevice on the machine specified by the NXHost default (normally the local host).  Returns nil if the sound resources cannot be accessed; otherwise returns self.

See also:  initOnHost:



initOnHost:
initOnHost:(const char *)hostName

Initializes the NXSoundDevice on the machine named hostName.  Returns nil if the sound resources cannot be accessed; otherwise returns self.

See also:  init



isReserved
(BOOL)isReserved

Returns YES if the device is reserved for exclusive access by this NXSoundDevice; otherwise, returns NO (the default).

See also:  setReserved:



lastError
(NXSoundDeviceError)lastError

Returns the most recent sound device error associated with the NXSoundDevice.  Many methods don't explicitly return an NXSoundDeviceError, but set an internal variable, which can be retrieved with this method.  To retrieve localized text that describes the error, pass the value returned by this method to the textForError: class method.

See also:  + textForError:



name
(const char *)name

Returns the name of the NXSoundDevice's underlying device, as registered with the network.



parameters
(id <NXSoundParameters>)parameters

Returns an object that contains the NXSoundDevice's parameter settings.  The parameters take default value settings in a freshly initialized NXSoundDeivce.

See also:  setParameters:



pauseStreams:
pauseStreams:sender

Pauses all streams that are connected to the NXSoundDevice.  You should check the return value of lastError after invoking this method to see if an error occurred.  Returns self.

See also:  pause: (NXSoundStream), lastError



resumeStreams:
resumeStreams:sender

Resumes all streams that are connected to the NXSoundDevice.  You should check the return value of lastError after invoking this method to see if an error occurred.  Returns self.

See also:  resume: (NXSoundStream), lastError



setParameters:
(NXSoundDeviceError)setParameters:(id <NXSoundParameters>)parameterObject

Sets the NXSoundDevice's parameter values to those specified in the argument.

See also:  parameters



setReserved:
(NXSoundDeviceError)setReserved:(BOOL)flag

If  flag is YES, reserves the underlying device for exclusive access by the NXSoundDevice (even if it's currently reserved by another NXSoundDevice--the current owner is forced to yield).  No other application, nor any other NXSoundDevice within your application, will be able access the device while it's reserved.  Any currently active streams not connected to this NXSoundDevice instance are aborted.  If flag is NO the device is made available to all NXSoundDevices.  NXSoundDevices are unreserved by default.  An error code is returned.

See also:  isReserved, soundStreamDidAbort:deviceReserved: (NXSoundStream delegate)



streamOwnerPort
(port_t)streamOwnerPort

Returns the port that the NXSoundDevice uses to connect to the sound driver.  You can't set this port yourself, and you normally don't need to note its identity; this method is provided in case you want to pass the port as an argument to a function such as port_status().