pythonfor-looplibrosaspectrogram

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


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()

Solution

  • 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()