Reverse engineering Formant.ar using only Sine oscillators

Hello, just to have a better understanding of this Ugen, could someone put me on track here ? Thanks a lot.

Do you want to do this in a SynthDef with just SinOsc? Or … what do you mean by ‘on track?’

Yes, a SynthDef with SinOsc’s (do we need to use also a BPF ?).
I would like to understand the structural relation between fundfreq / formfreq / bwfreq…
Thanks !

Great - that helps. And yes, I’m guessing in the end you’ll need more that SinOscs (which makes the title of the topic a little confusing unless you want an example to get as close as you can). I don’t have an answer at the moment, but I’ll take a look later to see what I can suggest.

for creating formants you need two resonances. for example two RLPFs or two SinOscs:

(
{
	var lfo = MouseX.kr(1,0);
	var sig = SinOsc.ar(lfo.linexp(0, 1, 100, 500)) + SinOsc.ar(lfo.linexp(0, 1, 1000, 500));

	sig ! 2 * 0.1;
}.play;
)

s.freqscope;

EDIT: here are also two versions of windowed sync which is doing a kind of fake RLPF by shifting the formants:

1.)

(
SynthDef(\windowsync, {
	arg syncEgTop=8, syncRatio=2, syncDcy=0.5;

	var syncEnv = EnvGen.kr(Env([syncEgTop / syncRatio, 1], [syncDcy], \exp));
	var gainEnv = EnvGen.kr(Env.adsr(0.01, 0.3, 0.6, 0.1), \gate.kr(1), doneAction: Done.freeSelf);

	var freq = \freq.ar(440);
	var sig, in, phase, synced;
	
	in = LFTri.ar(freq);
	phase = Sweep.ar(in, freq * syncRatio * syncEnv);
	synced = SinOsc.ar(0, (phase % 1) * 2pi).squared;
	
	sig = synced * in;

	sig = sig * gainEnv;

	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.25));
	Out.ar(\out.kr(0), sig);
}).add;
)

(instrument: \windowsync, freq: [57,60,64,65,70].midicps, syncEgTop: 20).play;

2.)

(
SynthDef(\windowsync, {
	arg freq=440, syncRatio=2, syncEgTop=20, syncDcy=0.5, overlap=1;
	var sig, fundamental, synced;

	var freqEnv = EnvGen.kr(Env([syncEgTop / syncRatio, 1], [syncDcy], \exp));
	var gainEnv = EnvGen.kr(Env.adsr(0.01, 0.3, 0.6, 0.1), \gate.kr(1), doneAction: Done.freeSelf);

	fundamental = GrainSin.ar(
		numChannels: 1,
		trigger: Impulse.ar(freq),
		dur: 1 / freq,
		freq: freq,
	);

	synced = GrainSin.ar(
		numChannels: 1,
		trigger: fundamental,
		dur: overlap / freq,
		freq: freq * syncRatio * freqEnv,
	);

	sig = synced.squared * fundamental;
	sig = LeakDC.ar(sig);

	sig = sig * gainEnv;

	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.25));
	Out.ar(\out.kr(0), sig);
}).add;
)

(instrument: \windowsync, freq: [57,60,64,65,70].midicps, syncEgTop: 20).play;

and a vector phase shaping pseudo Ugen ive been working on some time ago. whichs produces formants for bigger vertical values:

(
var nearestEven, nearestOdd, vps;

nearestEven = {
	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 ];
};

nearestOdd = {
	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 ];
};

vps = { |trig, freq, horizontal, vertical|
	var vertical_even = nearestEven.(vertical);
	var vertical_odd = nearestOdd.(vertical);
	var cos, phasor, sig;
	vertical = [vertical_even[0], vertical_odd[0]];
	phasor = Phasor.ar(Impulse.ar(trig), freq/2 * SampleDur.ir, horizontal.neg, 1-horizontal, horizontal.neg);
	phasor = phasor.bilin(0, horizontal.neg, 1-horizontal, vertical, 0, 1);
	cos = (phasor * 2pi).cos.neg;
	sig = XFade2.ar(cos[0], cos[1], vertical_even[1] * 2 - 1);
	sig = LeakDC.ar(sig);
	sig;
};

{
	var freq = \freq.kr(110);
	var horizontal = MouseX.kr(0.01,0.99);
	var vertical = K2A.ar(MouseY.kr(1.0,10.0));
	var sig = vps.(0, freq,  horizontal, vertical);
	sig!2 * 0.1;
}.play(fadeTime: 0);
)
1 Like

Great examples, the vector phase is wonderfull, thanks a lot @ dietcv !