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
MKNote
s 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.
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:
You can't use MKScorefileWriter
or
MKScorefilePerformer
objects in the performance.
You can't invoke an
MKOrchestra
method that changes the
MKOrchestra
's status; these are open, run,
stop, close, and abort.
You can't send messages to instances of
classes defined by the Application Kit, or
to instances of the SndKit's
SndView
and SndMeter
classes. In addition, you can't read or write soundfiles, or play or
record sounds through an instance of the
SndKit's Snd
class
(note, however, that you can use the sound
library functions).
You can't call DPS client functions.
You can't call NXStreams functions.
You can't call C functions that rely on
standard input and output; these are functions such as
printf()
and scanf()
.
Because of this, DSP error logging and
MusicKit tracing can't be used. In
addition, if you need to handle MusicKit
errors, you must provide your own error handler function through
MKSetErrorProc()
.
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-MKPerformer
s 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.
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.
You can also shape your performance's capabilities by affecting
the MKOrchestra
class, thus influencing the
manner in which DSP resources are used.
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.
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.
The MKOrchestra
doesn't know, at the
beginning of a MKNote
, if the DSP can execute a
given set of MKUnitGenerator
s 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.
MKConductor
and the Clock on the
WallThe 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 MKConductor
s 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.
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.
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
MKPerformer
s 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 MKNote
s 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.
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 MKNote
s 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.
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.