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

IOAudio

Inherits From:	IODirectDevice : IODevice : Object

Declared In:	driverkit/IOAudio.h

Class Description

IOAudio is an abstract class for controlling sound cards. It works closely with the Sound Kit, interpreting messages from user-level programs into method invocations in the driver. IOAudio has three threadsÐone that listens for messages from user-level programs, one that waits for sound-related keyboard events such as Insert (which raises the volume), and one that serves as the I/O thread. Only the I/O thread is used to invoke subclass methods that might need access to the hardware. 

Audio drivers have some restrictions. Because they're closely tied to the Window Server, for security reasons, you can't start up an audio driver at just any time. Instead, it's easiest to reboot to load a new version of an audio driver. Because the Sound Kit currently has no way to choose between audio drivers, only one IOAudio driver instance at a time can run. 

To play (output) sound data, IOAudio mixes together the data (obtained from NXPlayStreams) into a circular DMA buffer. If a DMA transfer isn't already in progress, IOAudio invokes startDMAForChannel:read:buffer:bufferSizeForInterrupts: (a hardware-specific method). After the number of bytes specified by bufferSizeForInterrupts has been transferred, the hardware interrupts; IOAudio zeros out the just-transferred part of the buffer and puts more data into it, if possible. In this way, DMA proceeds continuously until no more data is left to be transferred. When no more data is left (all the NXPlayStreams have completed), IOAudio invokes stopDMAForChannel:read:.

Note:  The word ªchannelº has two meanings in sound-related API. It can refer to a DMA channel, or to a sound channel. A sound channel is a transmission path for sound. IOAudio currently supports either 1 (mono) or 2 (stereo) sound channels for each DMA transfer.

The sample rate, data encoding, and number of sound channels used for a DMA transfer remain the same from the time startDMA... is invoked until the time stopDMA... is invoked. Their values are taken from the first NXPlayStream associated with the DMA transfer.

Recording sound data is similar to playing it. One DMA buffer exists for playing sound, and one for recording it. The buffers can share a DMA channel, or they can each have their own. Either way, IOAudio currently schedules transfers on only one channel at a time; that is, simultaneous playback and recording isn't allowed. In the future, support may be added for using both channels simultaneously. 

	Warning:	Currently, the DMA buffer size is 64KB for ISA-based systems and 128K for EISA-based systems, and the interrupt interval is 8KB. You should not depend on either the size or number of these buffersÐthey will change in future releases.

Implementing a Subclass

Your subclass of IOAudio must implement the following methods:

·	probe: (IODevice class method)
·	reset
·	startDMAForChannel:read:buffer:bufferSizeForInterrupts:
·	stopDMAForChannel:read:
·	interruptClearFunc (and its associated function)
·	interruptOccurredForInput:forOutput:
·	channelCountLimit
·	getDataEncodings:count:
·	getSamplingRatesLow:high:
·	getSamplingRates:count:

Your subclass should implement the following methods if the hardware supports the associated feature. For example, if your hardware supports loudness enhancement, you should implement updateLoudnessEnhanced.

·	updateLoudnessEnhanced
·	updateInputGainLeft
·	updateInputGainRight
·	updateOutputMute
·	updateOutputAttenuationLeft
·	updateOutputAttenuationRight

Besides implementing the methods listed above, you might also need to implement the following:

·	acceptsContinuousSamplingRates
·	timeoutOccurred

Note:  In the future, subclasses may be able to override methods that interpret NXSoundParameterTags passed from user-level programs. This mechanism will allow your subclass to interpret device-specific parameters.

Instance Variables

None declared in this class.

Method Types

Creating and freeing instances	- initFromDeviceDescription:
- free
- reset

Starting and stopping DMA	- startDMAForChannel:read:buffer:
		bufferSizeForInterrupts:
- stopDMAForChannel:read:

Getting DMA buffer address and size
- getInputChannelBuffer:size:
- getOutputChannelBuffer:size:

Handling interrupts	- interruptOccurredForInput:forOutput:
- interruptClearFunc

Getting notification of I/O thread difficulties	
- timeoutOccurred

Getting and setting information about sound channels	
- channelCountLimit
- isInputActive
- isOutputActive

Getting supported sampling rates	
- acceptsContinuousSamplingRates
- getSamplingRates:count:
- getSamplingRatesLow:high:

Getting supported data encodings
- getDataEncodings:count:

Getting device settings	- channelCount
- dataEncoding
- sampleRate

Determining what hardware settings are or should be
- inputGainLeft
- inputGainRight
- isOutputMuted
- isLoudnessEnhanced
- outputAttenuationLeft
- outputAttenuationRight

Setting hardware state	- updateInputGainLeft
- updateInputGainRight
- updateOutputMute
- updateLoudnessEnhanced
- updateOutputAttenuationLeft
- updateOutputAttenuationRight

Instance Methods

acceptsContinuousSamplingRates

- (BOOL)acceptsContinuousSamplingRates

Returns NO. Drivers that accept continuous sampling rates, as opposed to accepting a few, discrete sampling rates, should implement this method so that it returns YES. For example, if a device has a low rate of 2000 Hz and a high rate of 44100 Hz and supports every sampling rate in between, its implementation of this method should return YES. 

See also:  - getSamplingRates:, - getSamplingRatesLow:High:

channelCount

- (unsigned int)channelCount

Returns the number of sound channels to be used for the audio data that's about to be played or recorded. This value, which can be either 1 (for mono) or 2 (for stereo), is determined during mixing and is set before startDMAForChannel:... is invoked.

Note:  The number of sound channels has nothing to do with the number of DMA channels used by the device.

See also:  - dataEncoding, - sampleRate

channelCountLimit

- (unsigned int)channelCountLimit

Returns zero. Drivers must implement this method so that it returns either 1 (if only mono is supported) or 2 (if both mono and stereo are supported). 

See also:  - channelCount

dataEncoding

- (NXSoundParameterTag)dataEncoding

Returns the data encoding to be used for the audio data that's about to be played or recorded. This value is determined during mixing and is set before startDMAForChannel:... is invoked. Possible values (defined in the header file soundkit/NXSoundParameterTags.h) are currently NX_SoundStreamDataEncoding_Linear16, NX_SoundStreamDataEncoding_Linear8, NX_SoundStreamDataEncoding_Mulaw8, and NX_SoundStreamDataEncoding_Alaw8. 

See also:  - channelCount, - sampleRate

free

- free

Frees the instance and returns nil.

getDataEncodings:count:

- (void)getDataEncodings:(NXSoundParameterTag *)encodings count:(unsigned int *)numEncodings 

Returns zero in numEncodings. Subclasses must override this method to supply an array of supported data encodings. Possible values (defined in the header file soundkit/NXSoundParameterTags.h) are currently NX_SoundStreamDataEncoding_Linear16, NX_SoundStreamDataEncoding_Linear8, NX_SoundStreamDataEncoding_Mulaw8, and NX_SoundStreamDataEncoding_Alaw8. Below is an example of implementing this method. Note that you don't have to allocate memory for encodings; it already has enough space to hold all possible encodings.

- (void)getDataEncodings: (NXSoundParameterTag *)encodings
                   count:(unsigned int *)numEncodings
{
    encodings[0] = NX_SoundStreamDataEncoding_Linear16;
    encodings[1] = NX_SoundStreamDataEncoding_Linear8;
    *numEncodings = 2;
}

getInputChannelBuffer:size:

- (void)getInputChannelBuffer:(void *)address size:(unsigned int *)byteCount

Gets the starting address and size of the (already allocated) DMA buffer for the input channel. This method allows the driver to access data in the audio buffer directly.  

See also:  - getOutputChannelBuffer:size:

getOutputChannelBuffer:size:

- (void)getOutputChannelBuffer:(void *)address size:(unsigned int *)byteCount

Gets the starting address and size of the (already allocated) DMA buffer for the output channel. This method allows the driver to access data in the audio buffer directly.

See also:  - getInputChannelBuffer:size:

getSamplingRates:count:

- (void)getSamplingRates:(int *)rates count:(unsigned int *)numRates 

Returns zero in numRates. Subclasses must override this method to supply the supported sampling rates in rates array, which has room for up to 256 entries. If the driver supports continuous sampling rates, this method should return some common sampling rates, as shown below.

- (void)getSamplingRates:(int *)rates
                   count:(unsigned int *)numRates
{
    /* Return a few common rates */
    rates[0] = 2000;
    rates[1] = 8000;
    rates[2] = 11025;
    rates[3] = 16000;
    rates[4] = 22050;
    rates[5] = 32000;
    rates[6] = 44100;
    *numRates = 7;
}

See also:  - acceptsContinuousSamplingRates, - getSamplingRatesLow:High:

getSamplingRatesLow:high:

- (void)getSamplingRatesLow:(int *)lowRate high:(int *)highRate 

Returns zero in lowRate and highRate. Subclasses must override this method to supply their highest and lowest supported sampling rates. Here's an example of implementing this method.

- (void)getSamplingRatesLow:(int *)lowRate
                       high:(int *)highRate
{
    *lowRate = 2000;
    *highRate = 44100;
}

See also:  - acceptsContinuousSamplingRates, - getSamplingRates:

initFromDeviceDescription:

- initFromDeviceDescription:description 

Initializes a newly allocated IOAudio instance. Subclasses don't generally override this method; they merely invoke it in their probe: method. Subclasses perform device-specific initialization in their implementation of the reset method.

IOAudio's implementation of initFromDeviceDescription: invokes super's version of initFromDeviceDescription:, invokes attachInterruptPort, sets the interrupt port to have a maximum backlog, and then performs the reset method. Next, it creates and initializes the private objects that perform much of the driver's work, creates private ports, and forks threads to listen to requests on the ports. Finally, it invokes registerDevice. Returns nil if initialization was unsuccessful; otherwise, returns the IOAudio instance.

inputGainLeft

- (unsigned int)inputGainLeft

Returns the general scaling factor that's applied to the left channel of the incoming sound. This value can be anywhere from 0 to 32768, where 0 is no gain and 32768 is maximum gain. User-level programs specify the gain using the Sound Kit. To support input gain, you must implement updateInputGainLeft and updateInputGainRight.

See also:  - inputGainRight

inputGainRight

- (unsigned int)inputGainRight

Returns the general scaling factor that's applied to the right channel of the incoming sound. This value can be anywhere from 0 to 32768, where 0 is no gain and 32768 is maximum gain. User-level programs specify the gain using the Sound Kit. To support input gain, you must implement updateInputGainLeft and updateInputGainRight.

See also:  - inputGainLeft

interruptClearFunc

- (IOAudioInterruptClearFunc)interruptClearFunc

Does nothing and returns zero. Subclasses must implement this method so that it returns the address of a function that clears interrupts on the card. The function is called only when the audio system needs to guarantee that your card has no pending interrupts. If you don't implement this method and function, your card is likely to suffer from poor performance with some applications. The function runs at interrupt level, so it must not block. 

Here's an example of implementing this method.

static void clearInterrupts(void)
{
    /* Driver-specific code that clears the card's interrupt
     * register(s) goes here. */ 
}

- (IOAudioInterruptClearFunc) interruptClearFunc
{
    return clearInterrupts;
}

interruptOccurredForInput:forOutput:

- (void)interruptOccurredForInput:(BOOL *)serviceInput
forOutput:(BOOL *)serviceOutput 

Notifies the instance that an interrupt occurred for its hardware. The IOAudio version of this method generates an error message; each subclass must implement this method.

The subclass implementation of this method should try to determine whether the hardware really has interrupted. If so, this method should clear the card's interrupt state, set serviceInput to YES if the interrupt was for input, and set serviceOutput to YES if the interrupt was for output. (The values of serviceInput and serviceOutput are initialized to NO.) 

After invoking this method, IOAudio checks whether any more data is available for DMA on the channels that require service. If none is available, stopDMAForChannel:read: is invoked. IOAudio always invokes this method from the I/O thread.

isInputActive

- (BOOL)isInputActive

Returns YES if data is being read from the hardware using DMA; otherwise, returns NO.

See also:  - isOutputActive

isLoudnessEnhanced

- (BOOL)isLoudnessEnhanced

Returns YES if loudness is enhanced; otherwise, returns NO. Loudness enhancement refers to the ability of some hardware to help compensate for the decreased sensitivity of the human ear by boosting the gain at low and high frequencies as the volume is decreased. User-level programs specify whether to use loudness enhancement with the NX_SoundDeviceOutputLoudness parameter. To support loudness enhancement, you must implement updateLoudnessEnhanced.

isOutputActive

- (BOOL)isOutputActive

Returns YES if data is being sent to the hardware using DMA; otherwise, returns NO.

See also:  - isInputActive

isOutputMuted

- (BOOL)isOutputMuted

Returns YES if output is muted; otherwise, returns NO. The user can mute audio output by holding down the Command key and pressing the Delete key. User-level programs can mute output using the Sound Kit. 

See also:  - updateOutputMute

outputAttenuationLeft

- (int)outputAttenuationLeft

Returns the attenuation setting of the left channel of the device. The user modifies the left and right attenuation simultaneously using the Volume slider in the Preferences application or with the Insert and Delete keys on the keyboard. User-level programs can specify the attenuation using the Sound Kit. The range is -84 decibels (inaudible) to 0 decibels (no attenuation).

See also:  - updateOutputAttenuationLeft, - outputAttenuationRight

outputAttenuationRight

- (int)outputAttenuationRight

Returns the attenuation setting of the right channel of the device. The user modifies the left and right attenuation simultaneously using the Volume slider in the Preferences application or with the Insert and Delete keys on the keyboard. User-level programs can specify the attenuation using the Sound Kit. The range is -84 decibels (inaudible) to 0 decibels (no attenuation).

See also:  - updateOutputAttenuationRight, - outputAttenuationLeft

reset

- (BOOL)reset

Generates an error message and returns NO. Subclasses must implement this method so that it resets and initializes the hardware. This method is invoked from initFromDeviceDescription:, as described above.

This method should initialize basic information by invoking setName: and setDeviceKind:. It should then check whether its interrupt (IRQ) and DMA channels (all obtained from its IODeviceDescription) have valid values. After initializing the hardware, this method should disable its DMA channels and then set any DMA parameters necessary, such as the transfer width.

This method should return YES on success; otherwise, it should return NO, which will cause initFromDeviceDescription: to return nil.

See also:  - initFromDeviceDescription:, - setName: (IODevice), - setDeviceKind: (IODevice)

sampleRate

- (unsigned int)sampleRate

Returns the sample rate to be used for the audio data that's about to be played or recorded. This value is determined during mixing and is set before startDMAForChannel:... is invoked.

See also:  - channelCount, - dataEncoding

startDMAForChannel:read:buffer:bufferSizeForInterrupts:

- (BOOL)startDMAForChannel:(unsigned int)localChannel 
read:(BOOL)isRead 
buffer:(IODMABuffer)buffer 
bufferSizeForInterrupts:(unsigned int)bufferSize 

Generates an error message and returns NO. Subclasses must override this method.

This method should perform DMA after configuring the hardware to reflect the values returned by sampleRate, dataEncoding, and channelCount. The DMA should be set up so that it generates an interrupt after every bufferSize byte interval. If isRead is YES, then the DMA is from the card to memory; otherwise, DMA is from memory to the card. See the example IOAudio driver for an implementation of this method.

IOAudio invokes this method from the I/O thread. You should never invoke this method in an IOAudio subclass implementation.

This method should return YES if it started DMA successfully; otherwise, it should return NO.

See also:  - startDMAForBuffer:channel (IODirectDevice architecture-specific category), - enableChannel (IODirectDevice architecture-specific category), - enableAllInterrupts (IODirectDevice architecture-specific category)

stopDMAForChannel:read:

- (void)stopDMAForChannel:(unsigned int)localChannel read:(BOOL)isRead 

Generates an error message. Subclasses must override this method.

This method should disable the specified DMA channel, disable interrupts, and do anything else necessary to stop the DMA in progress on localChannel. See the example IOAudio driver for an implementation of this method.

IOAudio invokes this method from the I/O thread. You should never invoke this method in an IOAudio subclass implementation.

This method is invoked when an interrupt occurs and no more data is available to be transferred. It's also invoked any time that startDMAForChannel:... returns NO.

See also:  - startDMAForChannel:read:buffer:bufferSizeForInterrupts:, - disableChannel (IODirectDevice architecture-specific category), - disableAllInterrupts (IODirectDevice architecture-specific category)

timeoutOccurred

- (void)timeoutOccurred

Notifies the instance that although a DMA transaction is in progress, no interrupts have been detected for a long time (currently one second). The IOAudio version of this method does nothing; each subclass can implement it or not.

The subclass implementation of this method might reset the hardware. IOAudio invokes this method from the I/O thread.

updateInputGainLeft

- (void)updateInputGainLeft

Does nothing. Subclasses should implement this method so that it updates the hardware to the value returned by inputGainLeft. You generally have to convert the device-independent value returned by inputGainLeft to the appropriate value for your device.

- (void) updateInputGainLeft
{
    /* Convert gain (0 - 32768) into attenuation (0 - 31). */
    unsigned int gain = [self inputGainLeft] / 1057;
    
    setInputAttenuation(MICROPHONE, LEFT_CHANNEL, 
                        (unsigned char) gain);
    setInputAttenuation(EXTERNAL_LINE_IN, LEFT_CHANNEL, 
                        (unsigned char) gain);
}

IOAudio invokes this method from the I/O thread.

See also:  - updateInputGainRight

updateInputGainRight

- (void)updateInputGainRight

Does nothing. Subclasses should implement this method so that it updates the hardware to match the value returned by inputGainRight. You generally have to convert the device-independent value returned by inputGainRight to the appropriate value for your device. IOAudio invokes this method from the I/O thread. 

See also:  - updateInputGainLeft

updateLoudnessEnhanced

- (void)updateLoudnessEnhanced

Does nothing. Subclasses that support loudness enhancement should implement this method so that it updates the hardware to match the value returned by isLoudnessEnhanced. IOAudio invokes this method from the I/O thread.

updateOutputAttenuationLeft

- (void)updateOutputAttenuationLeft

Does nothing. Subclasses should implement this method so that it updates the hardware to match the value returned by outputAttenuationLeft. You generally have to convert the device-independent value returned by outputAttenuationLeft to the appropriate value for your device. Here's an example of implementing this method.

- (void) updateOutputAttenuationLeft
{
    /* Get the software value and convert it from the software range
     * (0 - -84) to the device range (0 - 31, for this card). */
    unsigned int attenuation = [self outputAttenuationLeft] + 84;
    attenuation = ((attenuation * 10)/27);
    
    /* Device-specific code sets the output attention of the left
     * sound channel to the value of the attenuation variable. */
}

IOAudio invokes this method from the I/O thread.

See also:  - updateOutputAttenuationRight

updateOutputAttenuationRight

- (void)updateOutputAttenuationRight

Does nothing. Subclasses should implement this method so that it updates the hardware to match the value returned by outputAttenuationRight. You generally have to convert the device-independent value returned by outputAttenuationRight to the appropriate value for your device. IOAudio invokes this method from the I/O thread.

See also:  - updateOutputAttenuationLeft

updateOutputMute

- (void)updateOutputMute

Does nothing. Subclasses should implement this method so that it mutes the output if isOutputMuted returns YES and unmutes the output if isOutputMuted returns NO. IOAudio invokes this method from the I/O thread.