1"""Dominant partials (0.3 to 1.5 s after the onset) of several clips, side by side.
3Usage: python3 partials.py CLIP.wav|CLIP.f32 ...
4"""
5import sys, wave, numpy as np
6from scipy.signal import find_peaks
8FS = 48000
11def load(p):
12 if p.endswith(".f32"):
13 return np.fromfile(p, dtype="<f4").astype(float)
14 w = wave.open(p)
15 return np.frombuffer(w.readframes(w.getnframes()), dtype="<i2").astype(float) / 32768
18def onset(x):
19 e = np.abs(x)
20 return max(0, int(np.argmax(e > 0.3 * e.max())) - int(0.005 * FS))
23def partials(x, a=0.3, b=1.5, fmin=100, fmax=1300, n=12):
24 s = x[int(a * FS):int(b * FS)]
25 S = 20 * np.log10(np.abs(np.fft.rfft(s * np.hanning(len(s)))) + 1e-12)
26 f = np.fft.rfftfreq(len(s), 1 / FS)
27 m = (f > fmin) & (f < fmax)
28 S = S - S[m].max()
29 idx, _ = find_peaks(S, prominence=8, distance=int(3 / (FS / len(s))))
30 idx = [i for i in idx if m[i]]
31 idx = sorted(idx, key=lambda i: -S[i])[:n]
32 return " ".join(f"{f[i]:.0f}({S[i]:.0f})" for i in sorted(idx))
35for p in sys.argv[1:]:
36 x = load(p)
37 x = x[onset(x):]
38 print(f"{p:36s} {partials(x)}")