Created
September 28, 2021 05:32
-
-
Save Roninkoi/9fcc44c4264d96ad34e8181d2389e166 to your computer and use it in GitHub Desktop.
Fourier analysis of piano hammer shapes and positions where they hit the string
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Fourier analysis of piano hammer shapes on string. | |
| # Hammers of different shapes hit piano string at different points, | |
| # what is the frequency spectrum? | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| # line + scatter plot of multiple data sets a | |
| def linescatter(a, titles=["", "", ""], labels=[], styles=[], fpath="", cm=plt.cm.rainbow): | |
| f = plt.figure(figsize=(10, 10)) | |
| plt.rcParams.update({'font.size': 22}) | |
| colors = cm(np.linspace(0, 1, len(a))) | |
| plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors) | |
| i = 0 | |
| for ai in a: | |
| alabel = str(i+1) | |
| amarker = "" | |
| alinestyle = "-" | |
| al = 1. | |
| if (len(labels) > 0): | |
| alabel = labels[i] | |
| if (len(styles) > 0): | |
| if (styles[i] == 1): | |
| amarker = "" | |
| alinestyle = "-" | |
| al = 0.5 | |
| if (styles[i] == 2): | |
| amarker = "o" | |
| alinestyle = "" | |
| if (styles[i] == 3): | |
| amarker = "o" | |
| alinestyle = "" | |
| al = 0.5 | |
| if (len(a) > 1): | |
| plt.plot(ai[:, 0], ai[:, 1], label=alabel, marker=amarker, linestyle=alinestyle, alpha=al) | |
| else: | |
| plt.plot(ai[:, 0], ai[:, 1], marker=amarker, linestyle=alinestyle) | |
| i += 1 | |
| plt.title(titles[0]) | |
| plt.xlabel(titles[1]) | |
| plt.ylabel(titles[2]) | |
| plt.grid(True) | |
| plt.legend(fontsize='xx-small') | |
| if (len(fpath) > 0): | |
| f.savefig(fpath, bbox_inches='tight') # save to file | |
| plt.show() | |
| plt.close() | |
| # generate hammer shapes | |
| def hammers(n, l, w, hx): | |
| x = np.linspace(-l/2., l/2., n) | |
| wi = abs(x-hx) <= w | |
| h1 = np.zeros(n) | |
| h2 = np.zeros(n) | |
| h1[wi] = 1. # flat hammer | |
| h2[wi] = np.sqrt(1. - ((x[wi]-hx)/w)**2) # circular hammer | |
| h3 = np.exp(- 4 * np.log(2.) * (x-hx)**2 / w**2) # Gaussian hammer | |
| h1[0] = 0. | |
| h1[-1] = 0. | |
| h2[0] = 0. | |
| h2[-1] = 0. | |
| h3[0] = 0. | |
| h3[-1] = 0. | |
| linescatter([np.array([x, h1]).T, np.array([x, h2]).T, np.array([x, h3]).T], titles=["Hammer shapes", "x", "y"]) | |
| return h1, h2, h3 | |
| # calculate spectrum for hammers | |
| def spectrum(n, l, h): | |
| bw = np.zeros(n) | |
| bn = np.zeros(n) | |
| for ni in range(n): # calculate Fourier coefficients | |
| fi = 0. | |
| for nxi in range(n): | |
| x = (nxi / n - 0.5) * l | |
| fi += h[nxi] * np.cos((ni+1) * np.pi * x / l) * l / n | |
| bw[ni] = ni / n | |
| bn[ni] = 2. / l * fi | |
| return np.array([bw, bn]).T | |
| # calculate spectra for all hammers with hx offset | |
| def spectra(n, l, hx): | |
| h1, h2, h3 = hammers(n, l, w, hx) | |
| f1 = spectrum(n, l, h1) | |
| f2 = spectrum(n, l, h2) | |
| f3 = spectrum(n, l, h3) | |
| return f1, f2, f3 | |
| n = 1000 # number of points | |
| l = 1. # length of string | |
| w = l / 50. # half width of hammer | |
| f1, f2, f3 = spectra(n, l, 0.) # centered | |
| fh1, fh2, fh3 = spectra(n, l, l / 4. - w) # halfway | |
| fe1, fe2, fe3 = spectra(n, l, l / 2. - w) # end | |
| linescatter([np.abs(f1)], titles=["Spectrum", "$\omega$", "$|A|$"]) | |
| linescatter([np.abs(f2)], titles=["Spectrum", "$\omega$", "$|A|$"]) | |
| linescatter([np.abs(f3)], titles=["Spectrum", "$\omega$", "$|A|$"]) | |
| f1[:, 1] = np.abs(f1[:, 1])**2 | |
| fh1[:, 1] = np.abs(fh1[:, 1])**2 | |
| fe1[:, 1] = np.abs(fe1[:, 1])**2 | |
| linescatter([f1[0:200], fh1[0:200], fe1[0:200]], titles=["Spectrum", "$\omega$", "$|A|^2$"], labels=["Centered", "Half", "End"], styles=[1, 1, 1]) | |
| print("h1 |A|^2:", f1[0, 1], fh1[0, 1], fe1[0, 1]) | |
| f2[:, 1] = np.abs(f2[:, 1])**2 | |
| fh2[:, 1] = np.abs(fh2[:, 1])**2 | |
| fe2[:, 1] = np.abs(fe2[:, 1])**2 | |
| linescatter([f2[0:200], fh2[0:200], fe2[0:200]], titles=["Spectrum", "$\omega$", "$|A|^2$"], labels=["Centered", "Half", "End"], styles=[1, 1, 1]) | |
| print("h2 |A|^2:", f2[0, 1], fh2[0, 1], fe2[0, 1]) | |
| f3[:, 1] = np.abs(f3[:, 1])**2 | |
| fh3[:, 1] = np.abs(fh3[:, 1])**2 | |
| fe3[:, 1] = np.abs(fe3[:, 1])**2 | |
| linescatter([f3[0:200], fh3[0:200], fe3[0:200]], titles=["Spectrum", "$\omega$", "$|A|^2$"], labels=["Centered", "Half", "End"], styles=[1, 1, 1]) | |
| print("h3 |A|^2:", f3[0, 1], fh3[0, 1], fe3[0, 1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment