Remove BufRd warn

hi,
on the BufRd help file there is this information:

NOTE: If you supply a bufnum of a buffer that has a different numChannels then you have specified to the BufRd, it will post a warning and output the channels it can.

I sometimes deliberately use a channel count smaller than “buffer.numChannels”. Works smoothly and in some cases allows you to reduce CPU consumption. Is it possible not to be warned of this “error”?

dws

like this?

  1. make SynthDef per the numChannels of Buffer.
  2. make a function to assign the corresponding Synth per the numChannels of Buffer.
(
var maxNumChannels = 3;
s.waitForBoot {(1..maxNumChannels).do { |numChannels|
	var synthName = (\bufrd ++ numChannels).asSymbol;
	SynthDef(synthName, { |bufnum, rate = 1, amp = 0.1, pan = 0, out = 0|
		var dur, env, phasor, sig;
		dur = BufDur.kr(bufnum) / rate;
		env = Env.linen(0.01, dur - 0.02, 0.01).kr(Done.freeSelf);
		phasor = Phasor.ar(0, BufRateScale.kr(bufnum) * rate, 0, BufFrames.kr(bufnum));
		sig = BufRd.ar(numChannels, bufnum, phasor) * env;
		sig = if(numChannels == 1) {
			Pan2.ar(sig, pan, amp)
		} {
			if(numChannels != 2) {
				sig = Splay.ar(sig)
			};
			Balance2.ar(sig[0], sig[1], pan, amp)
		};
		OffsetOut.ar(out, sig);
	}).add;
	~bufRd = { |bufnum, rate = 1, amp = 1, pan = 0, out = 0|
		s.bind {
			Synth((\bufrd ++ bufnum.numChannels).asSymbol, [
				\bufnum, bufnum, \rate, rate,
				\amp, amp, \pan, pan, \out, out
			]
			)
		}
	}
};

~bufs_numChannel_preserved = SoundFile.collectIntoBuffers(Platform.resourceDir +/+ "sounds/" ++ "*");

~bufs_mono = {
	var sndFiles;
	sndFiles = SoundFile.collect(Platform.resourceDir +/+ "sounds" +/+ "*");
	sndFiles.collect { |aSoundFile| Buffer.readChannel(s, aSoundFile.path, channels: [0]) };
}.();

s.sync;

(~bufs_numChannel_preserved ++ ~bufs_mono).do { |buf|
	var rate, amp, pan;
	# rate, amp, pan = [rrand(-6, 6).midiratio, 0.5.rand, 0.8.rand2];
	~bufRd.(bufnum: buf, rate: rate, amp: amp, pan: pan, out: 0);
	("\nbuf ID:" + buf ++ "\nnumber of channels: " + buf.numChannels).postln;
	(buf.duration / rate + 1).wait
}
}
)

@prko thank you for your answer,
sorry for not being clear enough. I really need to use the same buffer in differents synthDef with différents numChans.

(
SynthDef(\bufRd3, {
	arg buf, otherBuf, out, bpm;
	var rate = ControlDur.ir * (bpm/15);
	var phase = Phasor.kr(0, rate, 0, BufFrames.kr(buf));
	var sig;
	/* this don't warn on postWindow but read too many channels 
	sig = BufRr.kr(32, buf, phase, 1, 1)[..2];
	*/
	sig = BufRd.kr(3, buf, phase, 1, 1);
	sig = sig ++ BufRd.kr(29, otherBuf, phase, 1, 1);
	Out.kr(out, sig);
}).add;
SynthDef(\bufRdAll, {
	arg buf, out, bpm;
	var rate = ControlDur.ir * (bpm/15);
	var phase = Phasor.kr(0, rate, 0, BufFrames.kr(buf));
	var sig = BufRd.kr(32, buf, phase, 1, 1);
	Out.kr(out, sig);
}).add;

s.waitForBoot{
	var numframes = 128;
	var numchannels = 32;
	~stack = 0;
	~buf = 2.collect{ Buffer.alloc(s, numframes, numchannels) };
	~otherBuf = 2.collect{ Buffer.alloc(s, numframes, numchannels - 3) }; 
	~rdBus = Bus.control(s, 32);
	
	s.sync;
	
	~rd = Synth(\bufRd3, [
		\buf, ~buf[~stack],
		\otherBuf, ~otherBuf[~stack],
		\out, ~rdBus,
		\bpm, 120
	])
};
~rdType = {|type=0|
	r{
		var numFrames = 127.rand + 1;
		type = [\bufRd3, \bufRdAll][type];
		~stack = (~stack + 1).mod(2);
		
		// free next buffers
		~buf[~stack].free;
		~otherBuf[~stack].free;
		// load next buffers
		~buf.put(~stack, Buffer.alloc(s, numFrames, 32));
		~otherBuf.put(~stack, Buffer.alloc(s, numFrames, 29));
		
		s.sync;
		~rd.replace(type, [
			\buf, ~buf[~stack],
			\otherBuf, ~otherBuf[~stack],
			\out, ~rdBus,
			\bpm, 120
		], sameID: true
		)
	}.play
}
)
~rdType.(0)

I have many buffers in a project who are being mistreated in this way
What I’m trying to do is remove the warning that “polls” the post-window. I can’t find where this error is pointing (cpp file?).
this project is on a Bela, and I’m trying to pay attention to unnecessary CPU consumption

“Works smoothly,” ok, but “reduce[s] CPU consumption,” I would doubt.

Since you can’t pause some UGens in a synth node while running others, the number of operations downstream of the BufRd will be the same whether you’re using 1 channel or 20.

The server’s reported CPU usage depends on many things that aren’t related to scsynth evaluation, such as CPU clock speed throttling (where the reported CPU percent can go down when the load gets high enough to kick the CPU clock up to a faster rate). Running a few synths and looking at the server status bar isn’t a reliable indicator of CPU savings.

A couple of better methodologies would be:

  • Choose a target CPU % and create enough synth instances to hit the target. Speed is then proportional to the number of instances.

  • Or, create a target number of synths (large enough to push CPU up out of the “not breaking a sweat” 15-20% zone). Then speed is inversely proportional to CPU %.

Without benchmarking like that, showing a statistically significant difference, I’d have to assume that any CPU savings in this case would be illusory.

Prko’s approach is certain to run only the UGens required for the actual number of channels, which will definitely reduce CPU for buffers with fewer channels (not “maybe sorta sometimes reduce it”). IMO this is the recommended way.

hjh

Then you need to tailor the SynthDefs to the number of channels being used.

You will definitely waste a lot of CPU cycles if you are running a large multichannel SynthDef on a buffer with just a couple channels. Seriously: Don’t do it that way.

hjh

I think this is exactly what @dawamss is doing. The issue is that they want to use the same buffer for different SynthDefs, but BufRd complains if the number of buffer channels is larger than the number of outputs.

IMO, @dawamss’s complaint is justified. While it is probably an error to try to read too many channels, it should be valid to only read a subset of channels.

BTW, here’s the relevant code: supercollider/server/plugins/DelayUGens.cpp at 8338b0c55cc5d1be73341b09266c45abbb2ab8cd · supercollider/supercollider · GitHub

I guess bufChannels != numOutputs could be changed to bufChannels < numOutputs, and the error message adjusted accordingly.

Ah, I see, sorry for misreading the code. And thanks @Spacechild1 for the clarification.

On Bela, if you’re building SC yourself, then certainly, go ahead and remove the printed warning.

It would probably be a good idea to have a channel-subsetting UGen. My gut feeling is to disagree with removing the warning in SC’s core, and provide a better way to do it more thoroughly – for one thing, specifying a smaller number of channels (than the buffer has) can choose only the first n channels as the subset, which is not a general solution. I think it would be better to have a general solution rather than make it less irritating to hack this specific case.

hjh

That’s true. We’d probably also need an additional startChannel argument.

At the same time, a warning is just helpful information. I think it’s justified to remember the user of an ad-hoc decision.

Except the decision might be fully intentional. In this case the warning is just annoying.

Here’s a possible solution: if we add an startChannel argument, we might only warn if there is a channel mismatch and startChannel is not nil. If startChannel is set, we know for sure that the user only wants a subset of channels. (On the server side, nil might be translated as -1.)

1 Like

Fair enough. I often get annoyed by warnings, but have tried to just accept them. In this particular case, this might be a solution. But a solution for warnings in general, would be more complex.

Or… an alternate approach (and here I’m playing a little bit of devil’s advocate) – if the channels need to be accessed individually, this could be modeled as 32 one-channel buffers, instead of one 32-channel buffer.

That is… the general statement of the problem is: a large number of channels that you want to keep together sometimes, but use independently at other times.

SC’s buffer UGens currently assume the buffer, not the channel, to be the relevant unit. Probably it was expected that it would be very rare to have more than 4 or 8 channels, in which case, using a subset of them would not burn so much extra CPU. I’m pretty sure that “32-channel buffers, and using small subsets of the channels” was not on JMc’s mind.

AFAICS the available solutions are:

  • Read all the channels and throw away the ones you don’t want. (Drawback: extra interpolation calculations being done on channels that won’t be used.)
  • Add a UGen to BufRd a subset of the channels. (Good idea, but future maintenance burden, and not available today. But I think it wouldn’t be hard to write. Probably the best long-term solution.)
  • Use single-channel buffers. (Drawback: multiple BufRd UGens would add a bit of overhead. The overhead for a small subset of channels may be less than the interpolation overhead from the “read all” approach, but probably larger for a bigger subset. This isn’t tested, so I don’t know where the threshold is. In the given example, I’d guess that \bufRd3 would benefit from single-channel buffers, but \bufRdAll would be worse off. FWIW in Pd this would be the only possible solution.)

If there’s going to be an effort to address this use case, I think it’s worth designing it first. What’s the real need? What’s the best way to meet it? (What if the subset of channels is non-contiguous? Do we force the user to have multiple reader units with different startChannel values, or perhaps supply an array of the desired channels, e.g. BufRdChannels.ar(buf, phase, interpolation, [7, 2, 9, 28, 0]) and get 5 OutputProxies?) And… how many channels is too many? (If somebody gets up to, oh, 256 control-value channels in one buffer, is there a point where we say… maybe consider a different data structure?)

Recall also recent conversations about adding args to existing UGens: startChannel may be expedient but would at minimum need a mNumInputs guard.

hjh

1 Like

That’s not the situation even today. Even the RF64 format (the best option to my knowledge) has a limit of 18 channels, and I doubt it is compatible with every software. Although I think it would be safe for 8-channel files.

I think the most common situation is straightforward; it boils down to choosing one channel from stereo buffers loaded from stereo files. Does it get more complicated than that? Many still prefer using mono files for multichannel materials, even if just for compatibility and archiving reasons.

Thank you 'that’s what I was looking for

@smoge why RF64 is better ? aiff can have many channels

One reason is that it allows files larger than 4GB. More channels also mean larger file sizes.
It is an extension of the wave format, it’s very close to the WAVE64 format, but with a difference: it is an official standard specified by international broadcasting organizations, so you know it was used professionally in many different contexts. If something works on your computer, it’s not easy to know it will work everywhere now and in the future. Being a standard is important for archiving considerations.

Edit: I think Apple created the CAF format, but I’m not sure if it was really adopted in the same way.

If I understand @dawamss’s code correctly, the warnings in his code are due to the conflict between

  • the numChannels of BufRd and
  • the numChannels of the numcBuffer loaded into that BufRd.

There are two warnings:

  1. due to the following lines:
	~rd = Synth(\bufRd3, [
		\buf, ~buf[~stack],
		\otherBuf, ~otherBuf[~stack],
		\out, ~rdBus,
		\bpm, 120
	])

It returns:

 -> a Function
Buffer UGen channel mismatch: expected 3, yet buffer has 32 channels
  1. when evaluating ~rdType.(0) due to the following lines:
		~rd.replace(type, [
			\buf, ~buf[~stack],
			\otherBuf, ~otherBuf[~stack],
			\out, ~rdBus,
			\bpm, 120
		], sameID: true
		)

It returns:

-> a Routine
Buffer UGen channel mismatch: expected 3, yet buffer has 32 channels

The code from @dawamss does not show these warnings when changing some parts with numChannels as follows:

(
SynthDef(\bufRd3, {
	arg buf, otherBuf, out, bpm;
	var rate = ControlDur.ir * (bpm/15);
	var phase = Phasor.kr(0, rate, 0, BufFrames.kr(buf));
	var sig;
	/* this don't warn on postWindow but read too many channels 
	sig = BufRr.kr(32, buf, phase, 1, 1)[..2];
	*/
	sig = BufRd.kr(3, buf, phase, 1, 1);
	sig = sig ++ BufRd.kr(29, otherBuf, phase, 1, 1);
	Out.kr(out, sig);
}).add;
SynthDef(\bufRdAll, {
	arg buf, out, bpm;
	var rate = ControlDur.ir * (bpm/15);
	var phase = Phasor.kr(0, rate, 0, BufFrames.kr(buf));
	var sig = BufRd.kr(32, buf, phase, 1, 1);
	Out.kr(out, sig);
}).add;

s.waitForBoot{
	var numframes = 128;
	var numchannels = 3; // changed
	~stack = 0;
	~buf = 2.collect{ Buffer.alloc(s, numframes, numchannels) };
	~otherBuf = 2.collect{ Buffer.alloc(s, numframes, 32 - numchannels) }; // changed
	~rdBus = Bus.control(s, 32);
	
	s.sync;
	
	~rd = Synth(\bufRd3, [
		\buf, ~buf[~stack],
		\otherBuf, ~otherBuf[~stack],
		\out, ~rdBus,
		\bpm, 120
	])
};
~rdType = {|type=0|
	r{
		var numFrames = 127.rand + 1;
		type = [\bufRd3, \bufRdAll][type];
		~stack = (~stack + 1).mod(2);
		
		// free next buffers
		~buf[~stack].free;
		~otherBuf[~stack].free;
		// load next buffers
		~buf.put(~stack, Buffer.alloc(s, numFrames, 3)); // changed
		~otherBuf.put(~stack, Buffer.alloc(s, numFrames, 29));
		
		s.sync;
		~rd.replace(type, [
			\buf, ~buf[~stack],
			\otherBuf, ~otherBuf[~stack],
			\out, ~rdBus,
			\bpm, 120
		], sameID: true
		)
	}.play
}
)
~rdType.(0)

Do I understand @dawamss’s intention correctly?

@prko with alternativ ~rdType.(0) ~rdType.(1) warnings are come back

Of course!

  • Synth(\bufRd3) has two BufRd objects: one with 3 channels and one with 29 channels.
  • Synth(\bufRdAll) has only one BufRd with 32 channels.

These differences should be reflected correctly. However, my revision of your original code uses a 29-channel buffer for the 32-channel BufRd.
It should be changed again correctly…

Sorry, I did not see this problem.

This time I would not revise the code again, as I do not think I am following your intention correctly.
Anyway, I think your code should be revised, not the source file referred to by @Spacechild1.

It’s perfectly fine for any user to modify something in the source for their own use. If it’s a local only change, they don’t even have to publish it – GPL requires source code to be published for any release of a derivative work, but if it’s not released, there’s no restriction.

Removing the warning in the main SC repository is something different. As stated earlier, I’d prefer a general solution.

I’m not sure which one of those you’re referring to. I think, if the OP is making a one-off Bela device that wouldn’t be distributed, then they can do what they like with the source code. (Similarly, I’ve done one little tweak to Pure Data’s UI, which Miller Puckette disapproves, but I use it locally anyway.)

hjh

Yes, I agree!

I simply thought that perhaps the modified code could be released.