Caution
You're reading an old version of this documentation. If you want up-to-date information, please have a look at 0.10.2.
Source code for librosa.feature.rhythm
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Rhythmic feature extraction"""
import numpy as np
from .. import util
from ..core.audio import autocorrelate
from ..core.spectrum import stft
from ..util.exceptions import ParameterError
from ..filters import get_window
__all__ = ["tempogram", "fourier_tempogram"]
# -- Rhythmic features -- #
[docs]def tempogram(
y=None,
sr=22050,
onset_envelope=None,
hop_length=512,
win_length=384,
center=True,
window="hann",
norm=np.inf,
):
"""Compute the tempogram: local autocorrelation of the onset strength envelope. [#]_
.. [#] Grosche, Peter, Meinard Müller, and Frank Kurth.
"Cyclic tempogram - A mid-level tempo representation for music signals."
ICASSP, 2010.
Parameters
----------
y : np.ndarray [shape=(n,)] or None
Audio time series.
sr : number > 0 [scalar]
sampling rate of ``y``
onset_envelope : np.ndarray [shape=(n,) or (m, n)] or None
Optional pre-computed onset strength envelope as provided by
`librosa.onset.onset_strength`.
If multi-dimensional, tempograms are computed independently for each
band (first dimension).
hop_length : int > 0
number of audio samples between successive onset measurements
win_length : int > 0
length of the onset autocorrelation window (in frames/onset measurements)
The default settings (384) corresponds to ``384 * hop_length / sr ~= 8.9s``.
center : bool
If `True`, onset autocorrelation windows are centered.
If `False`, windows are left-aligned.
window : string, function, number, tuple, or np.ndarray [shape=(win_length,)]
A window specification as in `stft`.
norm : {np.inf, -np.inf, 0, float > 0, None}
Normalization mode. Set to `None` to disable normalization.
Returns
-------
tempogram : np.ndarray [shape=(win_length, n) or (m, win_length, n)]
Localized autocorrelation of the onset strength envelope.
If given multi-band input (``onset_envelope.shape==(m,n)``) then
``tempogram[i]`` is the tempogram of ``onset_envelope[i]``.
Raises
------
ParameterError
if neither ``y`` nor ``onset_envelope`` are provided
if ``win_length < 1``
See Also
--------
fourier_tempogram
librosa.onset.onset_strength
librosa.util.normalize
librosa.stft
Examples
--------
>>> # Compute local onset autocorrelation
>>> y, sr = librosa.load(librosa.ex('nutcracker'), duration=30)
>>> hop_length = 512
>>> oenv = librosa.onset.onset_strength(y=y, sr=sr, hop_length=hop_length)
>>> tempogram = librosa.feature.tempogram(onset_envelope=oenv, sr=sr,
... hop_length=hop_length)
>>> # Compute global onset autocorrelation
>>> ac_global = librosa.autocorrelate(oenv, max_size=tempogram.shape[0])
>>> ac_global = librosa.util.normalize(ac_global)
>>> # Estimate the global tempo for display purposes
>>> tempo = librosa.beat.tempo(onset_envelope=oenv, sr=sr,
... hop_length=hop_length)[0]
>>> import matplotlib.pyplot as plt
>>> fig, ax = plt.subplots(nrows=4, figsize=(10, 10))
>>> times = librosa.times_like(oenv, sr=sr, hop_length=hop_length)
>>> ax[0].plot(times, oenv, label='Onset strength')
>>> ax[0].label_outer()
>>> ax[0].legend(frameon=True)
>>> librosa.display.specshow(tempogram, sr=sr, hop_length=hop_length,
>>> x_axis='time', y_axis='tempo', cmap='magma',
... ax=ax[1])
>>> ax[1].axhline(tempo, color='w', linestyle='--', alpha=1,
... label='Estimated tempo={:g}'.format(tempo))
>>> ax[1].legend(loc='upper right')
>>> ax[1].set(title='Tempogram')
>>> x = np.linspace(0, tempogram.shape[0] * float(hop_length) / sr,
... num=tempogram.shape[0])
>>> ax[2].plot(x, np.mean(tempogram, axis=1), label='Mean local autocorrelation')
>>> ax[2].plot(x, ac_global, '--', alpha=0.75, label='Global autocorrelation')
>>> ax[2].set(xlabel='Lag (seconds)')
>>> ax[2].legend(frameon=True)
>>> freqs = librosa.tempo_frequencies(tempogram.shape[0], hop_length=hop_length, sr=sr)
>>> ax[3].semilogx(freqs[1:], np.mean(tempogram[1:], axis=1),
... label='Mean local autocorrelation', basex=2)
>>> ax[3].semilogx(freqs[1:], ac_global[1:], '--', alpha=0.75,
... label='Global autocorrelation', basex=2)
>>> ax[3].axvline(tempo, color='black', linestyle='--', alpha=.8,
... label='Estimated tempo={:g}'.format(tempo))
>>> ax[3].legend(frameon=True)
>>> ax[3].set(xlabel='BPM')
>>> ax[3].grid(True)
"""
from ..onset import onset_strength
if win_length < 1:
raise ParameterError("win_length must be a positive integer")
ac_window = get_window(window, win_length, fftbins=True)
if onset_envelope is None:
if y is None:
raise ParameterError("Either y or onset_envelope must be provided")
onset_envelope = onset_strength(y=y, sr=sr, hop_length=hop_length)
else:
# Force row-contiguity to avoid framing errors below
onset_envelope = np.ascontiguousarray(onset_envelope)
if onset_envelope.ndim > 1:
# If we have multi-band input, iterate over rows
return np.asarray(
[
tempogram(
onset_envelope=oe_subband,
hop_length=hop_length,
win_length=win_length,
center=center,
window=window,
norm=norm,
)
for oe_subband in onset_envelope
]
)
# Center the autocorrelation windows
n = len(onset_envelope)
if center:
onset_envelope = np.pad(
onset_envelope, int(win_length // 2), mode="linear_ramp", end_values=[0, 0]
)
# Carve onset envelope into frames
odf_frame = util.frame(onset_envelope, frame_length=win_length, hop_length=1)
# Truncate to the length of the original signal
if center:
odf_frame = odf_frame[:, :n]
# Window, autocorrelate, and normalize
return util.normalize(
autocorrelate(odf_frame * ac_window[:, np.newaxis], axis=0), norm=norm, axis=0
)
[docs]def fourier_tempogram(
y=None,
sr=22050,
onset_envelope=None,
hop_length=512,
win_length=384,
center=True,
window="hann",
):
"""Compute the Fourier tempogram: the short-time Fourier transform of the
onset strength envelope. [#]_
.. [#] Grosche, Peter, Meinard Müller, and Frank Kurth.
"Cyclic tempogram - A mid-level tempo representation for music signals."
ICASSP, 2010.
Parameters
----------
y : np.ndarray [shape=(n,)] or None
Audio time series.
sr : number > 0 [scalar]
sampling rate of ``y``
onset_envelope : np.ndarray [shape=(n,)] or None
Optional pre-computed onset strength envelope as provided by
``librosa.onset.onset_strength``.
hop_length : int > 0
number of audio samples between successive onset measurements
win_length : int > 0
length of the onset window (in frames/onset measurements)
The default settings (384) corresponds to ``384 * hop_length / sr ~= 8.9s``.
center : bool
If `True`, onset windows are centered.
If `False`, windows are left-aligned.
window : string, function, number, tuple, or np.ndarray [shape=(win_length,)]
A window specification as in `stft`.
Returns
-------
tempogram : np.ndarray [shape=(win_length // 2 + 1, n)]
Complex short-time Fourier transform of the onset envelope.
Raises
------
ParameterError
if neither ``y`` nor ``onset_envelope`` are provided
if ``win_length < 1``
See Also
--------
tempogram
librosa.onset.onset_strength
librosa.util.normalize
librosa.stft
Examples
--------
>>> # Compute local onset autocorrelation
>>> y, sr = librosa.load(librosa.ex('nutcracker'))
>>> hop_length = 512
>>> oenv = librosa.onset.onset_strength(y=y, sr=sr, hop_length=hop_length)
>>> tempogram = librosa.feature.fourier_tempogram(onset_envelope=oenv, sr=sr,
... hop_length=hop_length)
>>> # Compute the auto-correlation tempogram, unnormalized to make comparison easier
>>> ac_tempogram = librosa.feature.tempogram(onset_envelope=oenv, sr=sr,
... hop_length=hop_length, norm=None)
>>> import matplotlib.pyplot as plt
>>> fig, ax = plt.subplots(nrows=3, sharex=True)
>>> ax[0].plot(librosa.times_like(oenv), oenv, label='Onset strength')
>>> ax[0].legend(frameon=True)
>>> ax[0].label_outer()
>>> librosa.display.specshow(np.abs(tempogram), sr=sr, hop_length=hop_length,
>>> x_axis='time', y_axis='fourier_tempo', cmap='magma',
... ax=ax[1])
>>> ax[1].set(title='Fourier tempogram')
>>> ax[1].label_outer()
>>> librosa.display.specshow(ac_tempogram, sr=sr, hop_length=hop_length,
>>> x_axis='time', y_axis='tempo', cmap='magma',
... ax=ax[2])
>>> ax[2].set(title='Autocorrelation tempogram')
"""
from ..onset import onset_strength
if win_length < 1:
raise ParameterError("win_length must be a positive integer")
if onset_envelope is None:
if y is None:
raise ParameterError("Either y or onset_envelope must be provided")
onset_envelope = onset_strength(y=y, sr=sr, hop_length=hop_length)
else:
# Force row-contiguity to avoid framing errors below
onset_envelope = np.ascontiguousarray(onset_envelope)
# Generate the short-time Fourier transform
return stft(
onset_envelope, n_fft=win_length, hop_length=1, center=center, window=window
)