Skip to content

Instantly share code, notes, and snippets.

@ckmahoney
Last active January 6, 2023 17:30
Show Gist options
  • Save ckmahoney/a4ae25293c4f56b1d9d7d8aa5fcec732 to your computer and use it in GitHub Desktop.
Save ckmahoney/a4ae25293c4f56b1d9d7d8aa5fcec732 to your computer and use it in GitHub Desktop.
Generator for a constrained range of FM SynthDef
/*
Generate a list of new FM synths
This is a good preset for target frequencies in 50 - 600 Hz range.
Above that produces aliasing effects on the highs, and below on the lows.
*/
(
// Initialize a shared list to record the new synthesizers generated
~synths = [];
// a gen which can be called globally
~fmSynthGenerator = {|name|
var timbreCar = [LFTri, LFSaw, SinOsc].choose;
var modulationRatio = (1..16).choose / (1..8).choose;
var carrier = [SinOsc, Pulse, LFSaw].choose;
var envGen = {|dur = 1|
var breath = (1..3).choose.half;
var ampMod = 2.pow((3..6).choose);
// create an envelope measured in seconds
var atk = 5 + 30.rand;
var sus = (10..80).choose + (SinOsc.kr(breath).range(0, ampMod)/2);
var rel = 100 - atk - sus + 15.rand;
var a0, a1, a2, a3;
a0 = 20 + 80.rand;
a1 = 85 + 15.rand;
a2 = (a1/2) + (a1/4).rand;
a3 = 0;
EnvGen.ar(Env.new([a0, a1, a2, a3]/100, [atk, sus, rel]*dur/100, \exp));
};
name = name ? "fmSynth_" ++ ~synths.size.asString; // set default value to enclosed
SynthDef(name, {|freq=300, amp=0.1, dur=4, cps=2.1, t=0|
var sig = 0;
// timbreMixMod is the amount of color change over time
var timbreMixMod = timbreCar.ar(cps/128, t.mod(32)/32).range(0, 1);
var ampEnv = envGen.value(dur);
var formants = [];
// use a stack of timbre shapers for thicc results
16.do{|i| formants = formants.add(i+1) };
formants.scramble.keep(4);
formants.do{|hz, i|
var modTimeScale = 2.pow(i);
var gain = (formants.size - i)/(i + 1);
var timbre = timbreCar.ar(hz * cps/modTimeScale).range(0, 2.pow(i+5));
var modIndex = timbreMixMod * timbre;
var mod = modIndex * SinOsc.kr(modulationRatio * freq, mul: gain * formants.size.reciprocal);
sig = sig + carrier.ar(freq + mod, mul: formants.size.reciprocal);
};
sig = sig * ampEnv;
Out.ar(0, Pan2.ar(sig));
DetectSilence.ar(sig, doneAction: 2);
});
};
/* Convenience, make a new one and hear it now */
~synths = ~synths.add(~fmSynthGenerator.value);
~synths.last.add;
Pbindef(\player, \instrument, ~synths.last.name);
)
/* Play a melody */
(
if (~synths.isNil || ~synths.size == 0, { "Make a synth, you silly goose!".throw }, {
// Generate a melody
var scale = [0,2,4,5,7,9,11];
var octave = 5;
var midikey = 3;
var notes = (midikey + (12 * octave)) + scale.scramble;
var durs = notes.collect({[1/2, 1, 2].choose});
// Use a global to track the progression of time.
~t = 0;
~cps = 1 + 2.0.rand;
TempoClock.tempo = ~cps;
Pbindef(\player,
\instrument, ~synths.last.name,
\midinote, Pshuf(notes, inf),
\dur, Pseq(durs, inf),
\t, Pfunc({|event| var dur = event.at(\dur); ~t = ~t + dur; ~t; }),
\cps, ~cps,
).play;
});
)
/* Live update the player */
// Manually set the instrument you want to hear
Pbindef(\player, \instrument, \fmSynth_1);
Pbindef(\player, \instrument, ~synths.last.name);
Pbindef(\player, \instrument, ~synths[4].name);
/* reset the synth bank */
// ~synths = nil;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment