import numpy as np
import matplotlib.pyplot as plt
import IPython.display as ipd
As we saw, string support a base frequency $f_0$ followed by its harmonics or overtones, which are integer multiples of the base frequency. When a string is plucked, many of these are activated at once. They all decay over time due to friction, but the higher frequency ones decay more quickly. Below, let's create some arrays that we will element-wise multiply by frequencies to decay them exponentially
sr = 44100
f0 = 440
t = np.arange(sr*2)/sr
decay = np.exp(-2*t)
plt.plot(t, decay)
plt.plot(t, decay**2)
plt.plot(t, decay**3)
plt.legend(["Decay $f_0$", "Decay 1st harmonic", "Decay 2nd harmonic"])
Finally, let's add in the base frequency and the first harmonic, each decaying successively more rapidly, to get a physically inspired plucked string synthesis
y = np.zeros_like(t)
y += np.cos(2*np.pi*f0*t)*decay # Fundamental frequency
y += np.cos(2*np.pi*2*f0*t)*(decay**2) # First harmonic (overtone)
y += np.cos(2*np.pi*3*f0*t)*(decay**3) # Second harmonic
y += np.cos(2*np.pi*4*f0*t)*(decay**4) # Third harmonic
y += np.cos(2*np.pi*5*f0*t)*(decay**5) # Fourth harmonic
y += np.cos(2*np.pi*6*f0*t)*(decay**6) # Fifth harmonic
y += np.cos(2*np.pi*7*f0*t)*(decay**7) # Sixth harmonic
ipd.Audio(y, rate=sr)
The basic for loop in python is quite simple, as shown by an example below that generates 20 random numbers in an array and loops through them.
np.random.seed(0)
x = np.random.randn(20)
for i in range(5, len(x), 3):
print(i, x[i])