Score array order?

Just saw in the Score helpfile:

The list should be in the following format, with times in ascending order.
[
[beat1, [OSCcmd1]],
[beat2, [OSCcmd2], [OSCcmd3]],
...
[beat_n, [OSCcmdn]],
[beatToEndNRT, [\c_set, 0, 0]] // finish
]

and:

.sort
sort the score time order. This is recommended to do before recordNRT or 
write when you are not sure about the packet order.

I have been using Score without doing this and not noticed any problems.
i.e.:

(
~server = Server(\nrt,
  options: ServerOptions()
  .sampleRate_(48000)
  .numOutputBusChannels_(1)
  .numInputBusChannels_(0)
  .memSize_(1024*1024));
~score = Score([
  [0.0, ['/d_recv',
    SynthDef(\note, {
      var freq = \freq.kr(200);
      var env = Env.perc.ar(2);
      var sig = LPF.ar(Saw.ar(freq), env.linexp(0, 1, freq, freq * 5));
      Out.ar(0, sig * 0.1);
    }).asBytes;
  ]],
  [2.0, [\s_new, \note, 1002, 0, 1, \freq, 300]],
  [0.0, [\s_new, \note, 1000, 0, 1, \freq, 100]],
  [4.0, [\s_new, \note, 1004, 0, 1, \freq, 500]],
  [1.0, [\s_new, \note, 1001, 0, 1, \freq, 200]],
  [3.0, [\s_new, \note, 1003, 0, 1, \freq, 400]],
]);
~score.recordNRT(
  outputFilePath: "~/score_order_node.wav".standardizePath,
  headerFormat: "w64",
  sampleFormat: "int16",
  options: ~server.options,
  duration: 5,
  action: { "done".postln; }
);
)

So I am curious if this is still relevant advice, at least for NRT? or, is it just relevant to make sure the SynthDefs come before the Synths?

… because, when you provide a score list to Score *new, it’s automatically sorted for you.

(
~score = Score([
  [0.0, ['/d_recv',
    SynthDef(\note, {
      var freq = \freq.kr(200);
      var env = Env.perc.ar(2);
      var sig = LPF.ar(Saw.ar(freq), env.linexp(0, 1, freq, freq * 5));
      Out.ar(0, sig * 0.1);
    }).asBytes;
  ]],
  [2.0, [\s_new, \note, 1002, 0, 1, \freq, 300]],
  [0.0, [\s_new, \note, 1000, 0, 1, \freq, 100]],
  [4.0, [\s_new, \note, 1004, 0, 1, \freq, 500]],
  [1.0, [\s_new, \note, 1001, 0, 1, \freq, 200]],
  [3.0, [\s_new, \note, 1003, 0, 1, \freq, 400]],
]);
~score.score.do(_.postln);
)

[ 0.0, [ /g_new, 1, 0, 0 ] ]
[ 0.0, [ /d_recv, Int8Array[ ... snip ... ] ]
[ 0.0, [ s_new, note, 1000, 0, 1, freq, 100 ] ]
[ 1.0, [ s_new, note, 1001, 0, 1, freq, 200 ] ]
[ 2.0, [ s_new, note, 1002, 0, 1, freq, 300 ] ]
[ 3.0, [ s_new, note, 1003, 0, 1, freq, 400 ] ]
[ 4.0, [ s_new, note, 1004, 0, 1, freq, 500 ] ]

See Score:init, which calls this.sort.

You definitely, absolutely do not want the timestamps to be out of order.

Your original example begins with a message at 0.0. It then advances to 2.0. At this point, the server is processing audio for 2.0 seconds. All of the audio from 0.0 to 2.0 has already been processed. Then you have another message at 0.0… which is in the past. NRT is “non-real-time” but it doesn’t support time travel backward.

If you add messages individually to the score and don’t sort, you will get the wrong sounding result.

hjh

In addition, I often create thousands of notes with random start times. Sort fixes my bad habit of adding things indiscriminately.
Josh

/*
Josh Parmenter
www.realizedsound.net/josh
*/

1 Like
1 Like

I have also been using score.add without sorting, like this:

(
~server = Server(\nrt,
  options: ServerOptions()
  .sampleRate_(48000)
  .numOutputBusChannels_(1)
  .numInputBusChannels_(0)
  .memSize_(1024*1024));
~score = Score([
  [0.0, ['/d_recv',
    SynthDef(\note, {
      var freq = \freq.kr(200);
      var env = Env.perc.ar(2);
      var sig = LPF.ar(Saw.ar(freq), env.linexp(0, 1, freq, freq * 5));
      Out.ar(0, sig * 0.1);
    }).asBytes;
  ]],
]);
~score.add([2.0, [\s_new, \note, 1002, 0, 1, \freq, 300]]);
~score.add([0.0, [\s_new, \note, 1000, 0, 1, \freq, 100]]);
~score.add([4.0, [\s_new, \note, 1004, 0, 1, \freq, 500]]);
~score.add([1.0, [\s_new, \note, 1001, 0, 1, \freq, 200]]);
~score.add([3.0, [\s_new, \note, 1003, 0, 1, \freq, 400]]);
~score.recordNRT(
  outputFilePath: "~/score_order_node.wav".standardizePath,
  headerFormat: "w64",
  sampleFormat: "int16",
  options: ~server.options,
  duration: 5,
  action: { "done".postln; }
);
)

and so far never noticed a wrong sounding result. Can you give an example?

It took forever to find this, but…

recordNRT always passes a start and end time to writeOSCFile. This causes writeOSCFile to call section, and here the messages are sorted.

AFAICS then, with normal use of recordNRT, you won’t have to sort manually.

BUT… if someone invokes a NRT server on their own, not going through recordNRT, then they would be responsible for calling .sort. (I.e., I can give an example.)

// using the example ~score above, created by `.add`-ing:

~score.write("~/order.osc".standardizePath);

(
~options = ServerOptions.new
.numOutputBusChannels_(2)
.sampleRate_(44100);

"scsynth -N % _ % 44100 wav int16 %".format(
	"~/order.osc".standardizePath,
	"~/order.wav".standardizePath,
	~options.asOptionsString
).unixCmd;
)

start time 0
nextOSCPacket 0
nextOSCPacket 2
nextOSCPacket 0
ERROR: Packet time stamps out-of-order.

So it would be safe to say that recordNRT does not require the user to sort explicitly.

But it is not safe to say that every use of an NRT server sorts automatically. It is the user’s responsibility to make sure that the OSC command file is in timestamp order. It happens that recordNRT does it for you (though it appears to be a side effect of the section call – if somebody decided to optimize recordNRT and pass nil for start and end times, then it would break).

hjh

Ah, thanks for the clarification.

Ha! Same for me (a couple of months ago). Ideally, the documentation should mention this. Even more confusingly, the documentation for Score.sort says this:

sort the score time order. This is recommended to do before recordNRT or write when you are not sure about the packet order.

This implies that recordNRT does not sort the list – which is not true!

Score.write, on the other hand, does not automatically sort the score.

To top it off, Score.writeOSCFile only sorts if given either a start or end position. I don’t know why this method exists in the first place; to and from could have just been added as optional arguments to Score.write