Blending pure tones: Additive Synthesis from scratch with Python code
A Violin Sound
ipd.Audio("violin-a440.wav")
With a very simple synthesis, it sounds like this:
violin = addsyn(440, 1, [1, 0.263, 0.14, 0.099, 0.209, 0.02, 0.029, 0.077, 0.017, 0.01])
ipd.Audio(violin, rate=sr)
Try comparing it with the pure sine tone. It sounds so different, isn’t it?
sinewave = addsyn(440, 1, [1])
ipd.Audio(sinewave, rate=sr)
Additive Synthesis with Python
import IPython.display as ipd
import matplotlib.pyplot as plt
import numpy
import math
sr = 22050
def makesine(freq, dur):
t = numpy.linspace(0, dur, math.ceil(sr*dur))
x = numpy.sin(2 * numpy.pi * freq * t)
return x
output = numpy.array(())
y = makesine(261.63, .5) # C for 0.5 seconds
output = numpy.concatenate((output, y))
y = makesine(293.66, .5) # D for 0.5 seconds
output = numpy.concatenate((output, y))
y = makesine(329.63, .5) # E for 0.5 seconds
output = numpy.concatenate((output, y))
ipd.Audio(output, rate=sr)
def addsyn(freq, dur, amplist):
i = 1
t = numpy.linspace(0, dur, math.ceil(sr*dur))
### initialize a new output
out = numpy.zeros(t.size)
for amp in amplist:
### make a sine waveform with this max amplitude
### frequency is the integer multiple
x = numpy.multiply(makesine(freq*i, dur), amp)
### sum it to the output
out = out + x
i+=1
### making sure the maximum amplitude does not exeed 1
if numpy.max(out)>abs(numpy.min(out)):
out = out / numpy.max(out)
else:
out = out / -numpy.min(out)
return out
Basic Waveforms with Additive Synthesis
t = numpy.linspace(0, 1, sr)
sinewave = addsyn(440, 1, [1]) # Only one harmonic (the fundamental)
plt.plot(t, sinewave)
plt.xlim(0, 0.005) # Show only 0.005 seconds to see waveform shape
ipd.Audio(sinewave, rate=sr)
squarewave = addsyn(440, 1, [1, 0, 0.339, 0, 0.204, 0, 0.155, 0, 0.111, 0])
plt.plot(t, squarewave)
plt.xlim(0, 0.005)
ipd.Audio(squarewave, rate=sr)
sawtoothwave = addsyn(440, 1, [1, 0.501, 0.339, 0.24, 0.202, 0.168, 0.155, 0.123, 0.111, 0.102])
plt.plot(t, sawtoothwave)
plt.xlim(0, 0.005)
ipd.Audio(sawtoothwave, rate=sr)
trianglewave = addsyn(440, 1, [1, 0, 0.112, 0, 0.04, 0, 0.022, 0, 0.012, 0])
plt.plot(t, trianglewave)
plt.xlim(0, 0.005)
ipd.Audio(trianglewave, rate=sr)
And here's the violin again!
violin = addsyn(440, 1, [1, 0.263, 0.14, 0.099, 0.209, 0.02, 0.029, 0.077, 0.017, 0.01])
plt.plot(t, violin)
plt.xlim(0, 0.005)
ipd.Audio(violin, rate=sr)
More Sophisticated Ways to Generate a Melody
Going further, here we prepare a function for the pitch-to-frequency calculator:
def p2f(pitch):
freq = 2**((pitch-69)/12) * 440 # See https://en.wikipedia.org/wiki/Pitch_(music)#Labeling_pitches
return freq
And a melody player to accept lists of notes and durations to play, with a pre-defined list of harmonic ratios:
def playmelody(notes, durs, harmonics):
i = 0;
output = numpy.array(())
for i in range(len(notes)):
y = addsyn(p2f(notes[i]), durs[i], harmonics)
output = numpy.concatenate((output, y))
return output
p = [88, 86, 78, 80, 85, 83, 74, 76, 83, 81, 73, 76, 81]
d = numpy.multiply([.5, .5, 1, 1, .5, .5, 1, 1, .5, .5, 1, 1, 4], 0.25)
h = [0.141, 0.200, 0.141, 0.112, 0.079, 0.056, 0.050, 0.035, 0.032, 0.020]
print(d)
x = playmelody(p, d, h)
ipd.Audio(x, rate=sr)