Inaccuracy using Env.ar as Waveshaper

Hello, I’m trying to use a Env Generator for wave-shaping. The reason for this is to be able to retrigger it with the gate argument (connecting to to a different frequency) to add harmonics to this. I borrowed this idea from some analogue-like synthesizers. However i get strange artifacts and especially the pitch is not correctly done.

The first code block shows how the indexer without the problems of the accuracy in the EnvGen.
Under this, i try to create the same indexer with a Env.ar, however i think the steps of time duration ‘0’, still add one frame to the total Env. This leads to an effect that the intended wave keeps shifting to the right with every iteration.

//this is how i want it to be looking like
({
	var f, index, shape;
	shape = 0.2;
	f = 100;
	index = LFSaw.ar(f,1);
	index = index + LFPulse.ar(f*2,0.5,shape,2-(2*shape));
	index = index + LFPulse.ar(f*2,0.5+shape,shape,(2*shape)+2);
	index = index.wrap(-1,1);	
}.plot(0.03))
//this is how my Env looks like, it has a minor shift
({
	var f, index, shape;
	shape = 0.2;
	f = 100;
	index = EnvGen.ar(Env(
		[-1,-1,-0.5-shape,-0.5+shape,-0.5+(2*shape),-0.5-(2*shape),-0.5-shape,-0.5+shape,0.5-shape,0.5+shape,0.5+(2*shape),0.5-(2*shape),0.5-shape,0.5+shape,1,1],
		[0,0.25-(shape/2),0,shape/2,0,shape/2,0,0.5-(shape),0,shape/2,0,shape/2,0,0.25-(shape/2),0],
		0,14,0),1,timeScale:1/f);
	index = index.wrap(-1,1);
}.plot(0.03))

My question, is there a better way of archieving a retriggerable Indexer or how do i bypass this problem with my Env.ar? Maybe a different method than .new? However i haven’t found another method that can loop.

Thank you very much!

The Env starts at -1 and ends at 1, but it has one more point in the array than you want, so it is always out of phase by one sample.

I think you want to load the env into a buffer and read through the buffer using a combination of Phasor (which can retrigger) and Osc, which uses your env as a wavetable:

(
var shape = 0.2;

a = Env(
		[-1,-1,-0.5-shape,-0.5+shape,-0.5+(2*shape),-0.5-(2*shape),-0.5-shape,-0.5+shape,0.5-shape,0.5+shape,0.5+(2*shape),0.5-(2*shape),0.5-shape,0.5+shape,1,1],
		[0,0.25-(shape/2),0,shape/2,0,shape/2,0,0.5-(shape),0,shape/2,0,shape/2,0,0.25-(shape/2),0],0,14,0).asSignal(512);

b = Buffer.alloc(s, 512, 1);
b.loadCollection(a);
)


(
{ arg out=0,bufnum=0;
	var freq = MouseX.kr(0.5, 2);
    var phase = Phasor.ar(MouseButton.kr, freq/SampleRate.ir*2pi, 0, 2pi); //MouseButton resets the phase of the oscillator
    var modulator = Osc.ar(bufnum, 0, phase, 0.5);
	SinOsc.ar(200+(modulator*100), 0, 0.1)
}.play(s,[\out, 0, \bufnum, b.bufnum]);
)
1 Like

i think you can probably use a statelessWindow here:

(
var statelessWindow = { |levels, times, curve, phase|
	var x = 0;
	var window = times.size.collect({ |i|
		var x2 = x + times[i];
		var result = (phase >= x) * (phase < x2) * phase.lincurve(x, x2, levels[i], levels[i+1], curve[i]);
		x = x2;
		result;
	}).sum;
	window = window * (phase < 1);
	window;
};

{
	var shape = SinOsc.ar(10).linlin(-1, 1, 0.2, 0.40);
	var tFreq = 10;
	var trig = Impulse.ar(tFreq);
	var phase = Sweep.ar(trig, tFreq);
	
	var window = statelessWindow.(
		levels: [-1, -0.5-shape, -0.5+shape, -0.5+(2*shape), -0.5-(2*shape), -0.5-shape, -0.5+shape, 0.5-shape, 0.5+shape, 0.5+(2*shape), 0.5-(2*shape), 0.5-shape, 0.5+shape, 1],
		times: [0.25-(shape/2), 0.0001, shape/2, 0.0001, shape/2, 0.0001 ,0.5-(shape), 0.0001, shape/2, 0.0001, shape/2, 0.0001, 0.25-(shape/2)],
		curve: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		phase: phase
	);
	
	window;
	
}.plot(0.2);
)
1 Like

thank u very much! mmh this doesnt work out exactly for me because i cannot fixate the env in a buffer because i need to be able to manipulate the shape variable. atm it solves the problem with the windowsize. but i think my question is solved by the other response.
anyhow thanks again!!

thank u very much, that does the trick for what i wanted!!
i havn’t seen such a method before, its very interesting, it could also be used as a dynamic envelope in general i suppose. very nice (:

im using it for grain windows, but i think it also could be used for phase or waveshaping like in your case.
where have you picked up this specific shape?

i try to reverse engineer the korg minilogue xd and they use 3 different wave shapers for their saw, tri and pulse oscilators. this shape here is going for the triangle oscilator. i was wondering if i can post my code here in the forums once im finished, i wondered if there is any legal issues, even though most likely nothing would happen even if there was a problem. but i came up with this specific shape to copy their wave shaper.

grain windows sounds also cool. is it such a big difference though to create ur own grain windows?

ive been using some of these signals as grain windows or FM modulators per grain. there are also some waveshapers in the package.

But i like to get rid of all these buffered sounds, they feel kind of less expressive to me and lead to some problems when you would like to sequence different onces. so atm im trying to find ways to come up with ways to synthesize some of these which i liked.

I am a big fan of @dietcv’s StatelessWindow. It is a brilliant solution to many a problem, that I had certainly never considered.

One issue with it is that, at least as far as I understand it, it has to construct the entire window every time it accesses any value. This is fine with one of these running, but with many, CPU issues could arise. Another solution would be construct it out of Sweeps that convert to the square waves. I think this is sample accurate to the original:

(

var myPulse = {arg sweep, shape=0.2;
	var square = sweep.wrap(-1, shape*2);
	(square>0)
};

var myPulseB = {arg sweep, shape=0.2;
	var square = (sweep+(shape*2)).wrap(-1,shape*2);
	(square>0)
};

var mine = {
	var shape = MouseY.kr(0.01,0.25).poll;
	var freq = MouseX.kr(200, 400);
	var sweep = Sweep.ar(Impulse.ar(freq), freq*2);

	var sig = (sweep-1)+
	(myPulse.((sweep-0.5*2).wrap(-1,1), shape)*(2-(2*shape)))+
	(myPulseB.((sweep-0.5*2).wrap(-1,1), shape)*(2+(2*shape)));

	sig.wrap(-1,1)*0.05
}.play;
)
4 Likes

Thank you for posting another solution!
Also a way i havn’t been yet familiar with.