To get started, boot the server.
s.boot
Frequency modulation is the modulation of the frequency of a signal called the carrier via some other signal called the modulator. Typically, both the modulator and the carrier are sine waves. We'll see shortly that these two simple signals alone can produce complex spectra when frequency modulated. The mathematical realization can be shown below: $$g(t) = \sin(2\pi (f_c + k_f\sin(2\pi f_m t)t)$$
Note that is sometimes called direct FM.
SynthDef(\freqMod, {
arg out = 0, freq_c = 440, freq_m = 1, k_f = 1;
var mod, car;
mod = SinOsc.ar(freq_m, 0, k_f);
car = SinOsc.ar(freq_c + mod, 0, 1);
Out.ar(out, car ! 2);
}).add;
We will see quickly that frequency modulation produces interesting and unintuitive partials. So let us open up a frequency scope to observe what happens.
FreqScope.new;
Our initial settings have the frequency of the carrier at an audible rate of 440Hz. In these initial settings, we will hear this as the dominant frequency. The frequency of the modulator is a low frequency. Low frequency modulators are sometimes called LFOs (i.e., low-frequency oscillators). LFOs tend to have frequency rates below human hearing so less than 20Hz. Think carefully about what this LFO is doing. It's changing the frequency of the carrier by plus/minus 1. So the frequency of the signal we hear fluctuates between 339Hz and 441Hz creating a vibrato effect.
~freqMod = Synth(\freqMod, [\freq_c, 440, \freq_m, 1, \k_f, 1]);
Try increasing the value of k_f
to increase the deviation and widen the vibrato. For now, do not set this above about 10Hz.
~freqMod.set(\k_f, 4)
If we change the frequency of the carrier, the pitch changes.
~freqMod.set(\freq_c, 600);
Slowly bring the frequency of the modulator up to increase the rate of the vibrato. What happens as the vibrato rate increases about 20Hz? How do we perceive the sound?
~freqMod.set(\freq_m, 3); // Slowly bring this up
What happens to the partials if we change k_f
?
~freqMod.set(\k_f, 100);
~freqMod.free;
This example offers a way to explore frequency modulation using a mouse. The y position of the mouse represents the modulation index on a scale of 0 to 10 and the x position represents the frequency of the modulator. Recall that the modulation index is defined as $k_f/f_m$ where $k_f$ is the amplitude of the of the modulating sine wave. See what kind of sounds you can produce! Can you create any harmonic sounds?
SynthDef(\freqModMouse, {
arg out = 0, freq_c = 500;
var car, mod, modIndex, freq_m;
freq_m = MouseX.kr(1, 10000, 1).poll;
modIndex = MouseY.kr(0, 10, 0);
mod = modIndex * freq_m * SinOsc.ar(freq_m, 0, 1);
car = SinOsc.ar(freq_c + mod, 0, 1);
Out.ar(out, car ! 2);
}).add;
~freqModMouse = Synth(\freqModMouse);
~freqModMouse.free;
Frequency modulation with sine waves for the carrier and modulator are harmonic when the ratio between the two is an integer. In sound synthesis, the carrier is typically the fundamental. We can define the harmonicity ratio then as $f_m/f_c$.
SynthDef(\freqModHarm, {
arg out = 0, freq_c = 400, modIndex = 1, harmRatio = 1;
var car, mod, freq_m, k_f;
freq_m = harmRatio * freq_c;
k_f = freq_m * modIndex; // modIndex is simply k_f/freq_m so use modIndex to get k_f
mod = SinOsc.ar(freq_m, 0, k_f);
car = SinOsc.ar(freq_c + mod, 0, 1);
Out.ar(out, car ! 2);
}).add;
~freqModHarm = Synth(\freqModHarm);
~freqModHarm.set(\modIndex, 2);
~freqModHarm.set(\harmRatio, 3);
~freqModHarm.set(\freq_c, 200);
~freqModHarm.free;
As a reminder:
Let's add an amplitude envelope to shape our sound.
SynthDef(\fmEnv, {
arg out = 0, freq_c = 400, modIndex = 1, harmRatio = 1, atk = 0.05,
rel = 1.5, amp = 0.6;
var car, mod, freq_m, k_f, env;
freq_m = harmRatio * freq_c;
k_f = modIndex * freq_m;
mod = SinOsc.ar(freq_m, 0, k_f);
car = SinOsc.ar(freq_c + mod, 0, 1);
env = Env.perc(atk, rel, amp);
env = EnvGen.kr(env, doneAction: 2);
Out.ar(out, car * env ! 2);
}).add;
Synth(\fmEnv, [
\modIndex, 2,
\harmRatio, 2.35,
\freq_c, 120,
\atk, 0.1,
\rel, 3,
\amp, 0.6
]);
Synth(\fmEnv, [
\modIndex, 2,
\harmRatio, 3,
\freq_c, 1200,
\atk, 0.01,
\rel, 1,
\amp, 0.6
]);
Here we want to shape the strength of the partials over time. All real sounds have evolving spectra that adds interest and naturalness. To create more interesting FM, we want to shape the modulation index overtime since the modulation index controls the strength of the partials. modIndex
below will be the minimum strength and modIndex * indexFactor
will be the maximum strength. Increasing indexFactor will increase strength of partials at peak. Here we will create an envelope to change the modulation index over time.
SynthDef(\fmShimmer, {
arg out = 0, freq_c = 400, modIndex = 1, harmRatio = 1,
atk = 0.05, rel = 1.5, amp = 0.6, indexFactor = 2;
var car, mod, freq_m, env, indexEnv, k_f;
// Control amplitude of sidebands by ramping them up and then down
// based on modIndex and indexFactor
indexEnv = EnvGen.kr(Env.new(
[
modIndex, // Minimum
modIndex * indexFactor, // Maximum
modIndex // Minimum
],
[atk, rel] // Times to reach maximum and then minimum
));
// Compute the frequency modulation
freq_m = harmRatio * freq_c;
k_f = indexEnv * freq_m;
mod = SinOsc.ar(freq_m, 0, k_f);
car = SinOsc.ar(freq_c + mod, 0, 1);
// Envelope for the amplitude of the signal
env = Env.perc(atk, rel, amp);
env = EnvGen.kr(env, doneAction: 2);
Out.ar(out, car * env ! 2);
}).add;
~trumpet = Synth(\fmShimmer, [
\modIndex, 1,
\harmRatio, 1,
\freq_c, 600,
\indexFactor, 2,
\atk, 0.04,
\rel, 2,
\amp, 0.6
]);
~longTriangle = Synth(\fmShimmer, [
\modIndex, 1,
\harmRatio, 2,
\freq_c, 400,
\indexFactor, 0.01,
\atk, 2,
\rel, 6,
\amp, 0.6
]);
~acidBass = Synth(\fmShimmer, [
\modIndex, 2.3,
\harmRatio, 1,
\freq_c, 40,
\indexFactor, 6,
\atk, 1,
\rel, 4,
\amp, 0.6
]);
Phase Modulation is closely related to FM. The formula for PM is the following:
$$A_c\sin(2\pi f_c t + k_p\sin(2\pi f_m t))$$Note that this is sometimes called indirect FM or Chowning-style FM.
SynthDef(\phaseMod, {
arg out = 0, freq_c = 440, freq_m = 1, k_p = 1;
var car, mod;
mod = SinOsc.ar(freq_m, 0, k_p).mod(2pi);
car = SinOsc.ar(freq_c, mod);
Out.ar(out, car ! 2);
}).add;
FreqScope.new
~phaseMod = Synth(\phaseMod);
~phaseMod.set(\freq_c, 200);
~phaseMod.set(\freq_m, 100);
~phaseMod.set(\k_p, 10);
~phaseMod.free;
The End