As far as I can see, the spectrum is typical for a random noise sources. The spectral variation you see is equivalent to the temporal variation of noise sources. If you wanted to obtain an averaged (smoother) spectrum, you should use the welch approach, that uses a smaller window and averages the energy properly (you can interpret the welch also as temporal average of the 'power' spectrogram)
Edit:
To demonstrate, that there is nothing wrong with random spectrum, here is a python code with associated results using the raw data (thank you @lframond):
import numpy as np
import matplotlib.pyplot as plt
from scipy import fft
from scipy.io import wavfile
from scipy import signal
#
fname='c:/users/zimme/downloads/SnippetLframond.wav'
#
def butter_bandpass(lowcut, highcut, fs, order=5):
return signal.butter(order, [lowcut, highcut], fs=fs, btype='band')
#
def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
b, a = butter_bandpass(lowcut, highcut, fs, order=order)
y = signal.lfilter(b, a, data)
return y
def mSpectrum(xx,win,nfft,fs):
# estimate power spectral density (PSD) using rfft directly
nwin=len(win)
yy=np.fft.rfft(xx[:nwin]*win,nfft)
pp=np.abs(yy)**2
pp[1:] *=2 # compensate for negative frequencies
ff=np.arange(int(nfft/2)+1)*fs/nfft
sc1= fs * (win*win).sum() # for density
sc2= win.sum()**2 # for spectrum
return ff,pp/sc1,pp/sc2
fs,data= wavfile.read(fname)
audio=data[:,0]
audio=butter_bandpass_filter(audio, 2000, 8000, fs, order=5)
ta=np.arange(len(audio))/fs
# fft spectrum
nfft=2<<(len(audio)-1).bit_length()
win=np.hanning(len(audio))
ff,P_dens,P_spec=mSpectrum(audio,win,nfft,fs)
# welch periodogram
nw=256
fw,Pw=signal.welch(audio,fs=fs,nfft=2*nw,window=np.hanning(nw))
#Plots
fig, ax = plt.subplots(3, 1, num=0, clear=True, figsize=(7,10))
ax[0].plot(ta,audio)
ax[0].set_xlabel('Time [s]')
#
q,f,t,im=ax[1].specgram(audio, Fs=fs)
plt.colorbar(im)
ax[1].set_ylabel('Frequency [kHz]')
ax[1].set_xlabel('Time [s]')
#
#mplot=ax[2].plot
mplot=ax[2].semilogy
mplot(ff/1000,P_dens)
mplot(f/1000,np.abs(q).mean(1))
mplot(fw/1000,Pw,'k--')
ax[2].set_xlim(0,10)
ax[2].set_ylim(1e-5,10000)
ax[2].set_xlabel('Frequency [kHz]')
ax0=ax[0].get_position()
ax1=ax[1].get_position()
ax0.x1=ax1.x1
ax[0].set_position(ax0)
plt.show()
Result:
Comments:
FFT size was constructed to be longer than data length (data were 5136 samples) and fft size was becoming 16384 (at least 2 times data length to sharpen spectral peaks).
Spectrogram was from matplot libray to look similar to OP
for welch periodogram use same fft-length as for fft-spectrum
- spectrum density (here in log scale) is as random as in OP which is simply due to the length of random data used for the fft.
- welch periodogram and averaged spectrogram are 'smooth' and are identical indicating that periodograms are effectively averaged spectrograms.
- due to bandpass filter response there is a small VLF component, where welch periodogram and averaged spectrogram differ