Passing an array to a synth

I was thinking this would result in a multichannel expanded saw, but why is it not doing what I expected? The meter is showing one big long red line on the left channel!

(
SynthDef.new(\mysyn, {
	arg out = 0, frq = [220, 230];
	var sig = Saw.ar(freq: frq, mul: 0.1);
	Out.ar(out, sig);
}).add
)
Synth.new(\mysyn)

When you use an array as an arg in a SynthDef, it needs to be a literal.

See more at the helpfile: SynthDef - Literal Arrays.

What this means practically is that you need a hash tag before the brackets: frq = #[220, 230], or to use a NamedControl instead of an arg for defining arguments in your SynthDef.

Also note that it won’t be possible to set an array that has more elements than the amount you defined in the SynthDef. But you can predefine an array with many more elements and set fewer (this’ll use more computing power though).

1 Like

This is a weird corner case that’s avoided if you used NamedControl for synth controls.

(
SynthDef.new(\mysyn, {
  var sig;

  sig = Saw.ar(freq: \frq.kr([220, 230]), mul: 0.1);

  Out.ar(\out.ir, sig);
}).add
)
1 Like

I can’t figure out the using of kontrol rates for an Out, I have the following:

(
SynthDef(\foo, {
	Out.ar(\out.kr(1), SinOsc.ar(mul: EnvGen.ar(Env.perc, levelScale: 0.3, doneAction: 2)));
}).add

Synth(\foo, [out: [0,1]])
)

This still sounds only on bus 0. What am I missing, how could I send the sound to both 0 and 1 using a parameter to my synth?

Doing it this way is going to get really confusing when you have multichannel signals, unless you want to take each channel of a multichannel signal and route it differently?
Do this instead.

x = {
	var sig = WhiteNoise.ar();
	\outArray.kr([-1, -1]).do({|bus|
		Out.ar(bus, sig * (bus >= 0))
	})
}.scope

// negative numbers mute
x.set(\outArray, [-1, 2])

Have you run this code on your computer? It does nothing when run! How is it to be used?!

You’re probably running into a sync issue, if you ran it all as one block. Try running the first bit, and then the second bit.

Or,

x = {
	var sig = WhiteNoise.ar();
	\outArray.kr([-1, -1]).do({|bus|
		Out.ar(bus, sig * (bus >= 0))
	})
}.play(args: [\outArray, [-1, 2]]);

Btw sometimes people do post suggestions here without being at the computer (such as I’m doing now, on my phone).

hjh

1 Like

You have to change very little from your original code:

(
SynthDef.new(\mysyn, {
	var frq, sig;
	frq = \frq.kr([220, 230]);
	sig = Saw.ar(freq: frq, mul: 0.1);
	Out.ar(\out.kr(0), sig);
}).add
)

Only one\out parameter, and multichannel expansion takes care of writing to two buses. You only need to specify a multichannel \out parameter if you want the option of writing your two Saw signals to two DIFFERENT non-adjacent buses.

1 Like

How do I specify a multichannel out parameter? I tested:

Synth(\mysyn, [out: [0,1]])

should this send to the left and right channels? Or is something wrong with my headphone!

Synth(\mysyn, [out: 0]) - you only specify the first bus.

The way SC handles outputs is probably best understood by looking at the server meter (/Server/Show Server Meter - or cmd + M on a mac).

First:
s.options.numOutputBusChannels_(8); - now you have 8 outputs. Note that SC will show you the 8 outputs we just asked for regardless of wether there are any physical outputs connected. I am on a M1 with only 2 physical outputs, but I can still see the other 6 outputs, I just can’t hear them.

In a normal DAW, stereo outputs are labeled something like 1-2, 3-4 etc. and outputting a stereo signal to say 2-3 (across 2 separate stereo busses) is not immediately possible. SC don’t really have stereo outputs, but handles multichannel outputs as consecutive monosignals and the first bus can be any bus.

Run the examples below and watch the server meter

~mysyn = Synth(\mysyn, [out: 0]) // outputs to bus 0 and 1
~mysyn.free;
~mysyn = Synth(\mysyn, [out: 1]) // outputs to bus 1 and 2
~mysyn.free;
~mysyn = Synth(\mysyn, [out: 2]) // outputs to bus 2 and 3
~mysyn.free;

Modify the synthdef to a 4 channel synth

(
SynthDef.new(\mysyn, {
	var frq, sig;
	frq = \frq.kr([110, 220, 330, 440]); // 4 channels
	sig = Saw.ar(freq: frq, mul: 0.1);
	Out.ar(\out.kr(0), sig);
}).add
)

Now:

~mysyn = Synth(\mysyn, [out: 0]) // outputs to bus 0, 1, 2, 3
~mysyn.free;
~mysyn = Synth(\mysyn, [out: 1]) // outputs to bus 1, 2, 3, 4
~mysyn.free;
~mysyn = Synth(\mysyn, [out: 2]) // outputs to bus 2, 3, 4, 5
~mysyn.free;

Highlight first block, then the second. It’s not uncommon for the syncs to be left out online.

After reading some more of your comments I think you should just have \out.kr() with no array stuff.

Say you have some signal D with channels [d0, d1, d2, … dn].

If you use Out.ar(\out.kr(P), D), then d0 goes to P, d1 to P+1, d2 to P+2… dn to P+n.

If, however you want to be able to specify exactly where each channel do D goes on a per channel basis, use what I had previously.

To cut through some of the complexity:

The recommended way to begin with a mono signal and output balanced stereo is .dup.

(
SynthDef(\foo, {
	Out.ar(
		// note: you do not want the default to be 1
		// that is the right-hand output bus
		\out.kr(0),

		// this is a mono signal
		SinOsc.ar(
			mul: EnvGen.ar(Env.perc, levelScale: 0.3, doneAction: 2)
		)
		.dup
		// now we have just .dup'ed it
		// this means: mono --> [mono, mono]
		// the two-item array is the simplest way to write stereo
	);
}).add;
)

Synth(\foo, [out: 0]);  // don't write two bus numbers here
  • Mono-to-stereo:
    • If no panning is required, .dup the mono signal so that it becomes a stereo array (where both channels are the same).
    • If panning is required, use Pan2 – this takes in a mono signal and outputs stereo.
    • Then you plug the stereo array into Out using a single bus number, and you get stereo. (I strongly recommend against using an array of bus numbers with Out – don’t do this unless you’re sure you know what you’re doing.)
  • Stereo signal, stereo output: In this case, you already have left and right channels, so, just put them into Out (again, with a single bus number only): Out.ar(oneBusNumberOnly, myStereoArray).

To sum up:

The initial problem that you posted is that you thought you had a stereo signal, but in fact, you had only a mono signal.

The second problem that you posted is that you started with a mono signal (OK), and wanted to output it to two channels, but you defined a single channel \out control – so the synth was doing single channel source → single channel output.

But that’s going in the wrong direction. A better approach is: single bus number, and make sure you really have a multichannel source (either true stereo, or Pan2, or .dup).

hjh

But why is that eventhough I changed the size of default \frq array to a bigger number, I still only hear the first 2 frequencies:

(
SynthDef.new(\mysyn, {
  var sig;

	sig = Saw.ar(freq: \frq.kr([1,2,3,4]), mul: EnvGen.kr(Env.perc(releaseTime: 0.3), doneAction: 2));

  Out.ar(\out.ir(0), sig);
}).add
)
Synth(\mysyn,[frq: [100,300,500,700]])

Compare it with:

Synth(\mysyn,[frq: [100,300]])

Both sound the same!

Try:

sig.postln

You’ll see:

[ a BinaryOpUGen, a BinaryOpUGen, a BinaryOpUGen, a BinaryOpUGen ]

You provided 4 freq values, so you get 4 Saw’s, each playing out through a channel. Presuming you only have a stereo setup, that’s why you’re only hearing the first two. You’ll have to decide how to mix these four audio streams down to 2 in order to hear everything. (see Multichannel Expansion | SuperCollider 3.12.2 Help for more info here)

Using something like a Mix for example?:

(
SynthDef(\x, {
	var sig = Saw.ar(
				freq: NamedControl.kr(\knums, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).midicps, 
				mul: EnvGen.kr(Env.perc(releaseTime: 0.2), levelScale: 0.5, doneAction: 2)
			);
	var sigmix = Mix.new(sig);
	Out.ar(0, sigmix)
}).add
)

This Code leads to an unpleasant DC offset of extra 0s, I provided to allow large enough arguments! This is (imo) very ugly way of doing it! Is this the way one defines a Synth with args for an array with unknown sizes?

Try this instead:

(
SynthDef(\x, {
	var size = 18;
	var env = EnvGen.kr(Env.perc(releaseTime: 0.2), levelScale: 0.5, doneAction: 2);
	var amp = \amp.kr(0!size);
	var freq = \freq.kr(110!size); // better than using a freq of 0 as default;
	var sig = size.collect{|i|Saw.ar(freq[i]) * amp[i] }; 
	var sigmix = Splay.ar(sig); // this spreads the sigs across the stereo field and normalizes the output, which is helpful when you don't know how many oscillators you will use before hand
	sigmix = Mix.new(sig); // make it mono, comment out this line for stereo
	Out.ar(0, sigmix)
}).add
)

Synth(\x, [freq: [55, 60, 70, 62].midicps, amp: [0.3, 0.3, 0.1, 0.2]])
1 Like

That’s because midinote 0 is very very low. It shouldn’t technically be a DC offsetz, just a very slow low sine wave.

It would be better to create another argument with amplitudes, that way you can mute the unwanted frequencies.

There is no such thing as an array with unknown size on the serve, all shapes, sizes, and connections must be known, only values can change.

I always specify frequency of pitches in Synthdefs with a \freq arg in Hz, because of how note, midinote and degree work in the pattern library - these keys result in a freq key being sent to the synth (in Hz). If I need to do micicps, I will do it in the language. If I need to calculate something inside the synthdef in midinote numbers, I do the conversion in the synthdef with cpsmidi…caluclate…midicps.

This is not the only way to go about it but I feel it is the safest way, especially if you no expert in patterns-with-synthdefs. If you don’t use the pattern library at all I guess this general principle is less relevant BUT consistency is nice when you use the same \freq arg in all you synthdefs (plus that is what others users do) - also when you revisit you code in a couple of years.