Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Latency compensation in scsynth & supernova #3790

Merged
merged 4 commits into from
Jun 25, 2018

Conversation

smiarx
Copy link
Contributor

@smiarx smiarx commented Jun 17, 2018

There is currently a heated debate on the Ableton Link PR on how to implement accurate sample-level synchronization between instances. While doing some tests I noticed some inconsistencies regarding latency compensation in scsynth and supercollider for timestamped events:

  • scsynth with jack audio backend has a sign error when computing true output time, which leads into a doubling of the latency ! (I can't believed that I never noticed this).
  • supernova has no mechanism for latency compensation. I implemented it similarly to how it's done in scsynth.

@telephon
Copy link
Member

telephon commented Jun 17, 2018

There is currently a heated debate

this is not what I'd call a heated debate! It is really a friendly conversation on what might be the best solution.

accurate sample-level synchronization

There is only (very) accurate UTP-level time-stamp synchronzation in supercollider. We (unfortunately) have no means for sample level sync.

Where exactly is the difference between scsynth and supernova? Both do support OSC bundles with time stamps, I had thought. What is it you corrected?

@smiarx
Copy link
Contributor Author

smiarx commented Jun 17, 2018

Sorry I didn't mean that in a negative way at all ! I read with interest everyone's ideas. My apologies.

Most audio applications use estimated audio latency to compute signals ahead of time so that the audio is produced just at the right moment (see PortAudio doc for example).

For example you have a latency of 32ms and you want to produce a sound at a time T. If you start writing in the buffer at T you will hear the sound at T + 32ms. So by advancing the internal clock by 32ms, the buffer will be written ahead of time, and the audio will produced at T.

That's what scsynth is doing, except that in the Jack backend the latency was subtracted instead of added, so the audio was computed late.
Supernova wasn't implementing this mechanism, this can be highlighted by running one instance of scsynth and one instance of supernova and sending them exactly the same commands. The instances produce delayed signals.

This is also done in the Link demo

@telephon
Copy link
Member

telephon commented Jun 17, 2018

Ah I see. I had supposed that not only for scsynth but also for supernova the scheduled time means the nanosecond (according to network time) when the signal hits the wire. Thanks for the fix!

@nhthn
Copy link
Contributor

nhthn commented Jun 22, 2018

i feel like i would have a better grip on what this is doing if i had some test code. i'm not asking for a unit test, just a quick experiment to demonstrate the effect of this change.

also, congrats on finding a )/( :)

@telephon
Copy link
Member

@smiarx could you post a quick example of how you verified that it works?

@smiarx
Copy link
Contributor Author

smiarx commented Jun 24, 2018

thanks @snappizz :).

So I used two examples to test it. The first one checks that scsynth & supernova are in sync.

(
~scsynth = Server(\scsynth, NetAddr("localhost", 57110));
~scsynth.waitForBoot{
	~supernova = Server(\supernova, NetAddr("localhost", 57111));
	Server.supernova;
	~supernova.boot;
	~scsynth.latency = 0.2; ~supernova.latency = 0.2;
}
)

(
SynthDef(\click, {
	OffsetOut.ar(0, Impulse.ar(0, mul:0.6));
	Line.kr(dur:0.01, doneAction:Done.freeSelf)
}).send(~scsynth).send(~supernova);
)

(
~pattern = Pbind(\instrument, \click);
Ppar([
	Pbindf(~pattern, \server, ~scsynth),
	Pbindf(~pattern, \server, ~supernova)
]).play();
)

One can then use a recording tool to check the waveform of the output of the two servers. Before the fix supernova should be late by a time corresponding to the estimated latency (or the opposite for Jack). After the fix the clicks should be in sync.

I also tested it against an external tool using the LinkClock branch. In my case I used linkhut from the Link demos, but I believe that any other Link metronome should do it. Note that the Jack version of linkhut use the wrong latency estimation, use this patch to make it use the Jack API estimated latency (same as scsynth).

s.boot;
s.latency = 0.2;
l = LinkClock.new;
l.tempo = 1; // be sure that the tempo is 60 bpm on both sides

(
SynthDef(\click, {
	OffsetOut.ar(0, Impulse.ar(0, mul:0.6));
	Line.kr(dur:0.01, doneAction:Done.freeSelf)
}).add;
)

// I use the phase argument of Quant to compute ahead of time
// so that clicks happen right on time
Pbind(\instrument, \click).play(l, quant:[1, -0.2]);

I get <1ms of latency between the two programs with the fix.
latency-comp

Copy link
Contributor

@nhthn nhthn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this works as expected and the code looks fine. thanks!

@nhthn nhthn merged commit d0c3e43 into supercollider:develop Jun 25, 2018
@sonoro1234
Copy link
Contributor

sonoro1234 commented Dec 14, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants