Fine-Tuning a Performance Application

Responsiveness

The responsiveness of a performance to the user's actions depends on whether the MKConductor class is clocked or unclocked, and upon the value of the performance's delta time. By default, the MKConductor class is clocked. This means that the messages in the message request queues are sent at the times indicated by their timestamps. When the MKConductor class is clocked, a running NSApplication object must be present (unless the performance is being run in a separate thread, as described below).

If you don't need interactive control over a performance, you may find it beneficial to set it to unclocked by sending setClocked:NO to the MKConductor class. In an unclocked performance, messages in the message request queues are sent one after another as quickly as possible, leaving it to some other device―the DSP or the MIDI device driver―to handle the timing of the actual realization.

Setting the delta time further refines the responsiveness of a performance. Delta time is set through the MKSetDeltaT() C function; the argument defines an imposed time lag, in seconds, between the MKConductor's notion of time and that of the DSP and MIDI device drivers. It acts as a timing cushion that can help to maintain rhythmic integrity by granting your application a sort of computational head start: As you set the delta time to larger values, your application has more time to process MKNotes before they are realized. However, this computational advantage is obtained at the expense of degraded responsiveness. Choosing the proper delta time value depends on how responsive your application needs to be. For example, if you are driving DSP synthesis from MIDI input, a delta time of as much as 10 milliseconds (0.01 seconds) is generally acceptable. If you are adjusting MKNote parameters by moving a NSSlider with the mouse, a delta time of 100 milliseconds or more can be tolerated. Finding the right delta time for your application is largely a matter of experimentation.

For further information, see the Section called The Relationships between MIDI, the DSP, the MKConductor and the Clock on the Wall below.

Separate-Threaded Performance

To enhance the efficiency of a performance, you can run it in its own thread. This is done by sending useSeparateThread:YES to the MKConductor class. Running a performance in its own thread separates it from the main event loop, thus allowing music to play with greater independence from your application's other computations. However, certain restrictions must be adhered to when running a performance in its own thread:

These restrictions apply only to that part of your application that's running in the performance thread; specifically, messages sent by a MKConductor through its message request queue, and method invocations and C function class that are part of the design of a MKPerformer or MKInstrument (or pseudo-MKPerformers such as MKMidi) must follow these restrictions. For example, you can't use an MKInstrument that sends messages to an Application Kit NSWindow object; however, you can send messages to the NSWindow from your application's main thread.

The performance thread can cause a restricted method to be invoked or a restricted function to be called by sending a Mach message to a message port. To do this, you must first register the port through DPSAddPort() in the main thread. This is demonstrated in the Ensemble programming example (/Local/Developer/Examples/MusicKit/Ensemble).

An important restriction in a multi-threaded performance is that all messages (or groups of messages) to MusicKit objects sent from the main thread should be bracketed with lockPerformance and unlockPerformance.

Performance Priority

Give your real time application an advantage through the setTheadPriority: MKConductor class method. This method sets the Mach-scheduling priority of the performance thread, whether or not it's separate. Performance priority values are between 0.0 and 1.0, where 0.0 is unheightened (the default) and 1.0 is the maximum priority for a user process. Normally, Mach priorities degrade over time; by using setThreadPriority: and setting it to a value larger than 0, fixed scheduling policy is enabled. In this mode, your priority will not degrade.

Performing with the DSP

You can also shape your performance's capabilities by affecting the MKOrchestra class, thus influencing the manner in which DSP resources are used.

Sampling Rate

The DSP can output stereo samples at two rates, 22050 samples per second, or 44100 samples per second. By default, it runs at the low sampling rate. You can improve a performance's response time with regard to DSP synthesis by using the high sampling rate, as accomplished by sending the message

[MKOrchestra setSamplingRate:44100.0];

However, by asking the DSP to run at a higher sampling rate, you rob it of some of its power. In general, the DSP can be considered to be twice as “big” at the low sampling rate as at the high. In other words, if the DSP is able to synthesize twelve simultaneous voices at the low sampling rate using a particular MKSynthPatch, it may only be able to synthesize six such voices at the high sampling rate.

Sound Buffer Sizes

While the speed of the DSP makes real-time synthesis approachable, there's always an imposed time delay that's equal to the size of the buffer used to collect computed samples before they're shovelled to the DAC. To accommodate applications that require the best possible response time (the time between the initiation of a sound and its actual broadcast from the DAC), a smaller sample output buffer can be requested by sending the setFastResponse:YES message to an MKOrchestra. However, the more frequent attention demanded by the smaller buffer will detract from the DSP's synthesis computation and, again, fewer simultaneous voices may result.

Headroom

The MKOrchestra doesn't know, at the beginning of a MKNote, if the DSP can execute a given set of MKUnitGenerators quickly enough to produce a steady supply of output samples for the entire duration of the MKNote. However, it makes an educated estimate and will deny allocation requests that it thinks will overload the DSP and cause it to fall out of real time. Such a denial may result in a smaller number of simultaneously synthesized voices.

You can adjust the MKOrchestra's DSP processing estimate, or headroom, by invoking the setHeadroom: MKOrchestra method. This takes an argument between -1.0 and 1.0; a negative headroom allows a more liberal estimate of the DSP resources―resulting in more simultaneous voices―but it runs the risk of causing the DSP to fall out of real time. Conversely, a positive headroom is more conservative: You have a greater assurance that the DSP won't fall out of real time but the number of simultaneous voices is decreased. The default is a somewhat conservative 0.1.

The Relationships between MIDI, the DSP, the MKConductor and the Clock on the Wall

The MusicKit needs to satisfy two conflicting requirements, timely execution of scheduled events and steady performance. The MusicKit variable deltaT adjusts the trade-off between responsiveness and dependability. deltaT provides the MusicKit with a "cushion" of time to absorb any bursts of computation that would otherwise result in unsteady performance. Setting deltaT to 0.0 gives the most responsive performance, but one that is likely to be unsteady in musically dense textures. Setting deltaT to a larger values gives a more dependable performance at the expense of a greater latency. The function MKGetDeltaT() returns deltaT. You can set the value of deltaT with the function MKSetDeltaT(). For convenience, these have MKConductor class method equivalents, +deltaT and +setDeltaT, respectively.

The manner in which deltaT is interpreted depends on the deltaT mode. MusicKit supplies two modes, device lag mode and scheduler advance mode. In device lag mode, the MusicKit clockConductor stays in synch, as much as possible, with “wall time”, the actual time since the performance began, while devices such as MIDI and the DSP lag behind by deltaT. In contrast, in scheduler advance mode, the devices stay in synch with wall time, while the clockConductor runs in advance of wall time by deltaT. You set the deltaT mode with the C function: MKSetDeltaTMode(), passing it an argument of either MK_DELTAT_SCHEDULER_ADVANCE or MK_DELTAT_DEVICE_LAG. Both of these modes work whether or not any MKConductors are synchronizing to MTC.

Keep in mind that in order for a non-zero deltaT to have the desired effect, you must set the MKMidi and MKOrchestra objects to timed mode. See the Class References of these objects for details.

Device Lag Mode

The default mode is device lag mode (for historical reasons.) The value returned by MKGetDeltaT() is added to time stamps sent to the MIDI driver and DSP, providing the MusicKit with its "cushion". The function MKGetDeltaTTime() does this addition. The MKConductor runs steadily from the time it is started, while the output of the devices, such as MIDI and the DSP, seems to wait deltaT seconds and then runs steadily. The amount of time it waits is deltaT.

In addition, the MKOrchestra and MKMidi provide a “local deltaT” which is added in on top of the ordinary deltaT. This allows you to compensate for any difference of delay between these devices.

Device lag mode works well for many cases. But there are other cases, and MIDI Time Code synchronization is one of them, when we want the devices' notion of time to be that of wall time, while still keeping a cushion to absorb bursts of computation. This is the functionality provided by scheduler advance mode.

Scheduler Advance Mode

Here, we let the clockConductor's notion of time run ahead of wall time by deltaT. That is, the clockConductor races ahead when started, then maintains time such that the wall time since the performance began is the clockConductor's time minus deltaT. The time it reports (returned by MKGetTime() and [[MKConductor clockConductor] time]) includes this deltaT.

For example, assume deltaT is 1.0. At MTC time 4.0, the MKConductor runs MKPerformers scheduled for time 5.0, causing them to send the events destined for time 5.0 to the drivers. The drivers provide a delay of deltaT to compensate for the fact that the events were sent early. Thus, the events are rendered at the correct time―when MTC reaches 5.0, the listener hears the events for time 5.0.

Implementation details: In this mode, instead of adding deltaT to the time stamps sent to the devices, we just send the MKNotes early by deltaT. Thus, MKGetDeltaTTime() returns the same value as MKGetTime() so objects such as MKMidi and MKOrchestra that use MKGetDeltaTTime() to determine their time stamps work correctly. As a final refinement, if MKMidi's method useInputTimeStamps:YES was sent, the time stamps returned from the MKMidi driver are adjusted by adding deltaT before the MKConductor's time is set from them. The makes sure that these stamps are in the MKConductor's time base. MKNote that it is still possible to add a “local deltaT” to MKMidi or the MKOrchestra―these methods work the same way in scheduler advance mode as they do in device lag mode.

Sequencer Overdubbing

To simplify the task of overdubbing, MKPartRecorder and MKFileRecorder provide a method that makes sure that recorded time tags match wall time:

- compensateForDeltaT:(BOOL)yesOrNo /* default is NO */

If sent with an argument of YES, this method instructs the object to subtract deltaT from the time it assigns the MKNotes it records. This is useful because the effect of deltaT causes the listener to respond to an event at time t with a response at time t + deltaT. By subtracting deltaT, we undo that undesired offset.

Hints and Reminders

To minimize delays when using the DSP, be sure to send [MKOrchestra setFastResponse:YES]. Also, using a high sampling rate will give less of a delay when sending sound. The best response is obtained by taking sound from the DSP serial port. The delay here is on the order of a few milliseconds.