A crossfading Select

I need to crossfade between subsequent values of a Select Ugen, when the index changes.

So it is not like SelectX, where a crossfade is always between subsequent indices, but on change, one crossfades into the new value.

The solutions I’ve come up with fail badly when a change is faster than the crossfade, because the internal Selects switch in the middle of the transition.


SelectXFade {

	// todo: what todo when change is faster than fadeTime?
	*new1 { arg rate, which, array, fadeTime;
		var selector, trig, iCurr, iPrev, prev, curr, fade;

		iCurr = which.floor;

		selector = UGen.methodSelectorForRate(rate);
		trig = Changed.perform(selector, which);

		iPrev = LastValue.kr(iCurr);
		prev = Select.perform(selector, iPrev, array);
		curr = Select.perform(selector, iCurr, array);
		fade = Sweep.perform(selector, trig, 1/fadeTime).min(1) * 2 - 1;

		^this.crossfadeClass.perform(selector, prev, curr, fade)
	}

	*ar { arg which, array, fadeTime=0.01;
		^this.new1(\audio, which, array, fadeTime);
	}
	*kr { arg which, array, fadeTime=0.01;
		^this.notYetImplemented(thisMethod) // doesn't work
	}
	*crossfadeClass {
		^LinXFade2
	}
}

Has anyone a good idea of how to do this better?

A simple multiplexer selects between N channels according to an index signal K, and can be implemented by multiplying the Kth channel by 1, all other channels by 0, and adding them together. To add a crossfade you can slew the binary signals that envelope each of the input channels. This handles the case where the index signal is suddenly switched between 3 or more signals faster than the crossfade can keep up.

If the slew produces a linear ramp, sin(x pi / 2) will convert it to an equal-power fade.

3 Likes

Thanks for helping me to get rid of my blockhead.

(I should have known – for SelectXFocus I did exactly that).

OK, so here is the implementation:


SelectXFade {

	*new { arg which, array, fadeTime = 0.01, wrap = true;
		var j = which.floor;
		var slope = fadeTime.reciprocal;
		if(wrap) { j = j % array.size };
		^array.sum { |x, i|
			x * (absdif(i, j) < 0.5).slew(slope, slope)
		}
	}

	*ar { arg which, array, fadeTime=0.01, wrap = true;
		^this.new(which, array, fadeTime, wrap)
	}

	*kr { arg which, array, fadeTime=0.01, wrap = true;
		^this.new(which, array, fadeTime, wrap)
	}

}

Now the problem is that this is very inefficient by comparison. For what I do, it doesn’t work – it builds too slowly and eats too many cycles while running.

Probably we need a multiplexer UGen.

How many channels you got? The algorithm I’ve described will always be click-free but if you make certain assumptions about index signal changing slowly then I can imagine ways to optimize it without needing a ugen.

Another way might be to stick to the original crossfade idea, but use sample and hold on the index to choose a new index only when a crossfade has completed. This would essentially pause index changes during crossfades.
Best,
Paul

There’ll be several hundred, so it matters a lot.

Yes, that would be a good solution. Here is a sketch, that still won’t do it when frequecies come close to control rate (for 400 Hz it is OK):



(
f = { arg which, array, fadeTime;
		var  trig, iCurr, iPrev, prev, curr, fade, sig;
		var inFade = LocalIn.ar(1, 0.0);
		var completed = (inFade >= 1) + Impulse.ar(0);

		iCurr = Gate.ar(which.floor, completed);

		trig = Changed.ar(iCurr);

		iPrev = LastValue.kr(iCurr);
		prev = Select.ar(iPrev, array);
		curr = Select.ar(iCurr, array);
		fade = Sweep.ar(trig, 1/fadeTime).clip(0, 1);
		LocalOut.ar(fade);

		[trig, fade, inFade, LinXFade2.ar(prev, curr, fade  * 2 - 1)]
}
)


(
{
	var trig = Impulse.ar(700);
	var which = ToggleFF.ar(trig);
	f.(which, DC.ar([1, 2]), 0.1/300)
}.plot;
)

For higher frequencies, you need:

iPrev = LastValue.ar(iCurr);

Best,
Paul

1 Like

I made a 100x multiplexer ugen on my flight. Will upload it to GitHub in the morning.

Sam

Thank you!

In the meantime, for comparison, after the fix of the oversight, the following works. But it is also too inefficient for what I want to do.


SelectXFade {
*new1 { arg rate, which, array, fadeTime;
		var selector, trig, iCurr, iPrev, prev, curr, fade, sig, completed, inFade;


		selector = UGen.methodSelectorForRate(rate);


		inFade = LocalIn.perform(selector, 1, 0.0);

		completed = (inFade >= 1) + Impulse.perform(selector, 0);

		iCurr = Gate.perform(selector, which.floor, completed);

		trig = Changed.perform(selector, iCurr);

		iPrev = LastValue.perform(selector, iCurr);
		prev = Select.perform(selector, iPrev, array);
		curr = Select.perform(selector, iCurr, array);
		fade = Sweep.perform(selector, trig, 1/fadeTime).clip(0, 1);
		LocalOut.perform(selector, fade);

		^this.crossfadeClass.perform(selector, prev, curr, fade  * 2 - 1)
	}

	*ar { arg which, array, fadeTime=0.01;
		^this.new1(\audio, which, array, fadeTime);
	}

	*kr { arg which, array, fadeTime=0.01;
		^this.notYetImplemented(thisMethod) // doesn't work
	}

	*crossfadeClass{
		^LinXFade2
	}

}
1 Like

Nice. Well, it looks like you have a beautiful solution.

Mine is less efficient than yours, but the outputs are similar.

It uses this MUXMul UGen I made on the plane (more fun than watching another Thor movie):

({
	var size = 100;
	var array = (1,2..size-1);
	SelectXFade.ar(K2A.ar(MouseX.kr(0,size-0.001)), DC.ar(array)).poll;
	nil
}.play)

({
	var size = 100;
	var array = (1,2..size-1);
	f = 0.01;
	(Slew.ar(MUXMul100.ar(MouseX.kr(0,size-0.001)), 1/f, 1/f)*array).sum.poll;
	nil
}.play)

Sam

Nice!

In terms of efficiency, I am still stuck. I think that a UGen more similar to Select that does the slewing internally, only for those values that are nonzero and does an internal mul/add might be worth a try.

what if for every change of index you start a new synth,
which only reads the new channel of interest and fades it in,
and fades out the previous one?
that would trade more language side administration for
much less server CPU load.
also, timing would be quantized to blocksize borders
(for the fadeout triggers at least, fadeins could use OffsetOut)

2 Likes

Normally, this would be the best! But in this case, I need to have the whole thing as a single UGen tree. A new UGen will be the best, I think, and generally useful as well.

1 Like