Re: supernova sample-based scheduling bug?

[email protected] Sat, 22 May 2021 17:30:38 +0200
Newsgroups gmane.comp.audio.supercollider.user
Message-ID <[email protected]>
> Am 10.05.2021 um 15:31 schrieb [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), 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(out, [Impulse.ar(10), DC.ar(0)] * EnvGen.ar(Env([1, 1, 0], [duration + 0.01, 0]), doneAction: 2))
}).store;

SynthDef(\dirac, { |out = 0|
    OffsetOut.ar(out, [DC.ar(0), FreeSelf.kr(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(out, [Impulse.ar(10), DC.ar(0)] * EnvGen.ar(Env([1, 1, 0], [duration + 0.01, 0]), doneAction: 2))
}).store;

SynthDef(\dirac, { |out = 0|
    OffsetOut.ar(out, [DC.ar(0), FreeSelf.kr(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
-----------------------------