FM - Analog Synth Style

For some of you this may be an obvious thing and for others it may be an “aha” moment, as it was for me. I have always used Chowning’s formulas for FM to create lovely sidebands, like this:

({
	var mod = SinOsc.ar(MouseX.kr(5,700));
	var freq = 410;
	SinOsc.ar(freq +(mod*1000), 0, 0.1)
}.play)

Playing with my analog today, I realized, while this can create brass tones galore, it is not how analog synths make those fat FM tones they do.

What the analog synth seems to be doing is using the FM voltage to change the octave of the carrier frequency. Assuming the oscillator is operating at +/-5v, in a 1v/oct input, plus 5v means up 5 octaves and -5v means down 5 octaves. So, if the carrier frequency is C and the modulator voltage is M, then the resulting frequency is C*(2**M). A carrier of 200hz, for instance, with a modulator at 1V, would transpose the frequency up one octave, resulting in 400. -5V will transpose M down 5 octaves, so we have 6.75hz

The difference is substantial, as the frequency of the carrier will never go below 0 (any positive number can be divided by two forever and not reach 0), and the sweep is exponential instead of linear (giving that focus on the lower frequencies that makes the analog so beefy). The frequency sweep is always between 0 and 10v.

In digital, this means we can take the +/- 1 of an oscillator output and convert that to an octave transformation, rather adding to the carrier frequency, giving us: freq = freq*(2**(mod5)) instead of freq = freq+(modmodMult). The result won’t give use predictable sidebands, but it does sound more like analog:

({
	var mod = LFSaw.ar(MouseX.kr(5,700), 0, -1);
	var freq = 410;
	SinOsc.ar(freq*(2**(mod*5)), 0, 0.1)
}.play)

({
	var mod = LFSaw.ar(MouseX.kr(5,700), 0, -1);
	var freq = 410;
	LFTri.ar(freq*(2**(mod*5)), 0, 0.1)
}.play)

If anyone has a better approach, I’m all ears.

Sam

10 Likes

Indeed most analgue fm input are not ‘through zero’ but some are and the analogue fm guru are raving about those. So I find it super interesting that a digital master aims at analogue problems when the analogue master is looking at the digital perfection :slight_smile:

Not better, but to get even nearer, you can kink your sines. Even the best of analogue oscillator has a ‘triangle bias’ in the wave - you can see that on the scope or spectral analysis. So I’d change the SinOsc for something a bit more like a sine plus a (band limited :slight_smile: ) tri. They need to be sync’d so that will be fun… so I’d probably lowpass a tri instead…

by ear and by eye this looks/sound like my analogue ‘sines’
{LFTri.ar(110,mul: 0.25) + SinOsc.ar(110,mul: 0.75)}.plot

5 Likes

Admittedly, I have not yet had any time on an analog machine, but this is how I approach FM since it is, at the end of the day, a product of phase modulation in the underlying C++ implementation unless you’re not using a table reading sine wave.

(
Ndef(\fm, {
	var mod = SinOsc.ar(0, Phasor.ar(0, \cFreq.kr(440, spec: \freq.asSpec) * 2pi * SampleRate.ir.reciprocal, 0, 2pi, 0));
	var car = SinOsc.ar(0, Phasor.ar(0, \mFreq.kr(440, spec: \freq.asSpec) * 2pi * SampleRate.ir.reciprocal * mod * \index.kr(0, spec: \bipolar.asSpec), 0, 2pi, 0));
	Out.ar(0, car!2 * 0.5);
}).gui2;
)

PMOsc does this. I think FM is VCV fundamental is also just phase modulation. But maybe I misunderstand the question

2 Likes

Here is a slightly different approach:

(
{
	var modFreq = MouseX.kr(5, 700);
	var index = 5;
	var fmod = LFSaw.ar(modFreq, 0, -1);
	var fmodExp = 2 ** (fmod * index);
	var fmodExpHPF = (fmodExp - OnePole.ar(fmodExp, exp(-2pi * (modFreq * SampleDur.ir))));
	var carrFreq = 410; 
	var carr = SinOsc.ar(carrFreq + (modFreq * fmodExpHPF));
	carr!2 * 0.1
}.play
)
1 Like