SuperCollider code:
(
s.waitForBoot {
~dirt.free;
~dirt = SuperDirt.new(2, s);
~numberOfOrbits = 12;
//FM
~numberOfOperators = 6;
~fmbuses = ({Bus.audio(s,2)}!(~numberOfOperators-1));
(
SynthDef(\fmsine,{ |out,freq=440,speed=1,fmamp=0,fmin=1,gain=1,pan=0.5,sustain=1,decay=0|
var env, sound, fm, buses;
buses = [out]++~fmbuses;
env = EnvGen.ar(Env.pairs([[0,0],[0.007,1],[0.2,1-decay],[0.95,1-decay],[1,0]], -3),
timeScale:sustain, doneAction:2);
freq = freq*speed.abs;
fm = In.ar(Select.kr(fmin,buses),2)*freq*fmamp;
sound = SinOsc.ar(freq+fm,0,env)*gain;
Out.ar(out,
DirtPan.ar(sound, ~dirt.numChannels, pan, env)
)
}).add;
);
s.sync;
~dirt.start(57120, 0!~numberOfOrbits++[0]++~fmbuses);
};
);
Haskell:
let fmin i amp = (# pI "fmin" i) . (# pF "fmamp" amp)
fmout o = orbit (12+o)
fm output = (# s "fmsine") . (# fmout output)
Tidal example:
d1 $ stack[
fm 0 $ fmin 1 6 $ note "<0 7 12 19>" # octave 3
,fm 1 $ fmin 2 16 $ sometimes (jux (|+ note 12)) $ note "0(7,16)" # shape 0.5 # release 0.2 # legato 4
,fm 1 $ fmin 3 6 $ note "<0 7>*16?" # octave 3 # speed 7 # pan rand # attack 0.15 # gain 1.2
,fm 2 $ note "[12 ~] [<~ 0> 7]"
,fm "[3|2|1]" $ note "7*4?" # octave 7
]
Audio of the example, adding one element of the stack (so, one line of code) at a time:
The idea is having the final 6 orbits reserved for FM Synthesis, let's call them channels for now. Channel 0 is the one you can hear, and the rest of them work as modulators. And you can make all these channels interact with each other.
In the Tidal code you see this:
fm 0
-> outputs to FM channel 0
fm 0 $ fmin 1 6
-> outputs to channel 0, modulating the frequency using channel 1, and the modulation amplitude is 6.
The modulation is stereo; and outputs, inputs and mod amplitudes can be pattern'd. And because every channel is a SuperDirt orbit you can use all the effects like reverb, distortion, etc.