Re: supernova sample-based scheduling bug?

[email protected] Sun, 23 May 2021 17:57:15 +0200
Newsgroups gmane.comp.audio.supercollider.user
Message-ID <[email protected]>
I think you're right, my mind has slipped. So the problem with the
Supernova clock is "only" gradual clock drift, as reported here:
https://www.listarc.bham.ac.uk/lists/sc-dev-2014/search/

Christof

On 22.05.2021 17:30, [email protected] wrote:
>
>> Am 10.05.2021 um 15:31 schrieb [email protected]
>> <mailto:[email protected]>:
>>
>> The thing is, even if Supernova is using the sample clack, the sclang
>> client always uses the NTP system time. I think, true sample accurate
>> scheduling only works between bundles scheduled at the same logical
>> time in the client.
>>
>
> I’ve done some testing and cannot exactly confirm this. There seems to
> be no difference between using language-based timing and indirect
> timing via latency. There is a difference though between supernova and
> scsynth.
>
> On the flip side, compared to my original testing
> (https://scsynth.org/t/imperfection-of-language-based-timing/350
> <https://scsynth.org/t/imperfection-of-language-based-timing/350>),
> the bespoken issue seems to have dramatically improved. Now I have
> tested on OS 10.15.7 with 3.11.2 – no idea what caused the change.
> These awful jumps (in the size of /dozens/ of samples) that happened
> every 10-20 seconds or so in earlier tests (which I only performed in
> scsynth though) seem to have completely disappeared. This has a nice
> consequence: some variants of timing-sensitive pattern-based granular
> synthesis that produced unsatisfying discontinuities in realtime for
> the bespoken reasons are probably working fine now, for the extremely
> sensitive cases supernova should do it anyway.
>
>
> scsynth language-based timing (test 1)max deviation of 1 sample
> scsynth latency-based timing (test 2)max deviation of 1 sample
>
> supernova language-based timing (test 1)sample-accurate
> supernova latency-based timing (test 2)sample-accurate
>
> There were no differences between 44.1 and 48 kHz. Further testing
> should check longer run times than one minute and other interfaces
> (checked with built-in out on Mac now).
>
> I’d be happy to hear from your results with below tests on other
> operating systems as well !
>
>
> // for tests with scsynth
> (
> Server.scsynth;
> s.reboot;
> )
>
> // for tests with supernova
> (
> Server.supernova;
> s.reboot;
> )
>
> // prepare for test 1 - language-scheduled timing
> (
> // use store for later NRT usage,
> // define with dummy channel here for use with pattern recording,
> // play reference pulse train of synth to channel 0, "real" Pbind
> pulse train to channel 1
> // we need an out arg anyway for aPattern.record
>
> SynthDef(\diracs, { |out = 0, duration = 60|
> OffsetOut.ar <http://OffsetOut.ar>(out, [Impulse.ar
> <http://Impulse.ar>(10), DC.ar <http://DC.ar>(0)] * EnvGen.ar
> <http://EnvGen.ar>(Env([1, 1, 0], [duration + 0.01, 0]), doneAction: 2))
> }).store;
>
> SynthDef(\dirac, { |out = 0|
> OffsetOut.ar <http://OffsetOut.ar>(out, [DC.ar <http://DC.ar>(0),
> FreeSelf.kr <http://FreeSelf.kr>(Impulse.ar <http://Impulse.ar>(1))])
> }).store;
>
> // Synth as Pbind for pattern recording, needs legato = 1 to get equal
> duration
> p = Pbind(\instrument, \diracs, \duration, 60);
> q = Pbind(\instrument, \dirac, \dur, 0.1);
> )
>
>
> // prepare for test 2 - latency-based timing
> (
> // use store for later NRT usage,
> // define with dummy channel here for use with pattern recording,
> // play reference pulse train of synth to channel 0, "real" Pbind
> pulse train to channel 1
> // we need an out arg anyway for aPattern.record
>
> SynthDef(\diracs, { |out = 0, duration = 60|
> OffsetOut.ar <http://OffsetOut.ar>(out, [Impulse.ar
> <http://Impulse.ar>(10), DC.ar <http://DC.ar>(0)] * EnvGen.ar
> <http://EnvGen.ar>(Env([1, 1, 0], [duration + 0.01, 0]), doneAction: 2))
> }).store;
>
> SynthDef(\dirac, { |out = 0|
> OffsetOut.ar <http://OffsetOut.ar>(out, [DC.ar <http://DC.ar>(0),
> FreeSelf.kr <http://FreeSelf.kr>(Impulse.ar <http://Impulse.ar>(1))])
> }).store;
>
> // Synth as Pbind for pattern recording
> p = Pbind(\instrument, \diracs, \duration, 60);
>
> ~latency = s.latency;
>
> // we need a finite Pattern !
> q = Pbind(
> \instrument, \dirac,
> \dur, 0,
> // dur sequencing to be inserted here
> \realDur, Pn(0.1, 601),
> \latency, Pfunc { |ev|
> var latency = ~latency;
> ~latency = ~latency + ev[\realDur];
> latency
> }
> );
> )
>
>
> // run test 1 or 2
>
> (
> // record 60 sec of both pulse trains in realtime to compare,
> // wait until finishing confirmed in post window
> // mute to avoid playing hard clicks to speakers
>
> c = TempoClock.();
> t = 60;
> s.volume.mute;
>
> ~date = Date.getDate.stamp;
> ~fileName = Platform.recordingsDir +/+ "diracsRT_synthL_patR" ++ "_"
> ++ ~date ++ ".aiff";
>
> // Reference pulse train (Pbind with one synth) needs to know overall
> duration
> r = Ppar([p <> Pbind(\dur, Pn(t, 1)), q]);
>
> r.record(~fileName, clock: c, dur: t + 0.05);
>
> // need CmdPeriod here as record doesn't stop (broken)
> c.sched(t + 3, { CmdPeriod.run; s.volume.unmute });
> )
>
>
> (
> // helper function for index search
> ~indicesOfEqualWithPrecision = { |seq, item, prec = 0.0001|
>     var indices;
>     seq.do { |val, i|
>         if (item.equalWithPrecision(val, prec)) { indices =
> indices.add(i) }
>     };
>     indices
> };
>
> // analysing function
>
> ~analyse = { |fileName|
>     ~soundFile = SoundFile.openRead(fileName);
>     ~data = FloatArray.fill(~soundFile.numFrames *
> ~soundFile.numChannels, 0);
>     ~soundFile.readData(~data);
>     ~soundFile.close;
>     ~stereo = ~data.clump(2).flop;
>     ~dataL = ~stereo[0];
>     ~dataR = ~stereo[1];
>
>
>     // get indices of diracs
>     ~leftIndices = ~indicesOfEqualWithPrecision.(~dataL, 1);
>     ~rightIndices = ~indicesOfEqualWithPrecision.(~dataR, 1);
>
>     // look at deltas
>     ~leftDeltas = ~leftIndices.differentiate.drop(1);
>     ~rightDeltas = ~rightIndices.differentiate.drop(1);
>
>     // count occurences of (possibly) different deltas
>
>     ~leftDeltaSet = ~leftDeltas.asSet.collect { |x| [x,
> ~leftDeltas.occurrencesOf(x)] };
>     ~rightDeltaSet = ~rightDeltas.asSet.collect { |x| [x,
> ~rightDeltas.occurrencesOf(x)] };
>
>     "".postln;
>     "occurences of sample deltas with single synth: ".postln;
>     ~leftDeltaSet.postln;"".postln;
>
>     "occurences of sample deltas with Pbind: ".postln;
>     ~rightDeltaSet.postln;"".postln;
> };
> )
>
>
> // analyse, takes some seconds
>
> ~analyse.(~fileName)
>
>
> // results of test 1 and 2 with supernova (44100 Hz):
> ->
>
> occurences of sample deltas with single synth:
> Set[ [ 4410, 600 ] ]
>
> occurences of sample deltas with Pbind:
> Set[ [ 4410, 600 ] ]
>
>
> // results of test 1 and 2 with scsynth (of course second set can vary)
> ->
>
> occurences of sample deltas with single synth:
> Set[ [ 4410, 600 ] ]
>
> occurences of sample deltas with Pbind:
> Set[ [ 4409, 8 ], [ 4411, 52 ], [ 4410, 540 ] ]
>
>
> Greetings
>
> Daniel
>
> -----------------------------
> http://daniel-mayer.at <http://daniel-mayer.at>
> -----------------------------
>
>