How to calculate and plot multiple spectrogram in a for loop with librosa?

Question:

I have a 16-sec audio signal. I want to divide my signal into 16 1-sec signals with a for loop and calculate its spectrogram. Let’s assume the raw signal is like the figure that I have attached and I want to calculate and plot the spectrogram of each segment with a for loop exactly like the picture.
enter image description here

I appreciate your help in advance.

The code that I used to plot the 1-sec signals is:

ncols = 4
nrows = len(segment) // ncols + (len(segment) % ncols > 0) # finding the best number of rows to plot the segments

fig = plt.figure(figsize=(22,19))
plt.subplots_adjust(hspace=0.5,wspace=0.4)
plt.suptitle('Raw segmented signal n n', fontweight = 'bold',size = 20)

for i in range (0,16):
    plt.subplot(nrows, ncols, i+1)
    plt.plot(segment[i])
    plt.title('Segment: ' + str(i+1), fontweight = 'bold',size = 15)
    plt.ylabel('Amplitude (mV)', fontweight = 'bold')
    plt.xlabel('Samples',fontweight = 'bold')
    plt.ylim([-1.5, 1.5])
    plt.xlim(xmin = 0, xmax = len(segment[i]))
    fig.tight_layout()
plt.show()
Asked By: Malahat Mehraban

||

Answers:

Here’s an answer that might help. It uses np.split to divide the 16 second clip into the 1 second clips so there’s a built-in assumption that the clip is 16 seconds long (i.e. it won’t divide a clip of an arbitrary length into 1-second pieces) but since your question is specifically about getting 16 one second clips from a 16 second clip, maybe it is enough:

import numpy as np
import librosa
import matplotlib.pyplot as plt

NCOLS = 4
NROWS = 4

FILENAME = 'sound.wav'
sound, rate = librosa.load(FILENAME, sr=None)

segments = np.split(sound, NCOLS*NROWS)

fig = plt.figure(figsize=(22, 19))
plt.subplots_adjust(hspace=0.5, wspace=0.4)
plt.suptitle('Raw segmented signal n n', fontweight='bold', size=20)

for i in range (0, NROWS * NCOLS):
    plt.subplot(NROWS, NCOLS, i + 1)
    plt.plot(segments[i])
    plt.title('Segment: ' + str(i + 1), fontweight='bold', size=15)
    plt.ylabel('Amplitude (mV)', fontweight='bold')
    plt.xlabel('Samples', fontweight='bold')
    plt.ylim([-1.5, 1.5])
    plt.xlim(xmin=0, xmax=len(segments[i]))
    fig.tight_layout()
plt.show()

So that will plot the waveforms and then for the spectrograms you can run this:

fig = plt.figure(figsize=(22,19))
plt.subplots_adjust(hspace=0.5,wspace=0.4)
plt.suptitle('Spectrograms of segments n n', fontweight='bold', size=20)

for i in range (0, NROWS * NCOLS):
    plt.subplot(NROWS, NCOLS, i + 1)
    plt.specgram(segments[i], Fs=rate)
    plt.title('Segment: ' + str(i + 1), fontweight='bold', size=15)
    fig.tight_layout()
plt.show()

If you did want to split out a clip of arbitrary length into 1-second clips you could replace the np.split line above with:

segments = [sound[i:i+rate] for i in range(0, len(sound), rate)]

This works because rate is equivalent to the number of samples in one second. You’d have to do something more if you were looking for a different clip length. Note: the last segment won’t be equal to a full second if the original clip was not actually divisible by whole seconds. Other note: lots of the rest of this code assumes you end up with 16 segments.

The original question did suggest that the spectrogram be computed with librosa and a follow-on comment clarified that the hope was that the spectrogram be computed solely using librosa instead of with pyplot.specgram. Here is an example for doing this (note: seemed to be noticeably slower than using pyplot.spectrogram on my computer):

def specgram_librosa(segment, rate, ax, win_length=256, hop_length=64):
    D = librosa.stft(segment, hop_length=hop_length, win_length=win_length)
    S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
    img = librosa.display.specshow(S_db,
                                   sr=rate,
                                   x_axis='s',
                                   y_axis='linear',
                                   ax=ax)

    return img


fig, ax = plt.subplots(NROWS, NCOLS, figsize=(22, 19))
ax = ax.flatten()
plt.subplots_adjust(hspace=0.5, wspace=0.4)
plt.suptitle('Spectrograms of segments n n', fontweight='bold', size=20)

for i in range (0, NROWS * NCOLS):
    ax[i].set_title('Segment: ' + str(i + 1), fontweight='bold', size=15)
    img = specgram_librosa(segments[i], rate, ax[i])
    fig.colorbar(img, ax=ax[i], format="%+2.f dB")
    fig.tight_layout()
plt.show()
Answered By: skumhest