Issue with NamedControl Array Not Containing All Items

Hi, I’m encountering an issue when using NamedControl. The array doesn’t seem to contain all the expected items, and I’m not sure why.

Here is the code I’m working with:

~degree = [0, 2, 4, 5, 7, 9, 11];

Ndef(\m02, {
    var degree = \degree.kr(~degree);
    var buffer = Buffer.alloc(s, degree.size, 1, { |b| b.setnMsg(0, degree) });
    var freq = DegreeToKey.kr(
        buffer.bufnum,
        in: Line.kr(0, 12),
        octave: 12,
        mul: 1,
        add: 48
    );

    SinOsc.ar((freq).midicps, mul: 0.1);
}).play;

In the above code, when I use NamedControl to define degree, the array doesn’t seem to hold all the items correctly. However, when I use a global reference (~degree) in a similar setup, everything works as expected:

~degree = [0, 2, 4, 5, 7, 9, 11];

Ndef(\m02, {
    var degree = \degree.kr(~degree);
    var buffer = Buffer.alloc(s, degree.size, 1, { |b| b.setnMsg(0, ~degree) });
    var freq = DegreeToKey.kr(
        buffer.bufnum,
        in: Line.kr(0, 12),
        octave: 12,
        mul: 1,
        add: 48
    );

    SinOsc.ar((freq).midicps, mul: 0.1);
}).play;

Can someone explain why this is happening? I’m not sure why the degree array isn’t being populated correctly in the first example.

Thanks in advance for your help!

It’s because of mixing language side code into a SynthDef function.

This line:

var buffer = Buffer.alloc(s, degree.size, 1, { |b| b.setnMsg(0, degree) });

… runs in the language. It is not synthdef-able. Because it runs in the language, it doesn’t have access to control input values that exist only in the server.

Probably better would be LocalBuf.newFrom(degree) (though I haven’t tested it myself).

hjh

That makes sense. LocalBuf seems to handle the array correctly.

However, it doesn’t seem to update with new values after its initial evaluation:

~degree = [0, 2, 4, 6, 8, 10];
Ndef(\m02).set(\degree, [1, 3, 5, 7, 9, 11]);
(
Ndef(\m02, {
    var degree = \degree.kr(~degree);
    var buffer = LocalBuf.newFrom(degree);
    var freq = DegreeToKey.kr(
        bufnum: buffer,
        in: MouseX.kr(0, 12),
        octave: 12,
        mul: 1,
        add: 48
    );

    SinOsc.ar((freq).poll.midicps, mul: 0.1);
}).play;
)

Thank you!

I am not sure this can be done with LocalBuf - maybe it can and I just don’t know how to. Instead of using LocalBuf you can load the buffer language side, maybe something like this:

b = Buffer.alloc(s, 6);

(
Ndef(\m02, {
	var buffer = \buf.kr(b);
	var freq = DegreeToKey.kr(
		bufnum: buffer,
		in: MouseX.kr(0, 12),
		octave: 12,
		mul: 1,
		add: 48
	);	
	SinOsc.ar((freq).poll.midicps, mul: 0.1);
}).play;
~degree = [0, 2, 4, 6, 8, 10];
b.loadCollection(~degree, 0, { Ndef(\m02).set(\buf, b) });
)

(
// same size, different scale
~degree = [0, 1, 3, 5, 7, 9];
b.loadCollection(~degree, 0, { Ndef(\m02).set(\buf, b) });
)

(
// different size;
b.free;
~degree = (0..11);
b = Buffer.alloc(s, ~degree.size);
b.loadCollection(~degree, 0, { Ndef(\m02).set(\buf, b) });
)
1 Like

You could also set something up with Demand Ugens:

(
var sequencer = { |trig, arrayOfItems, numOfItems, repeatItem|
	var demand = Ddup(repeatItem, Dseq([Dser(arrayOfItems, numOfItems)], inf));
	Demand.ar(trig, DC.ar(0), demand);
};

Ndef(\m02, {

	var trig, arrayOfRatios, ratios, freq, sig;

	trig = Impulse.ar(2);

	arrayOfRatios = \arrayOfRatios.kr(Array.fill(7, 1));
	ratios = sequencer.(trig, arrayOfRatios, \numOfRatios.kr(7), \repeatRatio.kr(1));
	freq = (\midinote.kr(60) + \octave.kr(0)).midicps * ratios;

    sig = SinOsc.ar(freq);

	sig = sig * Decay2.ar(trig, 0.01, 0.4);

	sig!2 * 0.1;
}).play;

~getRatios = { |tuning, degrees|
	degrees.collect{ |degree| tuning.ratios[degree] }
};

~tuning = Tuning.et12;
~degrees = [0, 2, 4, 5, 7, 9, 11];

Ndef(\m02).set(
	\midinote, 60,
	\octave, 0,
	\arrayOfRatios, ~getRatios.(~tuning, ~degrees),
	\numOfRatios, 7,
	\repeatRatio, 3,
);
)
1 Like

I think you’d need to write continuously into the buffer:

var buffer = LocalBuf(degree.size);

BufWr.kr(degree, buffer, (0 .. degree.size - 1));

This would not scale up to large buffers, but for this use, should be fine.

(Untested but I think it should work.)

hjh

Sorry for the delay, I’ve been away from the computer for a couple of days.

I tried the approaches by @Thor_Madsen and @jamshark70, and they seem to work fine, thank you very much! I’m always surprised by how many different solutions SuperCollider provides.

@jamshark70 I haven’t been able to solve it using BufWr.kr, but I probably didn’t implement it correctly. And I am getting this warning: Wrapped channels from 6 to 1 channels.

Would like to offer a suggestion, but since there’s no code to look at, I can’t. Could you post?

hjh

@jamshark70 I couldn’t go further than this:

~degree = [0, 2, 4, 6, 8, 10];
Ndef(\m02).set(\degree, [1, 3, 5, 7, 9, 11]);
Ndef(\m02).set(\degree, [0, 2, 4, 6, 8, 10]);

(
Ndef(\m02, {
    var degree = \degree.kr(~degree);
    var buffer = LocalBuf.newFrom(degree);
    var freq = DegreeToKey.kr(
        bufnum: BufWr.kr(degree, buffer, (0 .. degree.size - 1)),
        in: MouseX.kr(0, 12),
        octave: 12,
        mul: 1,
        add: 48
    );

    SinOsc.ar((freq).poll.midicps, mul: 0.1);
}).play;
)

With this implementation, Ndef.set doesn’t update the initial value.
Thank you.

It’s quite likely that in this version, DegreeToKey isn’t accessing the buffer at all.

You’re using BufWr as the buffer number – this assumes that BufWr passes the buffer number out of its output.

But… what is BufWr’s output value? Help file doesn’t say. Note, however, that the examples in the help file don’t plug BufWr into any other UGen. It appears on a line by itself. Also, if one assumes that the output is a pass-through from one of the inputs, should it be the buffer number, or the signal, or the phase? Any could be useful at different times.

So the assumption isn’t safe.

The channel count warning appears because BufWr can write into only one frame at a time. To keep 6 values updated all the time, you need 6 BufWr objects – which we get by using an array for phase instead of a single value. Then these 6 BufWr objects are multichannel-expanding the rest of the synth chain. This isn’t wanted, so, avoid plugging the multichannel writer in here.

Writing the control values into the buffer is a separate operation from using the buffer in DegreeToKey. Also I see that I made a separate mistake: BufWr doesn’t mc-expand over the signal – should be fixed below.

(
Ndef(\m02, {
    var degree = \degree.kr(~degree);
    var buffer = LocalBuf.newFrom(degree);
    var writer = degree.collect { |deg, i| BufWr.kr(deg, buffer, i) };
    var freq = DegreeToKey.kr(
        bufnum: buffer,
        in: MouseX.kr(0, 12),
        octave: 12,
        mul: 1,
        add: 48
    );

    SinOsc.ar((freq).poll.midicps, mul: 0.1);
}).play;
)

(For completeness – though this is a subtle topic that may not be critical to your use case – I’m just mentioning it in case someone else chimes in to suggest that it’s extremely important. It may or may not be important to you, so feel free to ignore if it’s confusing. Depending on the way the UGens are ordered, a change in the scale might take effect one control block later. There’s a way to handle that, but I’d suggest – see if this simpler synth works well enough for you first.)

hjh

1 Like

I tested the code, and it works perfectly!

The issue you described doesn’t seem to affect my use case, but I truly appreciate the detailed explanation. The clarification about multichannel expansion and the need for multiple BufWr instances makes a lot of sense.

Thanks for taking your time to break it down so clearly! :raised_hands: