This package is a port of the Sound Analysis Toolbox for Matlab (SAT) in Python 3. It provides function to measure features of Zebra Finches songs, and to compare their similarities.
It does not give the exact same results as SAT, but gives qualitatively similar results. The variations in the results are due to the fact I removed some hackish code that was hard to port in Python.
In [1]:
%matplotlib inline
In [2]:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.io import wavfile
from scipy.io import matlab
import birdsonganalysis as bsa
In [3]:
sns.set_palette('muted')
In [4]:
songname = 'simple'
First we load a wavfile, the output is a simple 1D array
In [5]:
sr, sig = wavfile.read('../songs/{}.wav'.format(songname))
Now, we load the mat file with the data computed with SAT for comparison
In [6]:
sat = matlab.loadmat('../birdsonganalysis/tests/{}.mat'.format(songname),
squeeze_me=True, struct_as_record=False)[songname]
We compute the spectral derivatives of the song. It is like a sonogram but we plot the slope of the power and not the power itself. It makes the sound features more visible. It uses multitaper spectral analysis.
In [7]:
spec_der = bsa.spectral_derivs(sig)
In [8]:
contrast = 0.02 # A smaller contrast saturates more but makes visible less saillant changes in the song spectrogram
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax) # Draws the spectral derivatives on the `ax`
plt.show(fig)
Frequency modulation is the absolute slope of the frequency along time. see Frequency Modulation on SAP2011.
The blue curve are the values computed by birdsonganalysis. The green curve are the values computed by SAT. The values are normalized before being drawn.
In [9]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax)
fm = bsa.song_frequency_modulation(sig)
ax = bsa.plot_over_spec(fm, ax, label='bsa')
ax = bsa.plot_over_spec(sat.FM, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(fm), np.max(fm), np.median(fm)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.FM), np.max(sat.FM), np.median(sat.FM)))
In [10]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, 0.01, ax)
amp = bsa.song_amplitude(sig)
amp[amp < 0] = 0
ax = bsa.plot_over_spec(amp, ax, label='bsa')
sat_amp = np.copy(sat.amplitude)
sat_amp[sat_amp < 0] = 0
ax = bsa.plot_over_spec(sat_amp, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(amp), np.max(amp), np.mean(amp)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat_amp), np.max(sat_amp), np.median(sat_amp)))
In [11]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, 0.01, ax)
pitches = bsa.song_pitch(sig, sr, threshold=0.6)
pitches[pitches > 44100] = 0
ax = bsa.plot_over_spec(pitches, ax, label='bsa')
ax = bsa.plot_over_spec(sat.pitch, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(pitches), np.max(pitches), np.median(pitches)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.pitch), np.max(sat.pitch), np.median(sat.pitch)))
In [12]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax)
wiener = bsa.song_wiener_entropy(sig)
ax = bsa.plot_over_spec(wiener, ax, label='bsa')
ax = bsa.plot_over_spec(sat.entropy, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(wiener), np.max(wiener), np.median(wiener)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.entropy),
np.max(sat.entropy), np.median(sat.entropy)))
In [13]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax)
am = bsa.song_amplitude_modulation(sig)
ax = bsa.plot_over_spec(am, ax, label='bsa')
ax = bsa.plot_over_spec(sat.AM, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(am), np.max(am), np.median(am)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.AM), np.max(sat.AM), np.median(sat.AM)))
In [14]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax)
good = bsa.song_goodness(sig)
ax = bsa.plot_over_spec(good, ax, label='bsa')
ax = bsa.plot_over_spec(sat.goodness, ax, label='sat', alpha=0.8, ls='--')
ax.legend(frameon=True)
plt.show(fig)
print('bsa: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(good), np.max(good), np.median(good)))
print('SAT: min: {:.2f}, max: {:.2f}, med: {:.2f}'.format(np.min(sat.goodness), np.max(sat.goodness),
np.median(sat.goodness)))
In [15]:
fig = plt.figure(figsize=(16, 5))
ax = fig.gca()
ax = bsa.spectral_derivs_plot(spec_der, contrast, ax)
amp = bsa.song_amplitude(sig)
amp[amp < np.percentile(amp, 15)] = 0
ax = bsa.plot_over_spec(amp, ax, label='bsa')
In [17]:
In [24]:
sfeat = bsa.all_song_features(sig, 44100, freq_range=256, fft_size=1024, fft_step=40)
In [25]:
mfeat = {'am': sat.AM, 'amplitude': sat.amplitude, 'entropy': sat.entropy, 'fm': sat.FM, 'goodness': sat.goodness, 'pitch': sat.pitch}
In [27]:
for key in sfeat:
plt.figure(figsize=(14, 4))
plt.plot(sfeat[key])
plt.plot(mfeat[key], alpha=0.4)
plt.title(key)
In [ ]: