Skip to content

Tempo

tempo

This module provides functions for analyzing tempo characteristics in musical performances. It includes calculations for tempo slope, tempo drift, and tempo fluctuation.

Author: Huw Cheston (2025)

References
  • Cheston, H., Schlichting, J. L., Cross, I., & Harrison, P. M. C. (2024). Jazz Trio Database: Automated Annotation of Jazz Piano Trio Recordings Processed Using Audio Source Separation. Transactions of the International Society for Music Information Retrieval, 7(1), 144–158. https://doi.org/10.5334/tismir.186

  • Cheston, H., Cross, I., & Harrison, P. (2024). Trade-offs in Coordination Strategies for Duet Jazz Performances Subject to Network Delay and Jitter. Music Perception, 42(1), 48–72. https://doi.org/10.1525/mp.2024.42.1.48


tempo_slope

tempo_slope(beats: Iterable[float]) -> float

Calculates the tempo slope for a sequence of beat timestamps.

The tempo slope represents the overall tempo change per second in a performance. It is determined by the slope of a linear regression of instantaneous tempo against beat onset time. A negative slope indicates deceleration, while a positive slope indicates acceleration. The units are (quarter-note) beats-per-minute-per-second

The equation is:

$\hat{S} = \frac{\sum\limits_{i=1}^N (x_i - \bar{x}) (y_i - \bar{y})}{\sum\limits_{i=1}^N (x_i - \bar{x})^2}$,

where $x_i$ is the time of beat $i$ and $y_i$ is the tempo value in (quarter-note) beats-per-minute.

Parameters:

  • beats (Iterable[float]) –

    An iterable of beat timestamps in seconds, such as quarter-note onsets.

Returns:

  • float

    The computed tempo slope value.

Source code in amads/time/tempo.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def tempo_slope(beats: Iterable[float]) -> float:
    r"""
    Calculates the tempo slope for a sequence of beat timestamps.

    The tempo slope represents the overall tempo change per second in a performance.
    It is determined by the slope of a linear regression of instantaneous tempo against
    beat onset time. A negative slope indicates deceleration, while a positive slope
    indicates acceleration. The units are (quarter-note) beats-per-minute-per-second

    The equation is:

    $\hat{S} = \frac{\sum\limits_{i=1}^N (x_i - \bar{x}) (y_i - \bar{y})}{\sum\limits_{i=1}^N (x_i - \bar{x})^2}$,

    where $x_i$ is the time of beat $i$ and $y_i$ is the tempo value in
    (quarter-note) beats-per-minute.

    Parameters
    ----------
    beats : Iterable[float]
        An iterable of beat timestamps in seconds, such as quarter-note onsets.

    Returns
    -------
    float
        The computed tempo slope value.

    """

    return float(fit_tempo_linear_regression(beats).slope)

tempo_fluctuation

tempo_fluctuation(beats: Iterable[float]) -> float

Calculates the fluctuation around the overall tempo of a sequence of beats.

Tempo fluctuation is measured as the standard deviation of the instantaneous tempo, normalized by the mean tempo. Higher values indicate greater variability in tempo.

The equation is:

$\text{F} = \dfrac{\sqrt{\frac{1}{N-1} \sum\limits_{i=1}^N (y_i - \bar{y})^2}}{\bar{y}}$,

where $y_i$ is the tempo value in (quarter-note) beats-per-minute at beat $i$.

Parameters:

  • beats (Iterable[float]) –

    An iterable of beat timestamps in seconds, such as quarter-note onsets.

Returns:

  • float

    The computed tempo fluctuation value.

Source code in amads/time/tempo.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def tempo_fluctuation(beats: Iterable[float]) -> float:
    r"""
    Calculates the fluctuation around the overall tempo of a sequence of beats.

    Tempo fluctuation is measured as the standard deviation of the instantaneous tempo,
    normalized by the mean tempo. Higher values indicate greater variability in tempo.

    The equation is:

    $\text{F} = \dfrac{\sqrt{\frac{1}{N-1}
     \sum\limits_{i=1}^N (y_i - \bar{y})^2}}{\bar{y}}$,

    where $y_i$ is the tempo value in (quarter-note) beats-per-minute at beat $i$.

    Parameters
    ----------
    beats : Iterable[float]
        An iterable of beat timestamps in seconds, such as quarter-note onsets.

    Returns
    -------
    float
        The computed tempo fluctuation value.

    """

    # Compute instantaneous tempo values: this will also validate the input
    tempos = beats_to_tempo(beats)
    # Compute tempo fluctuation
    # No need for np.nanstd etc. here, we won't have NaN values
    return float(np.std(tempos) / np.mean(tempos))

tempo_mean

tempo_mean(beats: Iterable[float])

Calculates the mean tempo from an iterable of timestamps in quarter-note beats-per-minute.

The mean tempo can be calculated simply as:

$\bar{y} = \dfrac{\sum\limits_{i=1}^N\frac{60}{x_i - x_{i-1}}}{N-1}$

where $x_i$ is the time of beat $i$ (and $i \geq 1$) and $N$ is the number of beats.

Parameters:

  • beats (Iterable[float]) –

    An iterable of beat timestamps in seconds, such as quarter-note onsets.

Returns:

  • float

    The computed mean tempo value.

Source code in amads/time/tempo.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def tempo_mean(beats: Iterable[float]):
    r"""
    Calculates the mean tempo from an iterable of timestamps in quarter-note beats-per-minute.

    The mean tempo can be calculated simply as:

    $\bar{y} = \dfrac{\sum\limits_{i=1}^N\frac{60}{x_i - x_{i-1}}}{N-1}$

    where $x_i$ is the time of beat $i$ (and $i \geq 1$) and $N$ is the number of beats.

    Parameters
    ----------
    beats : Iterable[float]
        An iterable of beat timestamps in seconds, such as quarter-note onsets.

    Returns
    -------
    float
        The computed mean tempo value.

    """

    return np.mean(beats_to_tempo(beats))

beats_to_tempo

beats_to_tempo(beats: ndarray) -> ndarray

Converts beat timestamps to instantaneous tempo measurements.

Source code in amads/time/tempo.py
138
139
140
141
142
143
def beats_to_tempo(beats: np.ndarray) -> np.ndarray:
    """Converts beat timestamps to instantaneous tempo measurements."""

    # Raise error on invalid inputs
    _validate_beats(beats)
    return np.array(60 / np.diff(beats))

fit_tempo_linear_regression

fit_tempo_linear_regression(beats: Iterable[float])

Fits linear regression of BPM measurements vs. onset time.

Source code in amads/time/tempo.py
146
147
148
149
150
151
152
153
154
155
156
157
def fit_tempo_linear_regression(beats: Iterable[float]):
    """Fits linear regression of BPM measurements vs. onset time."""

    from scipy.stats import linregress

    # Target variable: BPM measurements
    # This will also validate the input
    beats_arr = np.array(beats)
    y = beats_to_tempo(beats_arr)
    # Predictor variable: the onset time
    x = beats_arr[1:]
    return linregress(x, y)