Wrap ugen with interrupted interval

Suppose we have an interrupted interval, by which i mean:
an interval <a, b> without <c, d>, where a < c < b and a < d < b

Would it be possible to wrap a function or ugen with this interval?
I.E. let x be an output value of the given function or ugen
if x in <c, d> wrap x to d
if x < a or x > b wrap x to b

I basically want the .wrap(a, b) functionality plus something that skips the interval <c, d>

I think this is what you’re looking for with a function:

(
~wrapWithSkippedInterval = { |inval, a, b, c, d|
	var aTemp = a, bTemp = b, cTemp = c, dTemp = d, out;
	//sort the ranges automatically
	var args = [aTemp, bTemp, cTemp, dTemp].sort;
	a = args[0];
	b = args[3];
	c = args[1];
	d = args[2];
	
	out = inval.wrap(a, b);
	if ((out >= c) && (out < d)) { out = out + d };
	out = out.wrap(a, b);
	out;
}
)


(
// wrap range
var a = 1;
var b = 10;

// skipped interval
var c = 4;
var d = 7;

var results = 15.collect { |i|
    var wrappedX = ~wrapWithSkippedInterval.(i, a, b, c, d);
	[i, wrappedX].postln;
	wrappedX;
};

results.plot(discrete: true);
)

Thanks!, i just need to see how to make it work for wrapping ugens.

Something like:

var truee = ; //function if in range to exclude
var falsee = ; //function if not in range to exclude
x = x.wrap(a, b);
if (InRange.kr(x, c, d), truee, falsee);

And probably wrap the result back to a <= x <= b at the end. It seems a bit more complicated to try to make wrapping functions for if the result is or is not in the exclude range since you’d need more ugens to tell you if it’s above or below.

I have currently something like this, but its not there yet.

(
~wrapWithSkippedInterval = { |inval, a, b, c, d|
	var out, slope, inCD, case1, case2;
	
	out = inval.wrap(a, b);
	slope = Slope.ar(inval);
	inCD = (out >= c) & (out <= d);
	
	//if ugen is rising and in interval CD
	case1 = inCD & (slope > 0);
	//if ugen is dropping and in interval CD
	case2 = inCD & (slope <= 0);
	
	//rising & inCD:   out = (1 * (out + |d - c|)) + 0 + 0
	//dropping & inCD: out = 0 + (1 * out - |d - c|) + 0
	//not inCD : 0 + 0 + (1 * out)
	out = case1 * (out + (d - c).abs) + (case2 * (out - (d - c).abs)) + (inCD.not * out);
	
	out.wrap(a, b);
}
)


(


{~wrapWithSkippedInterval.value(SinOsc.ar(100), -1, 1, -0.1, 0.1)}.plot;


)
(
{
	var a = 5, d = 20, b = 10, c = 15;
	var x = PulseCount.ar(Impulse.ar(10000));
	y = x.wrap(a, d);
	y = if (InRange.ar(y, b, c), (y + (b - c).abs), (y));
	y = y.wrap(a, d);
	y
}.plot;
)

Or if you also want to see the input value:

(
{
	var a = 5, d = 20, b = 10, c = 15;
	var x = PulseCount.ar(Impulse.ar(10000));
	y = x.wrap(a, d);
	y = if (InRange.ar(y, b, c), (y + (b - c).abs), (y));
	y = y.wrap(a, d);
	[x,y]
}.plot;
)

EDIT: I think this might not work quite the way you want it to because it’s going to wrap back to c first

Firstly sorry for the bad description of what exactly i wanted. But this is the closest i will get.

(
~wrapWithSkippedInterval = { |inval, a, b, c, d|
	var out, slope, inCD, aboveCD, underCD, case1, case2, case3, case4, case5, case6;
	
	out = inval.range(a, b);
	slope = Slope.ar(inval);
	inCD = (out >= c) & (out <= d);
	aboveCD = out > d;
	underCD = out < c;
	
	//if ugen is rising and in interval CD
	case1 = inCD & (slope > 0);
	//if ugen is dropping and in interval CD
	case2 = inCD & (slope <= 0);
	//if ugen is rising and above cd
	case3 = aboveCD & (slope > 0);
	//if ugen is dropping and above cd
	case4 = aboveCD & (slope <= 0);
	//if ugen is rising and under cd
	case5 = underCD & (slope > 0);
	//if ugen is dropping and under cd
	case6 = underCD & (slope <= 0);
	
	out = if(case1, out + (d - c), out);
	out = if(case2, out - (d - c), out);
	out = if(case3, out + (d - c), out);
	out = if(case4, out + (d - c), out);
	out = if(case5, out - (d - c), out);
	out = if(case6, out - (d - c), out);
	
	out.wrap(a, b);
}
)


(


{~wrapWithSkippedInterval.value(SinOsc.ar(100), -1, 1, -0.1, 0.1)}.plot(0.03)


)

Secondly,
What i actually wanted, with this example in mind: a sinewave that goes from -1 to 1 just like a normal sinewave but skipping the values from c to d. But that requires to look ahead in the calculation of the ugen. One other solution could be phaseshifting, but not every ugen can be phaseshifted afaik.

Just a sidenote:
I thought it was not possible to use if statements in Synthdefs, but somehow this works.

Here’s what you’re looking for in a way that doesn’t alter the frequency when you change the min and max values in the range to exclude:

(
{
	var skipMin = -0.5, skipMax = 0.5; 
	var mid = skipMax - ( (skipMin - skipMax).abs * 0.5 );
	var sig = SinOsc.ar(); 
	var sel = sig >= mid;
	Select.ar(sel, [sig.clip(-1, skipMin), sig.clip(skipMax, 1)]);
}.plot
)

And this actually makes a really handy set of pseudo ugens:

Xrange {
	*ar { |in, min=(-0.5), max=0.5|
		var mid, temp = min;
		if (min > max) {min = max; max = temp};
		mid = max - ( (min - max).abs * 0.5 );
		^Select.ar(in >= mid, [in.clip(-1, min), in.clip(max, 1)]);
	}
	*kr { |in, min=(-0.5), max=0.5|
		var mid, temp = min;
		if (min > max) {min = max; max = temp};
		mid = max - ( (min - max).abs * 0.5 );
		^Select.kr(in >= mid, [in.clip(-1, min), in.clip(max, 1)]);
	}
}

Xrange2 {
	*ar { |in, aNumber|
		if (aNumber.isNil) {aNumber = 0};
		aNumber = aNumber.clip2(1).abs;
		^Select.ar(in >= 0, [in.clip(-1, aNumber.neg), in.clip(aNumber, 1)]);
	}
	*kr { |in, aNumber|
		if (aNumber.isNil) {aNumber = 0};
		aNumber = aNumber.clip2(1).abs;
		^Select.kr(in >= 0, [in.clip(-1, aNumber.neg), in.clip(aNumber, 1)]);
	}
}

+ UGen {
	xrange { |min, max|
		if (this.rate == \audio) {
			^Xrange.ar(this, min, max)
		} {
			^Xrange.kr(this, min, max)
		}
	}
	xrange2 { |aNumber|
		if (this.rate == \audio) {
			^Xrange2.ar(this, aNumber)
		} {
			^Xrange2.kr(this, aNumber)
		}
	}
}

Its very close! If those flat lines of value 0.5 and -0.5 were gone it would be perfect. But i think that would be hard. Because then, sine1 needs to shift 1/6pi to the left, sine2 shifts 2/6pi respectively to sine1, sine3 2/6pi to sine2, sine4 2/6pi to sine3, etc. You’d need to increase the phase of every next sine.

Its probably possible with the stepper ugen, but still that would work only for ugens that have a phase argument.

I did that on purpose. Without the flat lines, you’re increasing the frequency. You’d have to scale back the frequency of your desired wave to compensate for the range you’re excluding, which effectively cuts the phase (and therefore increases the frequency) by (skipMin - skipMax).abs * 0.5

I’m having trouble understanding what it is you want exactly. Is it the plot you just drew? If not a diagram would really help.

@mjsyts I cant seem to make it work. When i try to change the frequency other problems seem to arise.
@VIRTUALDOG What I’m looking for is something that transforms any ugen or range of values into itself, but skipping all samples that have an output value within a certain range. However this requires you to look ahead into the ugen. For example if [a, b] is the range (on the y-axis) you want to omit, then if f(t) is a ugen, you’d need to skip to the t for which f(t) = b when you reached the sample for which f(t) = a.


Since precalculation of ugens on the clientside seem to be not so easy, the next best thing is wrapping.
Besides there being a range of values i want the ugen to stay out of, i also want a range of values where the ugen stays in, with wrapping. Lets say [p, q] is that range. Thats basically solved with f(t).wrap(p, q). However i keep running into nasty exceptions when i try the wrapping method.

Hm, what are you planning to do with this? And do you want it to work on truly arbitrary signals, or do you have some guarantees about the input? That would help choose between different tradeoffs.

@VIRTUALDOG @mjsyts i apologize for being so finicky and unclear, i appreciate the help.
Im a bit late with the reply, but better late then never. I solved my problem with a feedback loop. I keep adding or subtracting to/from my “inSignal” and check the next feedback loop wether some conditions are true. If the conditions state that the current signal is reaching one of borders, it will jump over the interval. I needed a correction, i assume, to deal with the controlblock size delay.

(
~skipWrapTest = {
	arg sigIn;
	var slope, inCD, localin, localout;
	var case1, case2, trig1, trig2, add, sub; 
	//corrections to get the right timing
	var corDown = 0.45, corUp = 0.42;
	//interval borders
	var bottom = -0.1, top = 0.1;
	var sigOut, select, trigLength = ControlDur.ir;
	
	sigIn = SinOsc.ar(50, phase: pi/2);
	
	localin = LocalIn.ar(1);
	
	sigIn = sigIn + localin;
	
	sigIn = sigIn.wrap(-1, 1);
	
	slope = Slope.ar(sigIn);
	
	inCD = (sigIn >= (bottom - corDown)) & (sigIn <= (top + corUp)); 
	case1 = inCD & (slope > 0);
	case2 = inCD & (slope < 0);
	
	trig1 = Trig.ar(case1, trigLength);
	trig2 = Trig.ar(case2, trigLength);
	
	add = Stepper.ar(trig1, 0, 0, 10e3);
	add = add * (top - bottom).abs;
	
	sub = Stepper.ar(trig2, 0, 0, 10e3);
	sub = sub * (top - bottom).abs.neg;
	
	localout = add + sub;

	LocalOut.ar(localout);
	
	sigOut = sigIn;
	
	sigOut;
};
)

{~skipWrapTest.value}.plot(0.1);

Made one with the one-sample feedback ugen/function. It still needs a correction for the timing, but atleast not as big as with LocalIn/LocalOut.

(
{
	var inSig = SinOsc.ar(50, phase: pi/2);
	var sig;
	var step = DC.ar(0.2);
	
	sig = Fb1({|in, out|
		var out_1, out_2;
		
		//difference between previous output sample and the one before that
		var diff = out[1][1] - out[2][1];
		
		//is the previous sample within the specified interval?
		var inCD = (out[1][1] <= 0.1) & (out[1][1] >= -0.1);
		
		//out_1 contains the stepper
		//if output rising and in interval then return positive stepper + 1 step
		//otherwise keep the same level
		out_1 = if(
			(diff > 0) & inCD, 
			out[1][0] + in[0][1], 
			out[1][0]
		);
		
		//if ugen dropping and in interval then return negative stepper - 1 step
		//otherwise keep the same level
		out_1 = if(
			(diff < 0) & inCD, 
			out[1][0].neg - in[0][1], 
			out_1
		);
		
		//out_2 is the wanted output signal
		//add stepper to current input
		out_2 = out_1 + in[0][0];
		
		//returns stepper and output
		//to be used in the next loop and outside the loop
		//[stepper, signalOut]
		[out_1, out_2.wrap(-1, 1)]
	}, [inSig, step], 2, 1, 3, leakDC: false);
	
	sig[1]
}.plot(0.04)
)

I have a few questions:

  • Do you want to do it in real time?
  • Should the variable sigIn in the following example be any other UGgen or input signal via SoundIn.ar?
(
{ var sigIn, sigOut, ampFollower;
	sigIn = SinOsc.ar(2); // sigIn = SoundIn.ar(0);
	ampFollower = Amplitude.ar(sigIn, 1e-4, 1e-4).abs > 0.5;
	sigOut = ampFollower.if(
		sigIn,
		SinOsc.ar(0)
)}.plot(1)
)

I think it seems very difficult to do it in real time.
There might be several ways to do it if you do not need it in real time.

  1. If you are using a particular waveform, you could use a buffer via a wavetable or signal, then read the buffer using BufRD or PlayBuf. To achieve your purpose, you can have BufRD (or PlayBuf) jump from the index (or sample frame) of c to the index (or sample frame) of d. I do not know if it could seamlessly work.

  2. If you are using any signal, including SoundIn, we should be concerned with time! So we should use RecordBuf. Its argument run makes the recording pause.

  3. As far as I know, SC can pause a node with Pause and a synth itself with PauseSelf, but SC does not seem to be able to pause a UGen inside a running synth. So we should use Bus.audio to separate the ‘sound generation’ and ‘recording buffers’ if necessary.

The code below is the second way:

(
s.waitForBoot{
	var duration, buffer;
	duration = 1;
	buffer = Buffer.alloc(s, s.sampleRate * duration);
	SynthDef(\test, {
		var signal, ampFollower, recorder;
		signal = SinOsc.ar(2);
		ampFollower = Amplitude.ar(signal, 1e-4, 1e-4).abs > 0.5;
		recorder = RecordBuf.ar(signal, buffer, run: ampFollower, loop: 0, doneAction: Done.freeSelf)
	}).add;
	s.sync;
	Synth(\test);
	duration.wait;
	buffer.plot
}
)

To compare input waveform and output waveform:

(
s.waitForBoot{
	var duration, bufferIn, bufferOut, buffers;
	duration = 1;
	# bufferIn, bufferOut = { Buffer.alloc(s, s.sampleRate * duration, numChannels: 1) } ! 2;
	SynthDef(\test, {
		var signal, ampFollower, recorderInput, recorderOutput;
		signal = SinOsc.ar(2);
		ampFollower = Amplitude.ar(signal, 1e-4, 1e-4).abs > 0.5;
		recorderInput = RecordBuf.ar(signal, bufferIn, loop: 0);
		recorderOutput = RecordBuf.ar(signal, bufferOut, run: ampFollower, loop: 0, doneAction: Done.freeSelf)
	}).add;
	s.sync;
	Synth(\test);
	duration.wait;
	buffers = [[], []];
	bufferIn.loadToFloatArray(action: { |array| buffers.put(0, array) });
	bufferOut.loadToFloatArray(action: { |array| buffers.put(1, array) });
	buffers.plot
}
)

Sorry for not giving the draft based on other ways. I have other things to do, but this thread is very interesting, so I thought about it for a moment.

I didn’t want to open this can of worms if it could be helped, but an external might be the most elegant solution. Maybe remap

https://faustlibraries.grame.fr/libs/interpolators/

Yes, generally i do, however there are some cases where predetermined signals could be used. Im using this to control the position of grains. I want to have live granulation, and want to be as flexible as possible with the positions of the grains. I will have a upperbound and a lowerbound around the recorderhead; no grains are allowed to be generated within this range in order to prevent clicks.
And i’d like to be able to control the position of grains with a midi controller for example without worrying about the position, but also, as i said before, have the biggest range of possible positions to generate grains from.

I’ve researched about live granulation. Maybe there are already ways to solve the problem i have, but i’m a bit stubborn and because of the way how my project has grown, it asks for methods in the specific ways i want them to be.

Great ideas! I didnt think of them. Its always nice hear other peoples views, because you are inclined to get tunnelvision on how to solve a certain problem.

Also i improved the one sample feedback loop a bit, in a sense that its more precise and needs less correction for timing.


(
{
	var inSig = SinOsc.ar(50, phase: pi/2);
	var sig;
	var c = -0.1, d = 0.1;
	var correction = 0.001;

	sig = Fb1({|in, out|
		var addSteps, subSteps, outSig;

		//difference between previous output sample and the one before that
		var diff = out[1][2] - out[2][2];

		//is the previous sample within the specified interval?
		var inCD = (out[1][2] <= (d + correction))  & (out[1][2] >= (c - correction));

		//addSteps contains all the added steps until now
		//if output rising and in interval then add the needed value to bridge the gap
		//otherwise keep the same level
		addSteps = if(
			(diff > 0) & inCD,
			out[1][0] + (d - out[1][2]),
			out[1][0]
		);

		//subSteps contains all the subtracted steps until now
		//if output is dropping and in interval then subtract the needed value to bridge the gap
		//otherwise keep the same level
		subSteps = if(
			(diff < 0) & inCD,
			out[1][1] + (c - out[1][2]),
			out[1][1]
		);

		//add stepper to current input to get the correct output
                //and wrap it
		outSig = addSteps + subSteps + in[0];
		outSig = outSig.wrap(-1, 1);
		
		
		//to be used in the next loop and outside the loop
		[addSteps, subSteps, outSig]
	}, inSig, 3, 1, 3, leakDC: false);

	//the second channel here is to show that there are no output samples with a value within the interval [c, d]
	//the first trigger is because i think the signal starts at 0 for one sample, but im not sure
	[sig[2], Trig.ar((sig[2] <= d) & (sig[2] >= c), 0.01)]
}.plot(0.1);
)

Been lurking because I didn’t really understand what you wanted, still not quite sure, but based on this…

Is this what you want?

{
	var incomingRange = K2A.ar(MouseY.kr(0, 1)); // must be normalised
	
	var excludeZone = [0.2, 0.8];
	var excludeRange = excludeZone[1] - excludeZone[0];
	
	var realRange = 1 - excludeRange;
	var correctRange = incomingRange * realRange;
	var correct = correctRange + (excludeRange * (incomingRange > excludeZone[0]));
	correct.poll
	
}.scope

If you need to wrap, just wrap before you assign to incomingRange.

1 Like

oh man that looks so clean. And you got it in such a simple way. I didnt think of scaling the ugen down! I made a small adjustment, because the excludeZone borders were scaled down too.

(
{
	var incomingRange, excludeZone, excludeRange, realRange, correctRange, correct; 
	incomingRange = SinOsc.ar(50, phase: pi/2).range(0, 1); // must be normalised
	
	excludeZone = [0.5, 0.6];
	excludeRange = excludeZone[1] - excludeZone[0];
	
	realRange = 1 - excludeRange;
	
	//[0.5, 0.6] need to be mapped back to their original positions
	excludeZone = excludeZone / realRange;

	correctRange = incomingRange * realRange;
	correct = correctRange + (excludeRange * (incomingRange > excludeZone[0]));
	
	correct
	
}.plot(0.04)
)

I wanted to preserve as much as possible information of the uGen, but since just skipping samples seem to be difficult in most cases, i had to settle for some information loss; That means distorting the original uGen. Mine increases the range(although wrapped) of the uGen. Yours decreases the slope. Both mean information loss, but yours definitely wins!

1 Like