// FM Trumpet
// Realization a Frequency Modulation Trumpet
// Based on Dodge p.129, an adaption of Dexter Morrill's design
(
SynthDef(\fmtrumpet, {arg dur = 1, amp = -3, fundfreq = 440, index = 1, halfsine = 1;
var env, amp1, amp2, formant, dev1, dev2, rand, vibamp, vibosc, porta, vib, ac1env,
ac2env, modenv, car1 ,car2, mod, mod2;
env = EnvGen.kr(Env([0.01, 1, 1, 0.01], [0.01, dur - 0.02, 0.01], \exp), doneAction: 2);
amp1 = amp.dbamp * 0.7; // amp of the first carrier
amp2 = amp1 * 0.2; // amp of the second carrier. It is the second carrier
// that will simulate the formant
// The equation below is a standard way of calculating a value for frequency
// which will be as near to a formant frequency as possible while also
// being a harmonic partial of the fundamental. The important formant
// for the trumpet is approximately 1500 Hz. Therefore we want to have an
// increase in energy near that frequency to simulate the formant in order
// to synthesize a trumpet-like sound. To do that we will use a second carrier
// oscillator with a frequency corresponding to the formant. The trumpet is an
// instrument with harmomic partials so the formant must also be a harmonic.
// For example, suppose the fundamental frequency were 440Hz. It is easy to see
// that 1500Hz is not a harmonic partial of 440Hz (1500 is not an integer
// multiple of 440). If we put 440 into the equation:
// formant = (1500/440).round(1) * 440
// we get a result of 1320 for formant. The "round" function:
// a.round(b)
// returns a value for a rounded to the nearest b. So (1500/440).round(1) would
// return 3 instead of 3.409.
// We do this because we want to multiply the fundamental by an integer to
// get a frequency that will be a harmonic partial.
// We then multiply the fundemental by that integer to get the
// value of that harmonic in Hz as was done above to get 1320Hz. It might
// help you understand this better if you insert your own values for
// fundamentals and formants and do the calculations to see the results!
formant = (1500 / fundfreq).round(1) * fundfreq;
// The variable "dev1" is the deviation, or amplitude of the the modulating
// oscillator. index is an index-like scalar set in the score to make the sound
// more or less bright.
dev1 = (fundfreq * 3) * index;
// The variable dev2 will be used to scale the output of the modulator
// before being used by the second carrier. Because we don't want to create
// as many side bands around the second carrier (the formant), the deviation
// should be smaller.
dev2 = 0.666 * index;
// VIBRATO
// a random component with interpolating LFNoise1
rand = LFNoise1.ar(15, 0.007);
// vibrato component amplitude. This will read from a half sine shape from
// a buffer stored in memory. To make sure it only reads once, its frequency
// will be the reciprocal of the duration.
vibamp = Osc.kr(halfsine, dur.reciprocal, 0, 0.007);
// vibrato component main oscillator
vibosc = SinOsc.ar(5, 0, vibamp);
// pitch slew function
porta = EnvGen.kr(Env([0, 0.03, 0.02], [0.06, dur - 0.06], \lin));
// multiply the three components together with 1 added to
// create a frequency multiplier centered around 1
vib = (1 + rand) * (1 + vibosc) * (1 + porta);
// ENVELOPES
ac1env = EnvGen.kr(Env([0.001, 1, 0.8, 0.001], [0.1, dur - 0.25, 0.15], \exp));
ac2env = EnvGen.kr(Env([0.001, 1, 0.8, 0.001], [0.1, dur - 0.45, 0.3], \exp));
modenv = EnvGen.kr(Env([0.001, 1, 0.8, 0.001], [0.1, dur - 0.11, 0.01], \exp));
// MODULATOR
// The C:M ratio is 1:1, so fm = fc * vibrato
mod = SinOsc.ar(fundfreq * vib, 0, dev1 * modenv);
// CARRIER1
// fc*vib gives the fundamental frequency and then
// we add the modulator signal
car1 = SinOsc.ar((fundfreq * vib) + mod, 0, ac1env * amp1);
// The modulation deviation is scaled back before
// applying it to the formant carrier, giving
// fewer sidebands
mod2 = mod * dev2;
// The C:M ratio for the formant carrier is different
// from the fundamental modulator, but since the
// formant is harmonic to fc, the resulting formant:M
// ratio is also harmonic, and in the reduced ratio
// M will still be 1 -- so the same spectral components
// will be present.
// CARRIER2
car2 = SinOsc.ar((formant * vib) + mod2, 0, amp2 * ac2env);
Out.ar(0, (car1 + car2) * env);
}).load(s);
)
/*
s = Server.internal.boot;
Synth(\fmtrumpet, [\dur, 0.5, \amp, -3, \fundfreq, 440, \index, 1]);
Synth(\fmtrumpet, [\dur, 1, \amp, -3, \fundfreq, 522, \index, 1]);
Synth(\fmtrumpet, [\dur, 1, \amp, -3, \fundfreq, 440, \index, 1.5]);
Synth(\fmtrumpet, [\dur, 2.5, \amp, -3, \fundfreq, 522, \index, 1.5]);
*/