Is there a equivalent to Dbufwr or BufWr but writes a whole array (to a LocalBuf) with each trigger

Hello!

Is there an equivalent to Dbufwr or BufWr that writes a whole array into a buffer upon trigger?
Basically I want to have a LocalBuf inside a SynthDef and each time I trigger a Demand.kr, the whole array (which I pass as an argument) would be loaded to the LocalBuf in order later to be read by a BufRd which gives the values to the inputs of a DemandEnvGen Ugen (levels and durations).

I have tried [array].as(LocalBuf) but it seems that the array is loaded at initialisation and cannot be updated when I want, that is why I wanted to use Dbufwr, but when I use it I have the problem of needing to write each specific index to the buffer which later creates other issues. Basically I would like to update a LocalBuf whenever I send a trigger if this makes sense. I don’t want to use Dseq to read the array because in very fast triggers it seems it skips values every now and then when the arrays are updated which causes some clipping. I am working on NRT mode btw.

Any help would be greatly appreciated.

thank you!!

If you don’t need to do this with LocalBuf, I would do this by making a new buffer in the language and assigning the buffer to the synth:

(
a = Signal.fill(200, {rrand(-1.0,1.0)});
b = Buffer.loadCollection(s, a.asWavetableNoWrap);

~synth = {|buffer|
  PlayBuf.ar(1, buffer, loop:1)*0.1;
}.play(s, args:[\buffer, b]);
)

(
a = Signal.fill(200, {rrand(-1.0,1.0)});
b.free;
b = Buffer.loadCollection(s, a.asWavetableNoWrap, 1, {~synth.set(\bufnum, b)});
)

Sam

Sorry. It’s simpler than that:

(
a = Signal.fill(200, {rrand(-1.0,1.0)});
b = Buffer.loadCollection(s, a.asWavetableNoWrap);

~synth = {|buffer|
  PlayBuf.ar(1, buffer, loop:1)*0.1;
}.play(s, args:[\buffer, b]);
)

(
a = Signal.fill(200, {rrand(-1.0,1.0)});
b.loadCollection(a.asWavetableNoWrap);
)

Sam

Hey!

thank you for your answer! Unfortunately .loadCollection won’t work on NRT mode…

that is why I looked for LocalBuf, since I want to have a buffer that is constantly updating. If what I wanted to do was in real time .loadCollection would work fine but in NRT it seems that each buffer update needs to be a file which I would allocReadMsg each one separetly . With LocalBuf and Dbufwr or BufWr I was able to write and update the same buffer in NRT mode and the only problem was that I couldn’t write the whole array at once. So if there was a way with one trigger a phasor could instantly write the array to a localbuf this would solve my problem…

thank you!

Maybe you could use a multichannel LocalBuf, 1 channel for each item of the array. Then use BufWr to update the whole array at once as a single multichannel frame.
I don’t think this would work with Dbufwr which only seems to want a single channel input and output buffer.

Or else I suppose you could use a single channel LocalBuf and then have a row of Dbufwr’s, 1 for each array item, and which each write to a different index when triggered together.

Repeating Sam’s point – if you let go of the idea that it must be a LocalBuf, and instead use a regular Buffer, then you can b_setn the buffer from the language, and problem solved.

  • Approach A: Array → n_set → synth argument, simultaneous trigger causes a UGen that doesn’t exist yet to copy the data into the LocalBuf.
  • Approach B: Array → existing command b_setn → Buffer.

Which one of those looks to be both 1/ possible today and 2/ less breakable?

Fixating on LocalBuf may be counterproductive.

hjh

Thank you all for your answers!

To be honest the only reason I looked at Localbuf was because from what I understood from the other post you had responded
" Doing loadCollection here is analogous to doing Buffer.read – it won’t work for NRT because it’s performing the operation Right Now instead of providing messages for the Score."

So I what I understood is that you need to have saved the data somewhere on disk in order for them to exist when the score wants to read them. So isn’t b_setn equivalent to .loadCollection here?

For Approach A: Array → n_set → synth argument, do you mean this works when you are constantly creating new synths, so the Localbuf gets updated in each synth creation?

thank you again!

b_setn doesn’t depend on a disk file at all – since it’s a server command, you can write it directly into the score.

If you could post a simplified example of your Score, I could edit it to show what I mean about b_setn. I’m afraid I don’t have time to build up my own DemandEnvGen demo case.

Could perhaps demo it with a tiny wavetable.

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

a = Score([
	[0.0, ['/d_recv',
		SynthDef(\NRTwave, { |out, bufnum, freq = 440|
			Out.ar(out, Osc.ar(bufnum, freq, 0, 0.2).dup)
		}).asBytes
	]],
	[0.0, (b = Buffer(server, 512, 1)).allocMsg],

	// HERE -- all of the loadCollection complexity goes away
	// -- just set the values directly
	[0.0, b.setnMsg(0, Signal.sineFill(256, [1, 0.8, 0.2, 0.3, 0.7]).asWavetable)],

	[0.0, (x = Synth.basicNew(\NRTwave, server, 1000)).newMsg(args: [freq: 400, bufnum: b])],
	[1.0, x.freeMsg],

	// can do on the fly too
	[1.0, b.setnMsg(0, Signal.sineFill(256, [0.1, 0.5, 0.3, 0.0, 0.0, 0.2]).asWavetable)],

	[1.0, (x = Synth.basicNew(\NRTwave, server, 1000)).newMsg(args: [freq: 400, bufnum: b])],
	[2.0, x.freeMsg]
]);

a.recordNRT(
	outputFilePath: "~/nrt-help.wav".standardizePath,
	headerFormat: "wav",
	sampleFormat: "int16",
	options: server.options,
	duration: 2,
	action: { "done".postln }
);

server.remove;
)

The point was that approach A is a bad way to do it, which I would go far out of my way to avoid doing.

hjh

Hi!

Thank you for this, it makes perfect sense and works really neatly!
I just have to do .setnMsg at the time I want to update the array…

Can I ask why if the array is big I get the error:

“ERROR: Primitive ‘_Array_OSCBytes’ failed.
caught exception ‘buffer overflow’ in primitive in method Array:asRawOSC”?

Because for what I am doing I would like to be able to load quite big arrays.

I posted your code a bit altered as an example:

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

a = Score([
	[0.0, ['/d_recv',
		SynthDef(\NRTwave, { |out, bufnum|
			Out.ar(out, PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum), loop: 1.0).dup*0.1)
		}).asBytes
	]],
	
	[0.0, (b = Buffer(server, 100000, 1)).allocMsg],
	
	[0.0, b.setnMsg(0,{rrand(-1,1.0)}!100000)],

	[0.0, (x = Synth.basicNew(\NRTwave, server, 100000)).newMsg(args: [ bufnum: b])],

	[10.0, b.setnMsg(0,{rrand(-1,1.0)}!100000)],

	[20.0, x.freeMsg]
]);


a.recordNRT(
	outputFilePath: "~/nrt-help.wav".standardizePath,
	headerFormat: "wav",
	sampleFormat: "int16",
	options: server.options,
	duration: 20,
	action: { "done".postln }
);

server.remove;
)

Thank you!!!

Because OSC is a bad way to transmit 100000 samples.

I remember a thread on the old SC mailing list, where somebody was asking about the feasibility of sending extremely high rate OSC messages. To that, James McCartney said something along the lines of, “If your control mechanism requires more bandwidth [i.e. a higher data rate] than the output, it might be a good idea to rethink the control mechanism.”

That is – are you certain the best way to go is essentially to generate audio data in sclang, and transmit it to the server for audio rendering?

The initial use case looked like DemandEnvGen, where even a very fast envelope, say 500 control points per second, would have almost 100 output samples per control point – so the output bandwidth is a couple of orders of magnitude larger than the bandwidth of the control messages. That passes JMc’s test. Transmitting audio buffers this way ends up on the other side of that inequality.

If I had to do this in NRT, I would write each chunk into a separate audio file, and b_read or b_allocRead the files at the appropriate points in the score.

The problem with loadCollection for that use is that it writes the data into a temporary file, and then deletes the file. But you need the file to stick around on disk until after rendering. So, then you would need to take it upon yourself to create the audio files.

Template for file writing:

(
// note, data MUST be a FloatArray or Signal
// writeData later will not accept a plain Array!
var data = Signal.whatever(...);

var path = "/some/where/audio-1.wav";

var f = SoundFile(path)
.sampleRate_(48000)
.numChannels_(1)
.sampleFormat_("int24")  // or "float"
.headerFormat_("wav");

if(f.openWrite) {
	protect {
		f.writeData(data);
	} {
		f.close;
	}
} { "Couldn't open % for writing".format(path.basename).warn };
)

hjh