About fof synthesis and Formlet

Hi there, been searching for FOF synthesis with SuperCollider without much luck. I know about the Formlet UGen but the help file only hint that “it has advantages over FOF” and no real examples on how to use it to general vocal/vowel sounds.

Any hints and help?

Thanks

heya,

not sure if this is what you’re looking for (possibly not) but there’s some Vowel synthesis done some time ago by Florian Grond and me in the Vowel quark.
I’d be interested in FOF etc., too :slight_smile:

There are two good ways of synthesizing formants with control over formant shift and bandwidth, modFM or singleSidebandPM (posted these already in other threads about formant synthesis):

modFM

(
var raisedCos = { |phase, index|
	var cosine = cos(phase * 2pi);
	exp(index.abs * (cosine - 1));
};

var crossfade_formants = { |phase, harm|
	var harmEven = harm.round(2);
	var harmOdd = ((harm + 1).round(2) - 1);
	var sigEven = sin(phase * 2pi * harmEven);
	var sigOdd = sin(phase * 2pi * harmOdd);
	LinXFade2.ar(sigEven, sigOdd, harm.fold(0, 1) * 2 - 1);
};

var modFM = { |freq, harm, index|
	var phase = Phasor.ar(DC.ar(0), freq * SampleDur.ir);
	var raisedCosWindow = raisedCos.(phase, index);
	var formants = crossfade_formants.(phase, harm);
	formants * raisedCosWindow;
};

SynthDef(\formant, {

	var harmMod, harmonics, sig;

	harmMod = SinOsc.ar(\freqMF.kr(0.3), [0.5, 1.0] * 2pi);

	harmonics = [
		\harmA.kr(8) * (2 ** (harmMod[0] * 3)),
		\harmB.kr(16) * (2 ** (harmMod[1] * 1))
	];

	sig = modFM.(\freq.kr(440), harmonics, \index.kr(1)).sum;

	sig = Pan2.ar(sig, \pan.kr(0));

	sig = sig * \amp.kr(-25.dbamp);

	sig = sig * Env.asr(0.001, 1, 0.001).ar(Done.freeSelf, \gate.kr(1));

	sig = LeakDC.ar(sig);
	Out.ar(\out.kr(0), sig);
}).add;
)

(
Routine({

	var ratios = [1.00, 1.04];
	var pan = [-1.0, 1.0];

	s.bind {
		ratios.collect{ |ratio, i|
			var freq = 125;

			Synth(\formant, [

				\freq, freq * ratio,
				\index, 8,

				\amp, -25.dbamp,
				\pan, pan[i],
				\out, 0,

			]);

		};
	};

}).play;
)

singleSidebandPM

(
var raisedCos = { |phase, index|
	var cosine = cos(phase * 2pi);
	exp(index.abs * (cosine - 1));
};

var singleSideBandPM = { |freq, modRatio, index|
	var carrPhase = Phasor.ar(DC.ar(0), freq * SampleDur.ir);
	var modPhase = Phasor.ar(DC.ar(0), freq * modRatio * SampleDur.ir);
	var raisedCosWindow = raisedCos.(modPhase, index);
	var mod = sin(modPhase * 2pi);
	var carr = sin(carrPhase * 2pi + (mod * index));
	carr * raisedCosWindow;
};

SynthDef(\singleSidebandPM, {

	var indexMod, indices, sig;

	indexMod = SinOsc.ar(\freqMF.kr(0.3), [0.5, 1.0] * 2pi);

	indices = [
		\indexA.kr(8) * (2 ** (indexMod[0] * 3)),
		\indexB.kr(16) * (2 ** (indexMod[1] * 1)),
	];

	sig = singleSideBandPM.(\freq.kr(110), \modRatio.kr(1), indices).sum;

	sig = Pan2.ar(sig, \pan.kr(0));

	sig = sig * \amp.kr(-25.dbamp);

	sig = sig * Env.asr(0.001, 1, 0.001).ar(Done.freeSelf, \gate.kr(1));

	sig = LeakDC.ar(sig);
	Out.ar(\out.kr(0), sig);
}).add;
)

(
Routine({

	var ratios = [1.00, 1.04];
	var pan = [-1.0, 1.0];

	s.bind {
		ratios.collect{ |ratio, i|
			var freq = 125;

			Synth(\singleSidebandPM, [

				\freq, freq * ratio,
				\modRatio, 1.5,

				// amp & outs
				\amp, -25.dbamp,
				\pan, pan[i],
				\out, 0,

			]);
		};

	};

}).play;
)
1 Like

The advantage over FOF is that you don’t need grain tracking. AFAICS the advantage isn’t necessarily that it sounds better, though.

Part of the trick is to relate bandwidth from formant tables over to attack and decay times. I’m assuming the right relationship is inverse (as bandwidth goes up, ring time hence attack and decay times should go down) but tuning the sound was (for me, with my poor maths) trial and error and I’m not entirely satisfied with the result. The baseline atk = 0.001 and dcy = 0.008 seems to strike a good balance between letting the pulses through (so that the fundamental is audible) and letting the formants color the sound. IMO FM formants sound better though (but this approach might work well with more tuning).

This code example is based on a FM-formant demo I’d thrown together a couple of years ago; I just deleted the FM bits and replaced them with Formlet usage. I found that both versions are a bit piercing in the 2000-4000 Hz range, suggesting that FormantTable’s amps for the high partials may be too high.

(
s.waitForBoot {
	var cond = CondVar.new;
	var k = "IEAOU".collectAs({ |chr| ("tenor" ++ chr).asSymbol }, Array);
	var formantBuf;
	var formantBus = Bus.control(s, 15);
	var freqBus = Bus.control(s, 1);
	var window, canvas, freqNumber;

	// [[ 5freqs, 5bws, 5amps ], [ 5freqs, 5bws, 5amps ]]
	var formants = k.collect { |id| FormantTable.at(id) };

	// [[ 5freqs, 5freqs, 5freqs ... ], [ 5bws, 5bws, 5bws ... ] ...]
	formants = formants.flop;

	// [[ low formants for 5 vowels, 2nd formants for 5 vowels etc... ]]
	formants = formants.collect(_.flop);
	formants.flat.size.debug;

	formantBuf = Buffer.sendCollection(s, formants.flat, 1, action: {
		cond.signalOne
	});

	b = formantBuf; c = formantBus; d = freqBus;

	~vowelSelector = SynthDef(\vowelSelector, { |out, bufnum, freqOut, x, y|
		var vowel = Lag.kr(y, 0.1) * 3.999; // MouseY.kr(0, 3.999);
		var offset5 = Array.series(5, 0, 5);
		// using BufRd for automatic linear interpolation
		var freqs = BufRd.kr(1, bufnum, vowel + offset5, loop: 0);
		var bws = BufRd.kr(1, bufnum, vowel + (offset5 + 25), loop: 0);
		var amps = BufRd.kr(1, bufnum, vowel + (offset5 + 50), loop: 0);
		Out.kr(out, freqs ++ bws ++ amps);
		Out.kr(freqOut, Lag.kr(x, 0.1).linexp(0, 1, 50, 800) /*MouseX.kr(50, 800, 1)*/);
	}).play(args: [out: formantBus, freqOut: freqBus, bufnum: formantBuf]);

	~formants = SynthDef(\formants, { |out, freq = 440, formantBus, atk = 0.001, dcy = 0.008, amp = 0.2|
		var freqs, bws, amps;
		var quotient, xfades, evenCar, oddCar, sig;

		// IMO it sounds better with Blip
		// var pulses = Impulse.ar(freq);
		var pulses = Blip.ar(freq, 11000 / freq);
		
		#freqs, bws, amps = In.kr(formantBus, 15).clump(5);

		// as bandwidth increases, atk and dcy should decrease
		sig = Formlet.ar(pulses, freqs, atk / bws, dcy / bws) * amps;
		sig = sig.sum;
		
		// empirically, 2000-4000 Hz formants are too prominent
		sig = MidEQ.ar(sig, 3000, 1.9, -24);

		Out.ar(out, LeakDC.ar(sig).dup);
	}).play(args: [formantBus: formantBus, freq: freqBus.asMap]);

	~fx = {
		var sig = In.ar(0, 2);
		sig = sig + DelayC.ar(sig, 0.2,
			Array.fill(2, {
				var predelay = Rand(0.005, 0.01);
				var width = Rand(0.7, 0.98);
				SinOsc.kr(ExpRand(0.1, 0.4), Rand(0.5, 3.0))
				* (width * predelay) + predelay
			})
		);
		ReplaceOut.ar(0, FreeVerb2.ar(sig[0], sig[1], 0.4, 0.8, 0.3))
	}.play(target: s.defaultGroup, addAction: \addToTail);

	window = Window("Formants", Rect(800, 200, 500, 500)).front;
	window.layout = VLayout(
		HLayout(
			canvas = Slider2D(),
			View().fixedWidth_(50)
			.layout_(
				VLayout(
					StaticText().string_("U"),
					nil,
					StaticText().string_("O"),
					nil,
					StaticText().string_("A"),
					nil,
					StaticText().string_("E"),
					nil,
					StaticText().string_("I")
				)
			)
		),
		HLayout(
			nil,
			StaticText().string_("freq").fixedWidth_(60),
			freqNumber = StaticText().fixedWidth_(60),
			nil
		)
	);

	canvas.action = { |view|
		~vowelSelector.set(\x, view.x, \y, view.y);
		freqNumber.string = view.x.linexp(0, 1, 50, 800).asString;
	};

	window.onClose = {
		[~vowelSelector, ~formants, ~fx].do(_.free);
	};

	~formants.onFree({
		[formantBus, freqBus, formantBuf].do(_.free);
	});
};
)

BTW in your Pd forum thread, you mentioned implementing Formlet for Pd – but I think you already had done this before. [resonant2~] help says “[resonant2~] is a resonator just like [resonant~], but you you can specify an attack time besides a decay time” and… that’s Formlet, isn’t it? So no need to have implemented it a second time.

hjh

That object has been removed a couple of versions ago or something :slight_smile:

I now worked on a different design and this new name.

The thing is, I’m not convinced this is an interesting/must have object :wink:

This is why I was asking for examples on how to use it for Formant Synthesis.

Cheers

Ok, I’m making some sense of it. It seems that if you feed an impulse with a frequency you get the f0 (fundamental). Then, the formlet frequency is the formant (or ‘center’) frequency.

What I’m not really capable of grasping is how to set the bandwidth over the formant frequency :slight_smile:

In Csound’s fof and other similar techniques you get to set the bandwidth in hertz.

Now, the code above sets the bandwidth via the attack and decay times, but it’s still unclear how to set it with hertz values.

Now, could we have a more minimal example, similar to the help file?

thanks

Formlet doesn’t promise to do this. There are other bandpass filters that use the reciprocal of Q for the inverse of the bandwidth.

Formlet is more useful for an additive-synthesis approach with resonators. It has no particular advantage over standard bandpass filters except that it expresses bandwidth in terms of ring time. So if you don’t want to express Q in terms of ring time, then Formlet (or Ringz) is not the right choice.

Edit: It’s possible, by doing some algebra on the coefficient calculation in Resonz vs Ringz, to derive the relationship between Q and ring time… but I’ve mislaid it and can’t post it just now.

Edit edit: Found it – given a center frequency, sample rate and decay time, this will give you Q. To calculate decay time from Q, solve for dt (which… I don’t have time at the moment, “exercise for the reader”):

// Ringz Q
q = { |f, sr, dt|
	var r = exp(log(0.001) / (dt * sr));
	(f * 2pi/sr) / (1 - r) * 0.5
};

hjh

Hi, I see now that fof does something like this. I’m not looking closely to your formulas, but they look familiar to what I’ve been working with.

If you use Decay, it seems you can get ms from bandwidth like this.

ms = (log(1000.0) / (pi * BW)) * 1000

Anyway, a bigger bandwidth means a shorter decay. It doesn’t sound intuitive to me as the sound is thinner and quieter, and it’s unlike the “paf” algorhythm from IRCAM, but I get this is what “FOF” and “Formlet” do.

I also see now how Formlet is a nice and clever way to implement a ‘fof-like’ algorhythm.

Next related question: What about the “Formant” UGen? It seems to sit in between FOF and Paf, but more like Paf :wink:

The bandwidth here just tends to make it sound “brighter” to me, though.

Anyway, the question is, what is the “source” of this? Where does it come from? I didn’t see any references.

thanks

OK, I ended up doing the derivation :wink:

q = (f * 2pi/sr) / (1 - exp(log(0.001) / (dt * sr))) * 0.5

2q / (f * 2pi/sr) = 1 / (1 - exp(log(0.001) / (dt * sr)))

(f * 2pi/sr) / 2q = 1 - exp(log(0.001) / (dt * sr))

exp(log(0.001) / (dt * sr)) = 1 - ((f * 2pi/sr) / 2q)

log(0.001) / (dt * sr) = log(1 - ((f * 2pi/sr) / 2q))

(dt * sr) / log(0.001) = 1 / log(1 - ((f * 2pi/sr) / 2q))

dt = log(0.001) / (sr * log(1 - ((f * 2pi/sr) / 2q)))

= log(0.001) / (sr * log(1 - (2 * f * pi / (2 * sr * q)))

= log(0.001) / (sr * log(1 - (f * pi / (sr * q)))

(
// Ringz Q
q = { |f, sr, dt|
	var r = exp(log(0.001) / (dt * sr));
	(f * 2pi/sr) / (1 - r) * 0.5
};

// Resonz decay time
d = { |f, sr, q|
	log(0.001) / (sr * log(1 - (f * pi / (sr * q))))
};
)

q.(440, 44100, 1);  // Q = 200

// the actual Q value returned by the 'q' function
d.(440, 44100, 200.1242046503);  // dt = 1.0000000000002

So ‘d’ is a proper inverse of ‘q’.

But I have to run to something else… I’ve got thoughts about the other questions, just no time right now.

hjh

Resonz help mentions a paper by Stieglitz about constant-gain filters. Then, Ringz help explains that the difference between Resonz and Ringz is (apart from the specification of bandwidth in terms of ring time vs Q) that Resonz is constant gain where Ringz is constant skirt gain.

I think this is how the magic works in Formlet: that the skirt (stopband) gain is more or less the same means that the (shorter) attack filter cancels out some of the spectrum of the decay filter. Without this, you don’t have an attack-decay characteristic. This isn’t deriving the result mathematically, but it’s my intuitive understanding. Not sure this is enough for the context of this thread :laughing:

hjh

I was asking about the source of Formant, not Formlet :slight_smile:

Happy new year, ping jamshark70 and others, any idea on what is the source/reference for Formant?

No, I don’t know – without a citation in the help file, there’s not much I can say.

hjh