Batch sample creation

Hi all! I think it’s my first question here, I’m fairly new to SuperCollider and not very skilled at coding in general.
I want to create a simple program that could create say 20, 50, 128, etc… generative samples in one click (then I can use them with tidal cycles).
I’ve tried to adapt the NRT help file but the results are erratic. The process stops in the middle of the loop with the message : FAILURE IN SERVER /n_free Node 1000 not found
Here is my code, can somebody review it and le me know what is wrong in it ? Thank you!

(
20.do {
	arg i;
	var server = Server(\nrt,
		options: ServerOptions.new
		.numOutputBusChannels_(2)
		.numInputBusChannels_(2)
	);
	
	a = Score([
		[0.0, ['/d_recv',
			SynthDef(\fm,
				{arg carHz=400, modHz=1, modAmp=300,amp=0.2, pan=0, atk=0.01, rel=1;
					var car, mod, env;
					env = EnvGen.kr(Env.perc(atk, rel), doneAction: Done.freeSelf);
					mod = SinOsc.ar(modHz, mul:modAmp);
					car = SinOsc.ar(carHz + mod) * env * amp;
					car = Pan2.ar(car, pan);
					Out.ar(0, car)}).asBytes;
		]],
		[0.0, (x = Synth.basicNew(\fm, server, 1000)).newMsg(
			args: [
				\carHz, exprand(20, 10000),
				\modHz, exprand(20, 10000),
				\modAmp, rrand(20, 10000),
				\amp, exprand(0.1, 0.5),
                \atk, exprand(0.001, 0.05),
				\rel, exprand(0.05, 0.94),
				\pan, rrand(-0.5, 0.5)
		])],
		[1.0, x.freeMsg]
	]);
	
	a.recordNRT(
		outputFilePath: ("~/sc_export/fm_perc_" + i + ".wav").standardizePath,
		headerFormat: "wav",
		sampleFormat: "int16",
		options: server.options,
		duration: 1.2,
		action: { "done".postln }
	);
	server.remove;
}
)

I think you can ignore this message.

The message shouldn’t stop the loop, for one thing. Each iteration of the loop is starting a subprocess (server process), and then going immediately on to the next iteration. The loop will have finished all n iterations before any of the server processes have even booted up. There is no way for a future failure in a subprocess to retroactively stop a loop that has already completed.

Second, the message is based on the idea that you asked for a node to be freed, but that node already disappeared and doesn’t exist at the moment of the request. But what’s important here is not the condition of that request, but rather the result.

Action Node existed Node didn’t exist
Issue /n_free End state: Node doesn’t exist End state: Node doesn’t exist

So, whether the error is printed or not, in either case you end up with the node having been removed.

There would be a couple of ways to prevent the message from being posted:

  • One way is to remove the doneAction from the SynthDef. If you know you’re going to n_free it, then there is no need for the synth to free itself.
  • Or, let doneAction do it and delete the x.freeMsg.

One or the other, but not both.


It’s also possible to restructure the code to write all of the files using one NRT server. My thinking here is that booting a NRT server is slower than the actual audio processing. Booting 100 NRT servers for 100 files is possible but inefficient.

Instead, the Score can allocate a buffer for the length of file that you want, and then the score consists of a series of 1/ run synth, 2/ write buffer messages.

[0.0, ["/g_new", 1, 0, 0]]
[0.0, ['/d_recv', Int8Array[83, 67, 103, 102, ... synthdef stuff...]]]
[0.0, ["/b_alloc", 0, 44100, 2, nil, 44100.0]]
[0.0, [9, 'fm', 1000, 0, 1, 'carHz', 24.473357229763, 'modHz', 1595.4977392932, 'modAmp', 9897, 'amp', 0.43937270652929, 'atk', 0.038553115502246, 'rel', 0.2032622298744, 'pan', -0.17469465732574, 'buf', 0]]
[1.5, ["/b_write", 0, "/home/xxx/tmp/perc0.wav", "WAV", "int16", -1, 0, 0, nil]]
[2.0, [9, 'fm', 1000, 0, 1, 'carHz', 4493.9346917845, 'modHz', 76.299006596099, 'modAmp', 8621, 'amp', 0.20440363673376, 'atk', 0.036271699843923, 'rel', 0.14351514096175, 'pan', 0.0030591487884521, 'buf', 0]]
[3.5, ["/b_write", 0, "/home/xxx/tmp/perc1.wav", "WAV", "int16", -1, 0, 0, nil]]
// etc. etc.

I tested with 10 and it’s very fast. It probably could write a couple hundred files in less than a second.

(
// change this for your file location -- use % where the index number should go
var pathTemplate = "~/tmp/perc%.wav".standardizePath;
var numFiles = 20;

var server = Server(\nrt,
	options: ServerOptions.new
	.numOutputBusChannels_(2)
	.numInputBusChannels_(2)
	.sampleRate_(44100)
);

var buf = Buffer(server,
	server.options.sampleRate, // * 1 = 1 second, * 2 = 2 seconds etc.
	2,
	sampleRate: server.options.sampleRate
);

var generateOne = { |score, path, time, args, dur|
	var synth;
	score.add([time, (x = Synth.basicNew(\fm, server, 1000))
		.newMsg(args: args)
	])
	.add([time + dur + 0.5, buf.writeMsg(path, "WAV", "int16")]);
};

var genMsgs = { |score, num = 10|
	// if it's 10, then 1 digit because 0-9 :wink:
	var numDigits = num.log10.ceil;
	num.do { |i|
		var iStr = i.asString;
		generateOne.(score,
			pathTemplate.format(
				String.fill(numDigits - iStr.size, $0) ++ iStr
			),
			i * (buf.duration + 1),
			[
				\carHz, exprand(20, 10000),
				\modHz, exprand(20, 10000),
				\modAmp, rrand(20, 10000),
				\amp, exprand(0.1, 0.5),
				\atk, exprand(0.001, 0.05),
				\rel, exprand(0.05, 0.94),
				\pan, rrand(-0.5, 0.5),
				\buf, buf
			],
			buf.duration
		)
	};
};

a = Score([
	[0.0, ['/d_recv',
		SynthDef(\fm,
			{ arg buf, carHz=400, modHz=1, modAmp=300,amp=0.2, pan=0, atk=0.01, rel=1;
				var car, mod, env;
				env = EnvGen.kr(Env.perc(atk, rel));
				mod = SinOsc.ar(modHz, mul:modAmp);
				car = SinOsc.ar(carHz + mod) * env * amp;
				car = Pan2.ar(car, pan);
				RecordBuf.ar(car, buf, loop: 0, doneAction: 2)
			}
		).asBytes;
	]],
	[0.0, buf.allocMsg]
]);

genMsgs.(a, numFiles);

a.recordNRT(
	// this is a trick that's noted in the NRT guide
	// since the output files are being written from buffers,
	// there's no need for main audio output
	// so we can shunt the main output to a "don't write" location
	outputFilePath: "/dev/null",  // "NUL" in Windows
	headerFormat: "wav",
	sampleFormat: "int16",
	options: server.options,
	duration: nil,
	action: { "done".postln }
);
server.remove;
)

hjh

1 Like

Thank you so much @jamshark70 for your detailed answer to my question.
I’ll have to take the time and dive into the code you provided me to fully understand it!
Thank you once again!