The MKOrchestra class manages DSP resources used in music synthesis/processing. Each instance of MKOrchestra represents a single DSP; in the basic NeXT configuration, there's only one DSP so you create only one MKOrchestra object.
The methods defined by the MKOrchestra class let you manage a DSP by allocating portions of its memory for specific synthesis/processing modules and by setting its processing characteristics.
You can allocate entire MKSynthPatches or individual MKUnitGenerator and MKSynthData objects through MKOrchestra methods. Primary among these are:
allocSynthPatch: allocates an instance of the given MKSynthPatch subclass.
allocUnitGenerator: does the same for a MKUnitGenerator subclass.
allocSynthData:length: allocates a portion of DSP memory of a given length.
Keep in mind, however, that similar methods defined in other classes―specifically, the MKSynthPatch allocation methods defined in MKSynthInstrument, and the MKUnitGenerator and MKSynthData allocation methods defined in MKSynthPatch―are built upon and designed to usurp the allocation methods defined by MKOrchestra. You only to need to allocate synthesis/processing objects directly if you want to assemble sound-making modules at a low level.
Different applications have different needs. When playing a sequence or scorefile, it is essential that the timing of the notes be precise and a bit of latency is tolerable. On the other hand, when playing the DSP from a MIDI keyboard, the critical element is to keep response time to a minimum. In the first case, you should set the MKOrchestra to “timed mode” by sending [orchestra setTimed:YES], while in the second case, you should set the MKOrchestra to “untimed mode” by sending [orchestra setTimed:NO]. In addition, when performing from a keyboard, you should call MKSetRealTimeEnvelopes(YES) before allocating your MKSynthPatches. This has the effect of setting the MusicKit MKSynthPatches to use a version of the envelope handler that is best-suited to the interactive situation. Actually, MKSetRealTimeEnvelopes(YES) may be used for either case; its only drawback is that it does not allow for arbitrarily long envelopes. In timed mode, the fixed latency is set by the function MKSetDeltaT(). For example, MKSetDeltaT(.1) sets a fixed latency of a tenth of a second.
To avoid creating duplicate synthesis/processing modules on the DSP, each instance of MKOrchestra maintains a shared object table. Objects on the table are MKSynthPatches, MKSynthDatas, and MKUnitGenerators; each is indexed by some other object that represents the shared object. For example, the OscgafUG MKUnitGenerator (a family of oscillators) lets you specify its waveform-generating wave table as a MKPartials object (you can also set it as a MKSamples object; for the purposes of this example we only consider the MKPartials case). When its wave table is set through the setTable:length: method, the oscillator allocates a MKSynthData object from the MKOrchestra to represent the DSP memory that will hold the waveform data computed from the MKPartials. It also places the MKSynthData on the shared object table using the MKPartials as an index by sending the message
[MKOrchestra installSharedSynthData: theSynthData for: thePartials]; |
If another oscillator's wave table is set as the same MKPartials object, the already allocated MKSynthData can be returned by sending the message:
id aSynthData = [MKOrchestra sharedObjectFor: thePartials]; |
The method installSharedObject:for: is provided for installing MKSynthPatches and MKUnitGenerators.
Before you can do anything with an MKOrchestra―particularly, before you can allocate synthesis/processing objects―you must create and open it. As usual, creation is done through the alloc and init methods; to open an MKOrchestra, you send it the open message. This provides a channel of communication with the DSP that the MKOrchestra represents. The DSP can be opened by only one application at a time, so you should always check the value returned by open; the method returns nil if the DSP couldn't be opened.
Once you've allocated the objects that you want, either through the methods described above or through those defined by MKSynthInstrument and MKSynthPatch, you can start the synthesis/processing by sending the run message to the MKOrchestra. The stop method halts synthesis/processing and close breaks communication with the DSP. These methods change the MKOrchestra's status, which is always one of the following MKDeviceStatus values:
Table 5-1. MKOrchestra status
Status | Meaning |
---|---|
MK_devOpen | The MKOrchestra is open but not running. |
MK_devRunning | The MKOrchestra is open and running. |
MK_devStopped | The MKOrchestra has been running but is now stopped. |
MK_devClosed | The MKOrchestra is closed. |
You can query an MKOrchestra's status through the deviceStatus method.
When the MKOrchestra is running it produces a stream of samples that, by default, are sent to the stereo digital to analog converter (DAC), which converts the samples into an audio signal. But there are two other options:
You can tell the MKOrchestra to write the samples to a soundfile by invoking the method setOutputSoundfile: (you must set the soundfile before sending run to the MKOrchestra).
You can tell the MKOrchestra to write the samples to the DSP serial port by invoking the method setSerialSoundOut: (you must set this before sending run to the MKOrchestra). For more information, see the Section called Using the DSP Serial Port below.
Note that you may not combine these output routes―the sound can only go to one destination at a time.
One of the most powerful aspects of the MusicKit is its seamless integration with the NeXT DSP serial port. There are three main uses for the serial port:
Direct to DAT transfers of sound in or out of the MusicKit
For very high quality digital-to-analog conversion.
For high quality analog-to-digital conversion.
The standard NeXT configuration (at the time of this writing) does not include a high-quality sound input. Therefore, in order to do real-time high-quality sound processing, you need to obtain a device that plugs into the DSP serial port. Similarly, you may want to use the DSP serial port for high-quality sound output to an outboard digital-to-analog converter or for direct digital transfer to or from a DAT machine.
A number of such devices are commercially available. Metaresearch sells the DigitalEars, an analog-to-digital converter. Ariel has an analog-to-digital converter called the Digital Microphone. Ariel also sells the ProPort analog-to-digital and digital-to-analog converter, as well as the DatPort DAT interface. Stealth sells the DAI2400 DAT interface. Singular Solutions sells the AD64x, which is a combination analog-to-digital converter and DAT interface.
To set up the MKOrchestra to do sound input via the DSP serial port, you send it the message:
[orchestra setSerialSoundIn:YES]; |
To set up the MKOrchestra to do sound output via the DSP serial port, you send it the message:
[orchestra setSerialSoundOut:YES]; |
You may simultaneously do serial port input and output. Alternatively, you may combine serial port input with normal NeXT sound output.
You may additionally provide an object that tells the MusicKit about the kind of device you have connected to the DSP serial port. To do this you send the message:
[orchestra setSerialPortDevice:aSerialPortDevice]; |
aSerialPortDevice must be an instance of the MusicKit class DSPSerialPortDevice or one of its subclasses. The class DSPSerialPortDevice itself provides a general-purpose interface to a number of common devices. It supports DigitalEars, the Ariel Digital Microphone, DatPort and ProPort. However, some devices have special requirements and features. The Stealth DAI2400 is supported by the StealthDAI2400 class, which is a subclass of DSPSerialPortDevice; similarly, the Singular Solutions AD64x is supported by the SSAD64x class. You must use these subclasses when using these devices. The devices are not compatible with the generic interface provided by DSPSerialPortDevice. For example, to use the AD64x:
#import <MusicKit/DSPSerialPortDevice.h> [orchestra setSerialPortDevice:[[SSAD64x alloc] init]]; |
For details on implementing your own DSPSerialPortDevice subclass, see the DSPSerialPortDevice class description in the file Classes/DSPSerialPortDevice.rtf.