increasing server memory can be done by uncommenting here:

[ https://github.com/monome/norns/blob/master/sc/core/Crone.sc#L70 ]

and in fact should probably be done in main branch since we aren’t using supernova.


but also another way is to replace DelayC with BufDelayC, and supply a buffer for max delay size. buffers are allocated from the heap and limited only by physical memory. (should have some hundred MB, so minutes not seconds.)

this option could be noticeably less performant if you are making a large number of delays. but on balance i think most SC folks would agree it is better to use the heap than to make server mem allocation very large.

4 Likes

OK so… trying to do this and failing so far. The following doesn’t get me any sound. (numFrames below for 60 seconds)

snipped from r.scd

		ugenGraphFunc: {
			|
				in_In,
				in_DelayTimeModulation,
				out_Out,
				param_DelayTime,
				param_DelayTimeModulation,
				visual_DelayTime
			|
			var buffer = Buffer.alloc(numFrames: 48000*60, numChannels: 1);
			
			var sig_In = In.ar(in_In);
			var sig_DelayTimeModulation = In.ar(in_DelayTimeModulation);

			var delayTimeSpec = ControlSpec(0.5, 120, 'lin', 0, 4, "sec");

			var delayTimeSec = delayTimeSpec.map(
				delayTimeSpec.unmap(param_DelayTime) + (sig_DelayTimeModulation * param_DelayTimeModulation)
			);

			var delayed = BufDelayC.ar(buf: buffer.bufnum, in: sig_In, delaytime: delayTimeSec); 

			Out.ar(out_Out, delayed);
			Out.kr(visual_DelayTime, delayTimeSec);
			buffer.free;
		}

examples show a server as a variable for Buffer.alloc, do I need to do that here or is the default server assumed? If I need it what do I use? (context.server? Server.default? )

b = Buffer.alloc(s,44100,1);

In SuperCollider buffers need to be allocated and freed outside of the ugenGraphFunc. Here you’ve stumlbed on a limitation with the current r framework - which abstracts away a lot of things and does not deal with buffers custom to modules yet. So the BufDelayC route is not really feasible right now. I can look into whether it can be supported in some way (by providing a way to create buffers for modules), but right now it’s not as easy as with writing an engine from scratch in SuperCollider.

1 Like

I’m new to SuperCollider so I may be missing something, but: I’ve definitely made buffers inside my SynthDefs before and had them work just fine… Is there something I’m doing wrong, or misunderstanding how my plain ol SynthDefs may be different from how R is constructed?

(For example, here’s the Delay pedal for Pedalboard and an Amp simulator that uses a buffer for an efficient transfer function/wavetable)

I think it’s due to how R is constructed?
see here for where the modules get setup: https://github.com/antonhornquist/r/blob/5395bab6158f94107a1d3e1a898f85e689da320f/r.scd#L1822

As far as I can tell that func is just sent to synthdef.new https://github.com/antonhornquist/r/blob/5395bab6158f94107a1d3e1a898f85e689da320f/r.scd#L1333

Back to your original question: have you tried sending context.server to Buffer.alloc's server argument?

var buffer = Buffer.alloc(server: context.server, numFrames: 48000*60, numChannels: 1);

like @jah, i am surprised that this is possible, but it seems like limitations are always changing in SC-land.

it doesn’t seem like SynthDef.wrap is supplying any special magic.

however, @okyeron you definitely don’t want to call buffer.free until you’re done with the synth.

and @21echoes i can’t immediately see where those buffers are being freed. (are buffers allocated in a ugen graph func, freed automatically when the synth is freed? seems worth checking for leaks.)

1 Like

That’s probably true – I free any synths I make with that synthdef, but I never explicitly free those buffers or anything. If I understand the synthdef/synth boundary properly, it’s probably also the case that if I made multiple synths from that same synthdef they’d be sharing the same buffer? Because the synthdef happens once to create the ugen graph, not once per synth creation?

that’s what i’d guess too - the ugen raph function is executed on the client, a buffer is allocated but never freed… should be easy to verify one way or another.

to my eyes it’s a very odd usage though and i can’t really reason about it. it “used to be” that you needed to use LocalBuf to declare a buffer within a synthdef function. (“local buffers” are allocated from the server’s memory pool and not from the heap, IIRC, and each is specific to a Synth instance.)

i would have probably made it so that a Pedal has to explicitly do the bookkeeping for its buffer resources, just like you have done for its bus resources. but it’s nice to learn new tricks.

1 Like

I’ve never done this level of SC debugging before – how would I check if the buffer gets freed?

Also, apologies for derailing this thread a bit! Hopefully it’s been somewhat helpful for y’all :zipper_mouth_face:

hm, not super elegant but this seems to work:

~countAllocatedBuffers = { arg server;
	var bufs = List.new;
	server.cachedBuffersDo({ arg buf;
		bufs.add(buf.bufnum);
	});
	postln("there are " ++ bufs.size ++ " buffers allocated: " ++ bufs);
};
1 Like

catching up…

I believe I tried this and it errored (?). I’d need to go again to get the error it generated.

I am defintely “stumbling” around here. :grin:

@21echoes FWIW - errors:

R is initializing... 
ERROR: Message 'server' not understood.

... etc ...

^^ The preceding error dump is for ERROR: Message 'server' not understood.
RECEIVER: Frame (0x1bbcfb0) of Interpreter:functionCompileContext

While it is technically possible to allocate buffers in the SynthDef ugenGraphFunc - and each R “module type” (ie. MultiOsc) just end up as a SynthDef as @21echoes correctly noted - this isn’t the right approach here. Buffers allocated in a SynthDef ugenGraphFunc would be shared between Synths based on that SynthDef.

SC Example
s.boot;

(
SynthDef('test', {
	var buf1, buf2;
	buf1 = Buffer.alloc(s, 1234);
	buf1.bufnum.poll(trig: 1, label: 'buf1_bufnum');
	buf2 = Buffer.alloc(s, 1234);
	buf2.bufnum.poll(trig: 1, label: 'buf2_bufnum');
}).add;
)

a=Synth('test');
a.free;
b=Synth('test'); // compare polled buf1_bufnum/buf2_bufnum printouts - these synths use the same pair of buffers ...
b.free;

c=Synth('test'); d=Synth('test'); // ... even if they're running at the same time

The handling of the ugenGraphFunc passed to a SynthDef is one of SuperCollider’s many quirks. Given how dynamic the language is, as a newcomer one believes the ugenGraphFunc is evaluated for every Synth that is created from a SynthDef. But, it is in fact evaluated at SynthDef creation time and language semantics are very different in it.

The “Static versus Dynamic Elements” section in the docs for SynthDef elaborates on this:

… A def’s UGen graph function (and the SC code within it) is evaluated only when the def is created. Thus ‘if’ statements, etc. will have no further effect at the time the def is used to create a Synth, and it is important to understand that a UGen graph function should not be designed in the same way as functions in the language, where multiple evaluations can yield different results. It will be evaluated once and only once .

This is weird and you learn it by experience. :slight_smile:

Anyway, @okyeron if you get it to work in R your delay line would be shared between multiple “module instances” (= what you get when you invoke engine.new("MyOsc", "MultiOsc") = a Synth based on the MultiOsc’s SynthDef), which I do not think you want (two delay modules would write into and read from one and the same buffer). Also, as noted above the life cycle of the buffer would be suboptimal - a really long buffer would always be present and take up memory, regardless if whether you use the module or not, and it wouldn’t be deallocated in a proper way.

To solve this we’d have to have a way in R - which essentially is a very regulated/constrained way of creating SynthDefs in order to standardize modules and make them interconnected - to specify custom buffers. Ie:

Idea
(
	name: 'LongDelay',
	description: " ... description ... ",
	inputs: [
		// .. input metadata ...
	],
	outputs: [
		// .. output metadata ...
	],
	params: [
		// .. params metadata ...
	],
	custom_buffers: [
		'DelayLine' -> (
			NumFrames: 120 * 44100,
			NumChannels: 1
		),
	],
	ugenGraphFunc: {
		|
			in_In,
			in_DelayTimeModulation,
			out_Out,
			param_DelayTime,
			param_DelayTimeModulation,
			visual_DelayTime,
			bufnum_DelayLine
		|

		var sig_In = In.ar(in_In);
		var sig_DelayTimeModulation = In.ar(in_DelayTimeModulation);
		var buffer = Buffer.alloc(numFrames: 48000*60, numChannels: 1);

		var delayTimeSpec = ControlSpec(0.1, 5000, 'exp', 0, 300, "ms");

		var delayTimeMs = delayTimeSpec.map(
			delayTimeSpec.unmap(param_DelayTime) + (sig_DelayTimeModulation * param_DelayTimeModulation)
		);

		var delayed = BufDelayC.ar(buf: bufnum_DelayLine, in: sig_In, delaytime: delayTimeSec); 

		Out.ar(out_Out, delayed);
		Out.kr(visual_DelayTime, delayTimeMs);
	}
)

This needs a bit of non-trivial frameworky stuff, but I’ve done something similar for upcoming sample support so I might be able to reuse it in some way.

2 Likes

This all sounds cool to me. Thanks for all the followup. Happy to test or try to break stuff if it comes together.

I started on a CombDelay last night with CombC.ar last night so I’ll try to PR that at some point soon. :slight_smile:

1 Like