Help for incomprehensible Mix behavior

Probably the answer is trivial, but I can’t understand why the first code “sounds” and the second doesn’t.
Thank you in advance.

// this works
(
var mix, s1, s2;
{
	s1=SinOsc.ar(220,mul: 0.3);
	s2=LFSaw.ar(110,mul: 0.3);
	a = [s2, s1];
	b = [s1, s2];
	mix = Mix.new([a, b]).postln;
}.play;
)

// this does not work (I simply changed the order of the two signals in the array 'a')
(
var mix, s1, s2;
{
	s1=SinOsc.ar(220,mul: 0.3);
	s2=LFSaw.ar(110,mul: 0.3);
	a = [s1, s2];
	b = [s1, s2];
	mix = Mix.new([a, b]).postln;
}.play;
)

Hello and welcome,

good catch, this looks like a bug in Mix.

(
// silent
{
	var sig = SinOsc.ar(220, mul: 0.3);
	Mix([sig, sig]);
}.play;
)

(
// ok
{
	var sig = SinOsc.ar(220, mul: 0.3);
	Mix([sig, sig * 2]);
}.play;
)



// compare
(
{
	var sig = SinOsc.ar(220, mul: 0.3);
	Mix(sig);
}.asSynthDef.dumpUGens;
)


// no Out here !
(
{
	var sig = SinOsc.ar(220, mul: 0.3);
	Mix([sig, sig]);
}.asSynthDef.dumpUGens;
)

Not a problem of Mix, it seems to be a weird bug in the UGen graph builder. I checked to exclude the possibility that something goes wrong in Function::play

// (1) ok

(
{
	var x = SinOsc.ar(mul: 0.1);
	x
}.play
)

(
SynthDef(\test_1, {
	var x = SinOsc.ar(mul: 0.1);
	Out.ar(0, x)
}).play
)

// (2) silent !

(
{
	var x = SinOsc.ar(mul: 0.1);
	x + x
}.play
)


(
SynthDef(\test_2, {
	var x = SinOsc.ar(mul: 0.1);
	Out.ar(0, x + x)
}).play
)


// (3) again sound !!

(
{
	var x = SinOsc.ar(mul: 0.1);
	x * x
}.play
)

(
SynthDef(\test_3, {
	var x = SinOsc.ar(mul: 0.1);
	Out.ar(0, x * x)
}).play
)

// (4) and here also sound !
// result multiplied with 0.1 only to avoid loud output

(
{
	var x = SinOsc.ar();
	x + x * 0.1
}.play
)

(
SynthDef(\test_4, {
	var x = SinOsc.ar();
	Out.ar(0, x + x * 0.1)
}).play
)

I’ve filed a bug report

Just a guess, if it’s of the form (x * b) + (x * b), that would trigger a muladd optimization, which would remove the first * and the + in favor of a muladd. But here, there is only one * so removing the “first” one also removes the “second.”

It depends how we are counting descendants of the * UGen. If descendants are counted as number of target inputs, then the * would have two descendants and the optimization should be skipped. If they’re counted as number of target UGens, then it would count one descendant, so it would look like a valid optimization (but it’s not).

I won’t quite put money on that but… check the count of descendants first.

A workaround would be to commute redistribute the multiplication:

var x = SinOsc.ar(mul: 0.1);
x + x  // KO

var y = SinOsc.ar;
(y + y) * 0.1  // OK

(So this is another argument in favor of writing * instead of using mul.)

hjh

PS No access to github from my phone. I might check it myself later on the computer. Edit: Confirmed, this is exactly the problem. descendants is a Set of unique UGens, not inputs. So it can’t detect the case of a UGen that feeds separately into two inputs of one other UGen.

I greet and thank you all.