Note
Go to the end to download the full example code.
Rhythm analysis#
This section covers methods for estimating tempo and beat positions from audio signals.
The previous section introduced methods for identifying the positions of note onsets. In this section, we build on onset detection to answer two rhythm-related questions: how fast is the pulse, and where do the beats occur?
import librosa
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import Audio, HTML
# Load an example audio file with a beat
y, sr = librosa.loadx("sweetwaltz", duration=20.0)
HTML(librosa.util.example_info("sweetwaltz", html=True))
Audio(url=librosa.util.example("sweetwaltz", url=True))
The starting point for rhythm analysis is often the onset strength envelope. We often use the envelope rather than detected onset positions to avoid error propagation from incorrect detections.
First, let’s visualize the audio signal, its spectrogram, and the onset strength envelope.
onset_env = librosa.onset.onset_strength(y=y, sr=sr)
times = librosa.times_like(onset_env, sr=sr)
S = librosa.stft(y)
fig, ax = plt.subplots(nrows=3, sharex=True, height_ratios=(1, 1, 2))
ax[0].plot(times, onset_env, label="Onset strength", color="C1")
librosa.display.waveshow(y, sr=sr, ax=ax[1], label="Waveform")
img = librosa.display.specshow(S, sr=sr, vscale="dBFS", x_axis="time", y_axis="log", ax=ax[2])
librosa.display.colorbar_db(img, label="dBFS")
ax[0].legend(loc="upper right")
ax[1].legend(loc="upper right")
ax[0].label_outer()
ax[1].label_outer()

The onset strength envelope displayed above contains many peaks, which correspond to musical onsets. In this example, the peaks are also regularly spaced in time. When we listen to this example this regular spacing is perceived as a pulse or beat. After computing the onset strength, the next step in rhythm analysis is often to estimate the tempo, that is, the time spacing of the pulse.
Estimating the tempo#
A typical method for estimating tempo from the onset strength envelope is to compute the autocorrelation of the envelope, which measures how similar the envelope is to a delayed copy of itself. The animation below illustrates this process, limited to the first 5 seconds of the audio signal.
fig, ax = plt.subplots(nrows=2)
times = librosa.times_like(onset_env, sr=sr)
ax[0].plot(times, onset_env, label="Onset strength", color="C1")
onset_delayed = ax[0].plot(times, onset_env, label="Delayed onset strength", color="C4", linestyle="--")[0]
ax[0].set(xlim=(0, 5), xlabel="Time (s)")
ax[0].legend(loc="upper right")
xcorr = np.correlate(onset_env, onset_env, mode="same")
corrplot = ax[1].plot(times, xcorr, label="Autocorrelation", color="C2")[0]
ax[1].legend(loc="upper right")
ax[1].set(xlim=(0, 5), xlabel="Lag (s)")
def _update(num):
"""Update the plot for each frame."""
# Update the delayed onset strength
# Show the onset strength envelope delayed by `num` frames
onset_delayed.set_xdata(times + num * times[1])
# Show the autocorrelation up to the current amount of lag
corrplot.set_xdata(times[:num])
corrplot.set_ydata(xcorr[:num])
return (onset_delayed, corrplot)
ani = animation.FuncAnimation(fig,
func=_update,
frames=int(np.ceil(5 / times[1]).astype(int)),
interval=50,
blit=True)