Electric Piano
Frequency modulation synthesis is the technique responsible for the classic electric piano tones of the DX7. The basic idea is to use one oscillator to very rapidly modulate the frequency of another oscillator. Kind of like vibrato, but very, very fast. When the frequency of the "vibrato" is in the audible range, additional harmonics are produced, depending on the frequency ratio between the sounding oscillator (carrier) and the modulator.
We are going to do a variant of FM called phase modulation, because that allows for a clean, precise waveforms unlike naive FM where numeric error accumulates. To be extra certain of phase coherence, let's drive all the operators from a single phasor and multiplex it.
Smooth Operator
We are going to start with an operator, which is a combination of an oscillator and an envelope. We are going to do the dirtiest sinusoid approximation possible, because that will give us a slightly more interesting FM sound. You can just pretend you never saw this function, but it essentially involves two parabolas, wire cutters and duct tape.
Import Gen
Shape(w) {
x = w * #2 - #1
y = Fraction(x) * #2 - #1
Math:Copy-Sign(#1 - y * y x)
}
snd = Shape( Gen:Phasor(330) ) * 0.2
The complete FM operator consists of an oscillator that runs at an integer multiple of a fundamental frequency, and an envelope to generate time-varying timbres.
Import Envelope
FM-Osc(phase modulator multiplier) {
Shape(Fraction((phase + modulator) * multiplier))
}
; cook a gate signal
g = 0.5 + 0.5 * Gen:Pulse(0.5 0.4)
; master phasor
p = Gen:Phasor(220)
; carrier envelope
env = Envelope:ADSR(g 0.02 0.8 0.5 0.5)
; multiplier
m = Floor(1 + Gen:Phasor(0.1) * 5)
; carrier oscillator
snd = FM-Osc(p #0 m) * env * 0.2
With just a single operator, there's no FM. Let's plug another operator with different parameters into the modulator input, and we hear the easily recognizable tone. The modulator multiplier will be different for each note.
mod-env = Envelope:ADSR(g 0.01 0.5 0.1 0.3)
mod = FM-Osc(p #0 m) * mod-env * 0.1
snd = FM-Osc(p mod 1) * env * 0.2
Electric Piano Tones
The famous EP sounds consist of a sustained body timbre and a percussive bell timbre. We can achieve something like that by using a sharp envelope and a high multiple of the fundamental frequency for the bell, and a second, slower envelope for the body timbre.
Let's mix two modulators to achieve bell and body timbre.
EP(g freq) {
p = Gen:Phasor(freq)
bell-env = Envelope:ADSR(g 0.0001 0.08 0.01 0.03)
bell = FM-Osc(p #0 18) * bell-env * 0.01
body-env = Envelope:ADSR(g 0.001 10 0 0.1)
body = FM-Osc(p #0 1) * body-env * 0.003
ep-env = Envelope:ADSR(g 0.002 0.2 0.5 0.5)
FM-Osc(p body + bell 1) * ep-env * ep-env
}
snd = EP(g 330)
Time for some jazzy chords! No electric piano would be complete without a stereo effect, and here we accomplish that by duplicating each voice with slight frequency variations on the left and right channel. We just need to feed the piano tone a vectorized frequency, and type propagation is going to handle the rest!
Import Vector
; midi note to frequency
m->f = m => 440 * Math:Pow(2 (m - 69) / 12)
; chord sequence
chords = [(47 52 57 61 68 71)
(49 53 59 62 67 71)
(42 49 56 57 64 71)
(41 51 57 62 67 72)
(0 0 0 0 0 0)]
; sequencer
time = Gen:Phasor(0.1) * 4.5
chord = Select-Wrap(chords time)
gate = (Fraction(time) < 0.6) & 1
Stereo-EP(g freq) {
; stereo effect by detuning via vectored frequency
EP(g Vector:Cons(freq - 0.5 freq + 0.5))
}
; six-voice piano sound
snd = Average(
Algorithm:Map(
nn => Stereo-EP(gate m->f(nn))
chord))
I would really like to hook it up to a MIDI input and a voice allocator, but this post is long enough as it is. Next time!