Some basic fft handling is included in the Python "numpy" module.
Basic use can be as barebones as the script below (copy it into a ".py" file and make sure you have a full python3 environment with "numpy" and "matplotlib" modules).
It brings up this display:
Screenshot from 2023-12-13 09-30-03.png
The icons are for
- zooming back out to the original display
- going forward and back in a "stack" of different zooms you've tried
- recentering the image (to get different parts of a zoom level in the screen)
- zooming in
- messing with appearance settings
- messing with axis settings
- saving the current image to a graphics file
To the right of the icons are the x,y coordinates of the cursor location on the chart.
Unfortunately, my screenshooter cannot capture the cursor.
The upper plot here is a synthesized wave form.
Below that is an FFT of the waveform.
Here's a zoom of the first peak in the 2nd graph above, with the cursor over the peak spot:
Screenshot from 2023-12-13 09-31-52.png
I found it easier to get Audacity to generate the FFT analysis (the code below is really too simple/underparameterized for musical analysis) then use python for something like this:
Screenshot from 2023-12-13 09-51-01.png
This has different aspects of playing one note in different valve combinations. Since volume makes a huge difference in spectrum, I include that in the legend on the chart. I also quantify how accurately I was able to maintain the desired pitch.
Being able to zoom from this too-too-busy overview into very specific parts of the spectrum, AND have the chart rescale itself is why I used Python on it.
One of the flaws in my character is that as soon as I get something from, say Audacity, I think,"Well, if only it ALSO showed me..." and I'm off down the rabit hole.
So I'm not exactly recommending this to you, but if you DO decide to chase down the rabit hole yourself, a thread on what you are after and how you achieve it could be entertaining.
Here's the script for the first examples:
#!/usr/bin/python3
# fft1.py
# play with python fft analysis
# dba 2021/04/02
import numpy as np
import matplotlib.pyplot as plt
# Construct time signal
Fs = 5000 # sample freq
tstep = 1 / Fs # sample time interval
#f0 = 100 # signal freq
f0 = 62 # signal freq
cycles = 10 # number of times to repeat base pattern
N = cycles * int(Fs/ f0) # number of samples
fstep = Fs / N # Frequency steps
h1 = .9
h2 = .02
h3 = .3
h4 = .01
h5 = .5
t = np.linspace(0, (N-1)*tstep, N) # array of time values
f = np.linspace(0, (N-1)*fstep, N) # array of freq values
base_angle = 2 * np.pi * f0 * t
#y = 1 * np.sin(2 * np.pi * f0 * t) # array of y values for wave
y = (h1 * np.sin( 1 * base_angle)
+ h2 * np.sin( 2 * base_angle)
+ h3 * np.sin( 3 * base_angle)
+ h4 * np.sin( 4 * base_angle)
+ h5 * np.sin( 5 * base_angle)
)
# setup fft
X = np.fft.fft(y)
X_mag = np.abs(X) / N
# corrections to ONLY results within Nyquist limit
f_plot = f[0:int(N/2+1)]
X_mag_plot = 2 * X_mag[0:int(N/2+1)] # adjust AC buckets for plus minus
X_mag_plot[0] = X_mag_plot[0] / 2 # adjust 0 Hz (DC) bucket back down
# get rms for waveform and fourier series
rms_wave = np.sqrt(np.mean(np.square(y)))
print("rms_wave=", rms_wave)
rms_fft = np.sqrt(np.mean(np.square(X_mag_plot)))
print("rms_fft=", rms_wave)
# plot it out
fig, [ax1, ax2] = plt.subplots(nrows=2, ncols=1)
ax1.plot(t, y, '.-')
ax2.plot(f_plot, X_mag_plot, '.-',label="Original")
ax1.set_xlabel("time (s)")
ax2.set_xlabel("freq (Hz)")
ax1.grid()
ax2.grid()
ax1.set_xlim(0, t[-1])
tick_step = int(1000*1/f0)/1000
tick_end = int(1000*cycles/f0)/1000
print("step=",tick_step," end=",tick_end)
ax1.set_xticks(np.arange(0,tick_end,tick_step))
#ax1.set_xticks(np.arange(0,0.16,0.016))
#ax2.set_xlim(0, f_plot[-1])
ax2.set_xlim(0, f0 * 21)
ax2.set_xticks(np.arange(0, f0 * 21, f0))
ax2.set_yscale('log')
plt.tight_layout()
plt.show()