Phaseshaping Osc Algorithms

i was trying to emulate the LFSaw behaviour with Phasor. is this right? At 0.005 sec its not accurate why is that?

(
{
	var width = 0.25;
	var freq = 400;
	var saw = LFSaw.ar(freq/2, 1).linlin(-1, 1, width.neg, 1-width);
	var pos = Phasor.ar(0, freq/2 * SampleRate.ir.reciprocal, width.neg, 1-width);
	//var pos = Phasor.ar(0, freq * BufFrames.ir(b) * SampleRate.ir.reciprocal, 0, BufFrames.ir(b));
	
	saw = saw.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	pos = pos.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	
	[saw, pos]
}.plot;
)

It is really only off by 1 sample:

(
{
	var width = 0.25;
	var freq = 400;
	var saw = Delay1.ar(LFSaw.ar(freq/2, 1).linlin(-1, 1, width.neg, 1-width));
	var pos = Phasor.ar(0, freq/2 * SampleRate.ir.reciprocal, width.neg, 1-width);
	
	saw = saw.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	pos = pos.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	
	[saw, pos]
}.plot;
)

But this will be exact:

(
{
	var width = 0.25;
	var freq = 400;
	var saw = LFSaw.ar(freq/2, 1).linlin(-1, 1, width.neg, 1-width);
	var pos = Phasor.ar(Impulse.ar(freq/2), freq/2 * SampleRate.ir.reciprocal, width.neg, 1-width, width.neg);
	//var pos = Phasor.ar(0, freq * BufFrames.ir(b) * SampleRate.ir.reciprocal, 0, BufFrames.ir(b));
	
	saw = saw.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	pos = pos.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	
	[saw, pos]
}.plot;
)

thanks so much :slight_smile:

can Shaper generally be exchanged with BufRd? im not getting the same results.

b = Buffer.alloc(s, 512, 1, { |buf| buf.chebyMsg([1,0,1,1,0,1])});
(
{
	var width = MouseX.kr().clip(0.1,0.9);
	var freq = MouseY.kr(200, 1000);
	var pos = Phasor.ar(Impulse.ar(freq/2), freq/2 * SampleRate.ir.reciprocal, width.neg, 1-width, width.neg);
	var sine, shaper, playbuf;
	
	pos = pos.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	sine = (Select.ar(pos>0, [0.5+pos, pos])*2pi).sin;
	
	//shaper = Shaper.ar(b, sine, 0.5);
	playbuf = BufRd.ar(1, b, sine, interpolation:4);
	
	//shaper.dup
	playbuf.dup
}.scope;
)

do you know how to get the variable-slope triangle wave out of the folded one as described in chapter 2.4 “tilted triangular fractional period phase signal”? many thanks :slight_smile:


(
{
	var width = 0.25;
	var freq = 400;

	var tri = LFTri.ar(freq/2, 0, LFTri.ar(0.2).range(1,2)).fold(-1,1);
	var saw = LFSaw.ar(freq/2, 1).linlin(-1,1, width.neg, 1-width);
	
	saw = saw.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	saw = Select.ar(saw>0, [0.5+saw, saw]);

	[saw, tri]
}.plot;
)

I have still some further questions, maybe you can help:

1.) is sine ment to be the final output? in the paper its declared as a waveshaper, so it should always be applied to another signal, right?
2.) when applying sine (waveshaper) to another signal should it be multiplied or should it modulate the phase (this is kind of redundant or?) saw is ment to be the phaseshaper right?
3.) is it necessary to divide the freq always by 2 in the LFSaw ? when sine is the final output the freq/2 causes a freq down shift of the final sound

thank you very much :slight_smile:

1 Like

Is this what you mean:

({
	var width = MouseX.kr;
	var phasor = LFSaw.ar(MouseY.kr(100, 1000)).range(width.neg, 1-width);
	phasor = phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*2-1;
	phasor.dup*0.1
}.scope)

Have you looked at the Maths quark? The Maths is basically a variable slope triangle (I think), with a slope you can warp towards exponential or logarithmic. The quark is implemented in Faust, but this thread made me realize I could do it is SC as well. Below is the working code in SC. I will fold that into the quark when I have the time. To use this, just save this into a .sc file and save it into the extensions folder. The help for Maths will work with Maths2. They should give the exact same results.

Maths2 {
	*ar {|rise=0.1, fall=0.1, linExp = 0.5, loop = 1, plugged = 0, trig = 0|

		var freq = 1/(rise.clip(0.001,10*60)+fall.clip(0.001,10*60)), width=rise.clip(0.001,10*60)/(rise.clip(0.001,10*60)+fall.clip(0.001,10*60));

		var plugTrig = Trig1.ar(1-plugged, 1/SampleRate.ir);
		var loopTrig = Trig1.ar(loop, 1/SampleRate.ir);

		var phasor = Phasor.ar(Silent.ar+loopTrig+plugTrig, 2*freq/SampleRate.ir, -1, 1, -1);

		var phasorTrig = Trig1.ar(0.5-phasor, 1/SampleRate.ir)+EnvGen.ar(Env([0,0,1,0], [1/SampleRate.ir,1/SampleRate.ir,1/SampleRate.ir]));
		var latchTrig = (phasorTrig+(DelayN.ar(loopTrig, 0.01, 0.01))).clip(0,1);
		var postEnv =(Latch.ar(K2A.ar(loop), latchTrig)>0);


		var eof, eor;


		var inTrig = Trig1.ar(trig, 1/SampleRate.ir);
		var phasor2 = Phasor.ar(inTrig, 2*freq/SampleRate.ir, -1, 1, -1);
		var postEnv2 = SetResetFF.ar(Delay1.ar(inTrig), Trig1.ar(0.5-phasor2, 1/SampleRate.ir));
		var maths, maths2, interp;

		phasor = phasor.linlin(-1,1,width.neg, 1-width);
		maths = phasor.bilin(0, width.neg, 1-width, 0, -1, 1);
		maths = 1-(maths.abs);
		maths = maths*postEnv;

		phasor2 = phasor2.linlin(-1,1,width.neg, 1-width);
		maths2 = phasor2.bilin(0, width.neg, 1-width, 0, -1, 1);
		maths2 = 1-(maths2.abs);
		maths2 = maths2*postEnv2;

		maths = Select.ar(plugged, [maths,maths2]);

		interp = Select.kr(linExp>0.5, [linExp.linlin(0, 0.5, 1, 0) , linExp.linlin(0.5, 1, 0, 1)]);
		maths = Select.ar(linExp>0.5, [ maths-1, maths]);
		maths = (maths**8*interp)+(maths*(1-interp));
		maths = Select.ar(linExp>0.5, [ maths+1, maths]);

		eof = Select.ar(plugged, [(phasor*postEnv).neg>0, (phasor2*postEnv2).neg>0]);
		eor = Select.ar(plugged, [(phasor*postEnv)>0, (phasor2*postEnv2)>0]);

		^[maths, eof, eor]
	}
}

hey thanks :slight_smile:

(
{
	var width = 0.25;
	var freq = 400;
	var phasor = LFSaw.ar(freq/2).range(width.neg, 1-width);
	phasor = phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*2-1;
	[phasor]
}.plot
)

this gives control over the tilt, yes!
in the paper its also folded, do you have an idea how to adjust the code?
in the paper it looks like this:
Slope

2.) many thanks. i will have a look :slight_smile:

i think this is doing it now, but im not sure about the right amounts for Fold.ar and here has something to be changed as well sine = (Select.ar(phasor>0, [0.5+phasor, phasor])*2pi).sin; for the tilted triangle i think, or?

(
u = Signal.sineFill(512, [1]);
b = Buffer.loadCollection(s, u, 1);
)

//variable-slope ramp

(
{
	var width = 0.25;
	var freq = 400;
	var sine, shaper;
	var bufFrames = BufFrames.ir(b);
	var phasor = Phasor.ar(Impulse.ar(freq/2), freq/2 * SampleRate.ir.reciprocal, width.neg, 1-width, width.neg);
	phasor = phasor.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	sine = (Select.ar(phasor>0, [0.5+phasor, phasor])*2pi).sin;
	shaper = BufRd.ar(1, b, sine.range(0, bufFrames-1));
	
	[phasor, sine, shaper]
}.plot
)


//tilted triangle 
(
{
	var width = 0.25;
	var freq = 400;
	var sine, shaper;
	var bufFrames = BufFrames.ir(b);
	var phasor = Phasor.ar(Impulse.ar(freq/2), freq/2 * SampleRate.ir.reciprocal, width.neg, 1-width, width.neg);
	phasor = phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*2-1;
	phasor = Fold.ar(phasor, -0.5, 0.5);
	sine = (Select.ar(phasor>0, [0.5+phasor, phasor])*2pi).sin;
	shaper = BufRd.ar(1, b, sine.range(0, bufFrames-1));
	
	[phasor, sine, shaper]
}.plot
)

It is very close. Try it with Wrap instead of Fold:

({
	var width = MouseX.kr;
	var phasor = LFSaw.ar(MouseY.kr(100, 1000)).range(width.neg, 1-width);
	phasor = (phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*1.5).wrap(0,1);
	phasor.dup
}.plot)

wrap, fold, and clip are your friends. Look those up in the help.

Sam

thanks. unfortunately the extension for maths2 is not working.
i get the error message:

ERROR: Select arg: ‘which’ has bad input: false

  • Select requires a UGen input for the index.
  • An index such as linExp > 0.5 is a UGen if linExp is a UGen.
  • In a SynthDef, arguments are automatically promoted to UGens (outputs of a Control). So one might have expected argument linExp → promoted to UGen → comparison op → UGen result → Select would be fine.
  • But an *ar method of a pseudo-UGen class does not promote arguments to UGens. If you pass a fixed number for linExp, it will do e.g. 0 > 0.5 and produce false and break Select.

But Maths2 could detect the Boolean comparison results and prune branches that will never be used (which would be more efficient than simply wrapping numbers in UGens).

hjh

As an exercise, and also to illustrate some points about efficient SynthDef writing, here is a revision to Maths2 that also fixes the bug about scalar linExp values.

Edit: I’m being picky about it for a couple of reasons – mainly to demonstrate some things that we can think about while writing synths. Sam explained later that this was written very quickly on low battery without access to power, so it wasn’t finished. That’s fine! I hope it’s helpful that I went ahead with some updates.

The main points:

  • Multiplication is faster than division: so, x * SampleDur.ir is more efficient than x / SampleRate.ir.
  • SC does not automatically fold duplicate UGens down into one. If you write SampleDur.ir 10 times, there will be 10 instances of SampleDur.
    • This is even more important for operators. If you write linExp > 0.5 six times, it will actually do the identical comparison operation six times. It’s much more efficient to save the result in a variable and reuse the variable in multiple places.
  • The linear interpolation formula (y - x) * interp + x has one fewer multiplication than (y * interp) + (x * (1 - interp)).
    • In general, it’s worth looking for algebraic reductions: e.g., x.neg > 0 is the same as x < 0, except that the latter renders into a single UGen where the former requires two.
Maths2 {
	*ar { |rise = 0.1, fall = 0.1, linExp = 0.5, loop = 1, plugged = 0, trig = 0|

		var freq = (rise.clip(0.001, 10*60) + fall.clip(0.001, 10*60)).reciprocal;
		var width = rise.clip(0.001, 10*60) / (rise.clip(0.001, 10*60) + fall.clip(0.001, 10*60));

		// avoid multiple instances of the same UGen,
		// if it's known that they're all the same.
		// also dividing by SampleRate is slower than multiplying by SampleDur
		var sampleDur = SampleDur.ir;

		var plugTrig = Trig1.ar(1 - plugged, sampleDur);
		var loopTrig = Trig1.ar(loop, sampleDur);

		var eof, eor;
		var maths, maths2, interp, isExp;

		var phasor = Phasor.ar(Silent.ar + loopTrig + plugTrig, 2 * freq * sampleDur, -1, 1, -1);

		var phasorTrig, latchTrig, postEnv;
		var inTrig, phasor2, postEnv2;

		var pluggedIsVariable = plugged.rate != \scalar;
		var needMaths = pluggedIsVariable or: { plugged < 1 };
		var needMaths2 = pluggedIsVariable or: { plugged >= 1 };

		if(needMaths) {
			phasorTrig = Trig1.ar(0.5 - phasor, sampleDur) + EnvGen.ar(Env([0, 0, 1, 0], [sampleDur, sampleDur, sampleDur]));
			latchTrig = (phasorTrig + (DelayN.ar(loopTrig, 0.01, 0.01))).clip(0, 1);
			postEnv = (Latch.ar(K2A.ar(loop), latchTrig) > 0);

			phasor = phasor.linlin(-1, 1, width.neg, 1-width);
			maths = phasor.bilin(0, width.neg, 1-width, 0, -1, 1);
			maths = 1 - maths.abs;
			maths = maths * postEnv;
		};

		if(needMaths2) {
			inTrig = Trig1.ar(trig, sampleDur);
			phasor2 = Phasor.ar(inTrig, 2 * freq * sampleDur, -1, 1, -1);
			postEnv2 = SetResetFF.ar(Delay1.ar(inTrig), Trig1.ar(0.5 - phasor2, sampleDur));
			phasor2 = phasor2.linlin(-1, 1, width.neg, 1 - width);
			maths2 = phasor2.bilin(0, width.neg, 1 - width, 0, -1, 1);
			maths2 = 1 - maths2.abs;
			maths2 = maths2 * postEnv2;
		};

		if(pluggedIsVariable) {
			maths = Select.ar(plugged, [maths, maths2]);
		} {
			if(plugged >= 1) { maths = maths2 };
		};

		isExp = linExp > 0.5;  // don't repeat this 3 times

		// could reduce here as well, if linExp is scalar
		// but I'm tired now ;-p and these aren't expensive
		interp = Select.kr(isExp, [linExp.linlin(0, 0.5, 1, 0), linExp.linlin(0.5, 1, 0, 1)]);
		maths = Select.ar(isExp, [maths - 1, maths]);
		// maths = (maths ** 8 * interp) + (maths * (1 - interp));
		// - * + is more efficient than *, - * +
		maths = ((maths ** 8) - maths) * interp + maths;
		maths = Select.ar(isExp, [maths + 1, maths]);

		if(pluggedIsVariable) {
			// again, repeated multiplications...
			postEnv = phasor * postEnv;
			postEnv2 = phasor2 * postEnv2;
			// x.neg > 0, same as x < 0, save one unary op
			eof = Select.ar(plugged, [postEnv < 0, postEnv2 < 0]);
			eor = Select.ar(plugged, [postEnv > 0, postEnv2 > 0]);
		} {
			if(plugged < 1) {
				postEnv = phasor * postEnv;
				eof = postEnv < 0;
				eor = postEnv > 0;
			} {
				postEnv2 = phasor2 * postEnv2;
				eof = postEnv2 < 0;
				eor = postEnv2 > 0;
			};
		};

		^[maths, eof, eor]
	}
}

hjh

3 Likes

thanks alot for all the input @jamshark70 @Sam_Pluta. i was trying to get the tilted triangle out of maths2. looked at the source code for the pseudo uGen. but couldnt figure it out. any suggestions?

(
{
	var linExp = \linExp.kr(0.5);
	var rise = \rise.kr(0.01);
	var fall = \fall.kr(0.01);

	var freq = \freq.kr(150);
	var width = \width.kr(0.25);
	var trig = Impulse.ar(freq);
	var bufFrames = BufFrames.ir(b);
	var sampleDur = SampleDur.ir;

	var maths = Maths2.ar(
		rise: rise,
		fall: fall,
		linExp: linExp,
		loop: 0,
		plugged: 1,
		trig: trig
	)[0];

	var tilted_tri_maths = maths.wrap(0,1);

	var phasor = Phasor.ar(trig, freq * sampleDur, width.neg, 1-width, width.neg);
	var tilted_tri = (phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*1.5).wrap(0,1);

	[maths, tilted_tri_maths, phasor, tilted_tri]
}.plot
)

Notice what I was doing with the tilted tri. I was multiplying the signal by 1.5 before wrapping, so you need to do that here as well. It can’t wrap if it doesn’t go beyond the wrap points, and by nature, it only goes between 0 and 1.

You can get some really cool looking waveforms with this. How they sound is another can of worms!

(
{
	var linExp = \linExp.kr(0.9);
	var rise = \rise.kr(0.001);
	var fall = \fall.kr(0.001);

	var freq = \freq.kr(150);
	var width = \width.kr(0.25);
	var trig = Impulse.ar(freq);
	//var bufFrames = BufFrames.ir(b);
	var sampleDur = SampleDur.ir;

	var maths = Maths2.ar(
		rise: rise,
		fall: fall,
		linExp: linExp,
		loop: 1,
		plugged: 0/*,
		trig: trig*/
	)[0];
	
	var tilted_tri_maths = (maths*1.5).wrap(0,1);

	var phasor = Phasor.ar(trig, freq * sampleDur, width.neg, 1-width, width.neg);
	var tilted_tri = (phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*1.5).wrap(0,1);

	[maths, tilted_tri_maths, phasor, tilted_tri]
}.plot
)

Actually, this kind of manipulation (multiply by 1.5, subtract 0.5 and do an absolute value) might sound better in both cases:

(
{
	var linExp = \linExp.kr(0.8);
	var rise = \rise.kr(0.001);
	var fall = \fall.kr(0.001);

	var freq = \freq.kr(150);
	var width = \width.kr(0.25);
	var trig = Impulse.ar(freq);
	//var bufFrames = BufFrames.ir(b);
	var sampleDur = SampleDur.ir;

	var maths = Maths2.ar(
		rise: rise,
		fall: fall,
		linExp: linExp,
		loop: 1,
		plugged: 0/*,
		trig: trig*/
	)[0];
	
	var tilted_tri_maths = (maths*1.5-0.5).abs;

	var phasor = Phasor.ar(trig, freq * sampleDur, width.neg, 1-width, width.neg);
	var tilted_tri = (phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*1.5-0.5).abs;

	[maths, tilted_tri_maths, phasor, tilted_tri]
}.plot
)

thanks a lot. im not sure how to modulate the width in the best possible way for the phase shaping because rise and fall are binded together. but i think it sounds quite nice already :slight_smile:

EDIT
In this video NI Reaktor - Vector Phaseshaping Synthesis - YouTube following https://www.researchgate.net/publication/236616428_Vector_Phaseshaping_Synthesis he has one controlknob d for the point of inflection with values between 0-1 and he is building two macros for
1.) phase x < d and 2.) phase x > d
and has two functions for the first macro → 0.5 * x / d and for the second → 0.5 * [1+(x - d / 1 - d) ] (phase distortion). in addition he is replacing the fixed value 0.5 with the variable v for vector phase shaping. i would like to apply this procedure to our tilted tri / slope ramp to build the vector phase shaping osc. so i think i have to change (Select.ar(tri > 0, [0.5 + tri, tri]) right? d is represented in our original code by the variable width right?

is this correct for pd? im confused with .bilin

(
{

	var freq = \freq.kr(150);
	var width = \width.kr(0.10);
	var trig = Impulse.ar(freq);
	var sampleDur = SampleDur.ir;

	var phasor = Phasor.ar(trig, freq * sampleDur, width.neg, 1-width, width.neg);
	//var tri = (phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*1.5-0.5).abs;
	var saw = phasor.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	
	var pd = (Select.ar(saw<0, [0.5 * (saw/width), 0.5 * [1+(saw-width/1-width)]])*2pi).sin;

	[saw, pd]
}.plot
)

introducing variable vector for vector phase shaping

(
{

	var freq = \freq.kr(50);
	var vector = MouseY.kr(0.01,3);
	var width = MouseX.kr(0.01,0.99);
	var trig = Impulse.ar(freq);
	var sampleDur = SampleDur.ir;

	var phasor = Phasor.ar(trig, freq * sampleDur, width.neg, 1-width, width.neg);
	var tri = (phasor.bilin(0, width.neg, 1-width, 0, -1, 1).abs*1.5-0.5).abs;
	//var saw = phasor.bilin(0, width.neg, 1-width, 0, -0.5, 1);
	
	//var vps = (Select.ar(saw<0, [vector * (saw/width), (1 - vector) * (saw-width/1-width) + vector ]) * 2pi).sin;
	var vps = (Select.ar(tri<0, [vector * (tri/width), (1 - vector) * (tri-width/1-width) + vector]) * 2pi).sin;

	vps = LeakDC.ar(vps);
	vps!2 * 0.1
}.scope
)

Im wondering if this is right, it looks exactly like in the paper Figure 2 “VPS waveforms and spectra”:

(
{
	var freq = \freq.kr(500);
	var width = 0.5;
	var vector = 0.85;
	var phasor, vps;
	
	phasor = LFSaw.ar(freq,1).linlin(-1, 1, width.neg, 1-width);
	phasor = phasor.bilin(0, width.neg, 1-width, vector, 0, 1);
	
	vps = (phasor * 2pi).cos.neg;
	
	[phasor, vps]
}.plot;
)

Looks good to me. Sounds good to me. Isn’t playing with waveforms fun?

Sam

haha thanks a lot. yeah its a lot of fun and also a bit frustrating because im missing some of the math. but im learning a lot :slight_smile:
I would also like to add the alising suppression described in chapter 3.2 and found this pseudo Ugen classes in Glitch free vowel synthesis test - #2 by Geoffroy. already another difficult task. lets see.

Nearest_Even {
	*ar {
		arg val;
		var val_floor, val_ceil, res, distance;
		val_floor = val.floor;
		val_ceil = val.ceil;
		res = Select.ar (val % 2,
			[ val_floor, val_ceil ],
		);
		distance = (val - res).abs;
		^ [ res, distance ];
	}
}

Nearest_Odd {
	*ar {
		arg val;
		var val_floor, val_ceil, res, distance;
		val_floor = val.floor;
		val_ceil = val.ceil;
		res = Select.ar (val + 1 % 2,
			[ val_floor, val_ceil ],
		);
		distance = (val - res).abs;
		^ [ res, distance ];
	}
}

Crossfade_Formant {
	*ar {
		arg phasor = 0, phase_mod = 0, harm = 1, pm_index = 1, amp = 1; // lag = 0;
		var harm_even, harm_odd, sig_even, sig_odd, sig, phase;

		phase = phase_mod * pm_index;

		harm_even = Nearest_Even.ar (harm);
		sig_even = cos (phasor * 2pi * harm_even[0] + phase);

		harm_odd = Nearest_Odd.ar (harm);
		sig_odd = cos (phasor * 2pi * harm_odd[0] + phase);

		sig = XFade2.ar (sig_even, sig_odd, harm_even[1] * 2 - 1) * amp;
		^ sig;
	}
}
1 Like

I made a first approach. i think changing the values for the vector is kind of smooth now, but im not sure if its the exact same approach presented in the paper. any further ideas? :slight_smile:

(
~nearest_even = {|val|
	var val_floor, val_ceil, res, distance;
	val_floor = val.floor;
	val_ceil = val.ceil;
	res = Select.ar (val % 2,
		[ val_floor, val_ceil ],
	);
	distance = (val - res).abs;
	[res, distance];
};

~nearest_odd = {|val|
	var val_floor, val_ceil, res, distance;
	val_floor = val.floor;
	val_ceil = val.ceil;
	res = Select.ar (val + 1 % 2,
		[ val_floor, val_ceil ],
	);
	distance = (val - res).abs;
	[res, distance];
};
)

(
SynthDef(\vector, {
	arg out=0, amp=0.25, freq=150;
	var horizontal = MouseX.kr(0.01,0.99);
	var vertical = K2A.ar(MouseY.kr(1,10)); 
	var sig, sig_even, sig_odd, phasor, phasor_even, phasor_odd, harm_even, harm_odd;

	harm_even = ~nearest_even.(vertical);
	harm_odd = ~nearest_odd.(vertical);
	
	phasor = LFSaw.ar(freq,1).linlin(-1, 1, horizontal.neg, 1-horizontal);
	phasor_even = phasor.bilin(0, horizontal.neg, 1-horizontal, harm_even[0], 0, 1);
	phasor_odd = phasor.bilin(0, horizontal.neg, 1-horizontal, harm_odd[0], 0, 1);
	
	sig_even = (phasor_even * 2pi).cos.neg;
	sig_odd = (phasor_odd * 2pi).cos.neg;
	
	sig = XFade2.ar (sig_even, sig_odd, harm_even[1] * 2 - 1) * amp;

	sig = Splay.ar(sig);
	Out.ar(out, sig);
}).add;
)

(instrument: \vector, midinote: 31).play;
1 Like

I dont know how many hours i already spent on this one, and still not being able to figure it out. :smiley:
1.)
I think my first approach has somehow the right ingredients but is not yielding the desired result. The Crossfade between even/odd is following somehow the idea presented at the end of chapter 3.1 Synthesising Formants. but the plots dont look right and the sound has a totally different quality to it. Any Ideas? :slight_smile:
2.)
Im was also trying to come up with a solution for 3.2 Aliasing Suppression, which should modify the phaseshaper when the phase is inside an incomplete period and should render it as a smooth full-cycle sinusoid like in Fig. 5b.
Unbenannt

I think the modified phasor looks like in the paper but the signal is not rendered as a smooth full-cycle sinusoid at the inflection point. Any ideas? :slight_smile:

(
{
	arg freq = 392;
	var horizontal = \horizontal.kr(0.80);
	var vertical = \vertical.ar(2.2);
	var cos, phasor;

	phasor = LFSaw.ar(freq/2,1).range(horizontal.neg, 1-horizontal);
	phasor = phasor.bilin(0, horizontal.neg, 1-horizontal, vertical, 0, 1);
	phasor = Select.ar(phasor > vertical.floor, [phasor, phasor.abs.wrap(0,1)]);

	cos = (phasor * 2pi).cos.neg;

	[phasor, cos]
}.plot
)