110 lines
8.5 KiB
HTML
110 lines
8.5 KiB
HTML
|
<!doctype html><html lang='en'><head><title>Scheduling and Server timing | SuperCollider 3.12.2 Help</title>
|
||
|
<link rel='stylesheet' href='./../scdoc.css' type='text/css' />
|
||
|
<link rel='stylesheet' href='./../codemirror.css' type='text/css' />
|
||
|
<link rel='stylesheet' href='./../editor.css' type='text/css' />
|
||
|
<link rel='stylesheet' href='./../frontend.css' type='text/css' />
|
||
|
<link rel='stylesheet' href='./../custom.css' type='text/css' />
|
||
|
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||
|
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
|
||
|
<script src='./../lib/jquery.min.js'></script>
|
||
|
<script src='./../lib/codemirror-5.39.2.min.js' type='text/javascript'></script>
|
||
|
<script src='./../lib/codemirror-addon-simple-5.39.2.min.js' type='text/javascript'></script>
|
||
|
<script>
|
||
|
var helpRoot = './..';
|
||
|
var scdoc_title = 'Scheduling and Server timing';
|
||
|
var scdoc_sc_version = '3.12.2';
|
||
|
</script>
|
||
|
<script src='./../scdoc.js' type='text/javascript'></script>
|
||
|
<script src='./../docmap.js' type='text/javascript'></script>
|
||
|
<script src='qrc:///qtwebchannel/qwebchannel.js' type='text/javascript'></script>
|
||
|
</head>
|
||
|
<body onload='fixTOC()'>
|
||
|
<div id='toc'>
|
||
|
<div id='toctitle'>Scheduling and Server timing:</div>
|
||
|
<span class='toc_search'>Filter: <input id='toc_search'></span><ul class='toc'><li class='toc1'><a href='#Latency'>Latency</a></li>
|
||
|
<ul class='toc'></ul></ul></div><div id='menubar'></div>
|
||
|
<div class='contents'>
|
||
|
<div class='header'>
|
||
|
<div id='label'>
|
||
|
<span id='folder'>Guides</span>
|
||
|
| <span id='categories'><a href='./../Browse.html#Server'>Server</a> > <a href='./../Browse.html#Server>Architecture'>Architecture</a> | <a href='./../Browse.html#External Control'>External Control</a> > <a href='./../Browse.html#External Control>OSC'>OSC</a> | <a href='./../Browse.html#Scheduling'>Scheduling</a></span>
|
||
|
</div><h1>Scheduling and Server timing</h1>
|
||
|
<div id='summary'>Server bundling latency and OSC timing, logical and physical time</div>
|
||
|
</div>
|
||
|
<div class='subheader'>
|
||
|
<div id='related'>See also: <a href="./../Classes/Server.html">Server</a>, <a href="./../Classes/TempoClock.html">TempoClock</a></div>
|
||
|
</div>
|
||
|
|
||
|
<p>To ensure correct timing of events on the server, OSC messages may be sent with a time stamp, indicating the precise time the sound is expected to hit the hardware output.<h2><a class='anchor' name='Latency'>Latency</a></h2>
|
||
|
|
||
|
<p>In the SuperCollider language, the time stamp is generated behind the scenes based on a parameter called "latency."
|
||
|
<p>To understand how latency works, we need to understand the concepts of logical time and physical time.
|
||
|
<p>Every clock in SuperCollider has both a logical time and physical time.<dl>
|
||
|
<dt>Physical time<dd>always advances, represents real time.<dt>Logical time<dd>advances only when a scheduling thread wakes up.</dl>
|
||
|
|
||
|
<p>While a scheduled function or event is executing, logical time holds steady at the "expected" value. That is, if the event is scheduled for 60 seconds exactly, throughout the event's execution, the logical time will be 60 seconds. If the event takes 2 seconds to execute (very rare), at the end of the event, the logical time will still be 60 seconds but the physical time will be 62 seconds. If the next event is to happen 3 seconds after the first began, its logical time will be 63 seconds. Logical time is not affected by fluctuations in system performance.
|
||
|
<p>This sequencing example illustrates the difference. It's written deliberately inefficiently to expose the problem more clearly. Two copies of the same routine get started at the same time. On a theoretically perfect machine, in which operations take no time, we would hear both channels in perfect sync. No such machine exists, and this is obviously not the case when you listen. The routines also print out the logical time (clock.beats) and physical time (clock.elapsedBeats) just before playing a grain.<textarea class='editor'>s.boot;
|
||
|
|
||
|
SynthDef(\sinGrain, { |out = 0, freq = 440, amp = 0.5, dur = 1|
|
||
|
Out.ar(out, SinOsc.ar(freq, 0, amp) * EnvGen.kr(Env.sine(1), timeScale:dur, doneAction: Done.freeSelf));
|
||
|
}).add;
|
||
|
|
||
|
2.do({ |chan|
|
||
|
var rout;
|
||
|
rout = Routine({
|
||
|
var freq;
|
||
|
{ freq = 0;
|
||
|
rrand(400, 1000).do({ freq = freq + 1 });
|
||
|
[thisThread.clock.beats, thisThread.clock.elapsedBeats].postln;
|
||
|
Synth(\sinGrain, [\out, chan, \freq, freq, \dur, 0.005]);
|
||
|
0.1.wait;
|
||
|
}.loop;
|
||
|
});
|
||
|
TempoClock.default.schedAbs(TempoClock.default.elapsedBeats.roundUp(1), rout);
|
||
|
});</textarea>
|
||
|
|
||
|
<p>This is the output:<textarea class='editor'> Left channel Right channel
|
||
|
Logical vs Physical time Logical vs Physical time
|
||
|
95 95.001466112 95 95.002988196
|
||
|
95.1 95.101427968 95.1 95.103152311
|
||
|
95.2 95.201250057 95.2 95.202905826
|
||
|
95.3 95.301592755 95.3 95.303724638
|
||
|
95.4 95.401475486 95.4 95.403289141
|
||
|
|
||
|
Average physical latency:
|
||
|
0.00144247559999830 .0032120224000039</textarea>
|
||
|
|
||
|
<p>Notice that even though the left and right channel patterns were scheduled for exactly the same time, the events don't complete executing at the same time. Further, the Synth(...) call instructs the server to play the synth immediately on receipt, so the right channel will be lagging behind the left by about 2 ms each event--and not by the same amount each time. Timing, then, is always slightly imprecise.
|
||
|
<p>This version is the same, but it generates each synth with a 1/4 second latency parameter:<textarea class='editor'>2.do({ |chan|
|
||
|
var rout;
|
||
|
rout = Routine({
|
||
|
var freq;
|
||
|
{ freq = 0;
|
||
|
rrand(400, 1000).do({ freq = freq + 1 });
|
||
|
[thisThread.clock.beats, thisThread.clock.elapsedBeats].postln;
|
||
|
s.makeBundle(0.25, { Synth(\sinGrain, [\out, chan, \freq, freq, \dur, 0.005]); });
|
||
|
0.1.wait;
|
||
|
}.loop;
|
||
|
});
|
||
|
TempoClock.default.schedAbs(TempoClock.default.elapsedBeats.roundUp(1), rout);
|
||
|
});</textarea>
|
||
|
|
||
|
<p>By using makeBundle with a time argument of 0.25, the \s_new messages for the left and right channel are sent with the same timestamp: the clock's current logical time plus the time argument. Note in the table that both channels have the same logical time throughout, so the two channels are in perfect sync.
|
||
|
<p>These routines are written deliberately badly. If they're made maximally efficient, the synchronization will be tighter even without the latency factor, but it can never be perfect. You'll also see this issue, however, if you have several routines executing and several of them are supposed to execute at the same time. Some will execute sooner than others, but their logical time will all be the same. If they're all using the same amount of latency, you will still hear them at the same time.
|
||
|
<p>In general, all synths that are triggered by live input (MIDI, GUI, HID) should specify no latency so that they execute as soon as possible. All sequencing routines should use latency to ensure perfect timing.
|
||
|
<p>The latency value should allow enough time for the event to execute and generate the OSC bundle, and for the server to interpret the message and render the audio in time to reach the hardware output on time. If the client and server are on the same machine, this value can be quite low. Running over a network, you must allow more time. (Latency compensates for network timing jitter also.)
|
||
|
<p>Pbind automatically imports a latency parameter from the server's latency variable. You can set the default latency for event patterns like this:<textarea class='editor'>myServer.latency = 0.2; // 0.2 is the default</textarea>
|
||
|
|
||
|
<p>Here are three ways to play a synth with the latency parameter:<textarea class='editor'>// messaging style
|
||
|
// s.nextNodeID is how to get the next unused node ID from the server
|
||
|
s.sendBundle(latency, [\s_new, defName, s.nextNodeID, targetID, addAction, arguments]);
|
||
|
|
||
|
// object style, asking the object for the message
|
||
|
synth = Synth.basicNew(defName, s);
|
||
|
s.sendBundle(latency, synth.newMsg(target, arguments, addAction));
|
||
|
|
||
|
// object style, using automatic bundling
|
||
|
// like the previous example, when this finishes you'll have the Synth object in the synth variable
|
||
|
s.makeBundle(latency, { synth = Synth(defName, arguments, target, addAction); });</textarea>
|
||
|
<div class='doclink'>helpfile source: <a href='file:///Applications/SuperCollider.app/Contents/Resources/HelpSource/Guides/ServerTiming.schelp'>/Applications/SuperCollider.app/Contents/Resources/HelpSource/Guides/ServerTiming.schelp</a><br>link::Guides/ServerTiming::<br></div></div><script src='./../editor.js' type='text/javascript'></script>
|
||
|
</body></html>
|