Skip to content

Rhythm

rhythm

Basic properties of rhythms, which is to say 1D representations of musical events, without measures, beats etc., and certainly no scores.

Broadly, this is for stand-alone functions clearly intended for short, simple representations of rhythmic cycles, and is not suitable for calling on scores, for instance.

This also includes some measures of rhythmic complexity (which is clearly not the same as syncopation, but related, and often studied together.

Author: Mark Gotham

has_oddity_property

has_oddity_property(vector: Union[list[int], tuple[int, ...]]) -> bool

Given a rhythm cycle (i.e., with the expectation of repetition) as a vector, check if it has Arom's "rhythmic-oddity" property: no two onsets partition the cycle into two equal parts. This is slightly confusing to get the right way around: the function returns True (i.e., yes, has the property) in the absence of this equal division.

Parameters:

  • vector (Union[list[int], tuple[int, ...]]) –

    A vector for either the event positions in the cycle time span, or the beat pattern (sic, either).

Returns:

  • bool

    True if the rhythm has the rhythmic-oddity property, False otherwise.

Examples:

>>> son = (1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0)
>>> has_oddity_property(son)
True
>>> bembé = (1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1)
>>> has_oddity_property(bembé)
False

And here's a simple rhythm that does have the equal division:

>>> has_oddity_property((1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1))
False

Note that there does not need to be any further similarity between the two halves:

>>> has_oddity_property((1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1))
False
Source code in amads/time/rhythm.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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def has_oddity_property(vector: Union[list[int], tuple[int, ...]]) -> bool:
    """
    Given a rhythm cycle (i.e., with the expectation of repetition) as a
    vector, check if it has Arom's "rhythmic-oddity" property:
    no two onsets partition the cycle into two equal parts.
    This is slightly confusing to get the right way around:
    the function returns `True` (i.e., yes, has the property)
    in the _absence_ of this equal division.

    Parameters
    ----------
    vector
        A vector for either the event positions in the cycle time span, or
        the beat pattern (sic, either).

    Returns
    -------
    bool
        True if the rhythm has the rhythmic-oddity property, False otherwise.

    Examples
    --------

    >>> son = (1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0)
    >>> has_oddity_property(son)
    True

    >>> bembé = (1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1)
    >>> has_oddity_property(bembé)
    False

    And here's a simple rhythm that does have the equal division:
    >>> has_oddity_property((1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1))
    False

    Note that there does not need to be any further similarity between the two halves:
    >>> has_oddity_property((1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1))
    False

    """
    if not isinstance(vector, (list, tuple)):
        raise TypeError("The `vector` must be a list or tuple.")

    vector_length = len(vector)
    if vector_length % 2 != 0:
        return True  # By definition
    half_length = vector_length // 2

    indices = indicator_to_indices(vector)
    indices_set = set(indices)
    for i in indices:
        if (i + half_length) % vector_length in indices_set:
            return False

    return True

keith_via_toussaint

keith_via_toussaint(vector)

Although Keith's measure is described in terms of beats, it is inflexible to metric structure and fully defined by the onset pattern.

Parameters:

  • vector

    An indicator vector whose length must be a power of 2.

Raises:

  • ValueError

    If the vector length is not a power of 2.

Examples:

>>> son = [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0]
>>> keith_via_toussaint(son)
2
Source code in amads/time/rhythm.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def keith_via_toussaint(vector):
    """
    Although Keith's measure is described in terms of beats,
    it is inflexible to metric structure and fully defined by
    the onset pattern.

    Parameters
    ----------
    vector
        An indicator vector whose length must be a power of 2.

    Raises
    ------
    ValueError
        If the vector length is not a power of 2.

    Examples
    --------
    >>> son = [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0]
    >>> keith_via_toussaint(son)
    2

    """
    if not _is_power_of_2(len(vector)):
        raise ValueError(
            f"Vector length (currently {len(vector)}) must be a power of 2."
        )

    indices = indicator_to_indices(vector)  # Keith/Toussaint's `S`
    deltas = indicator_to_interval(
        vector, wrap=True
    )  # Keith/Toussaint also `delta`
    powers_of_2 = [
        _prev_power_of_2(x) for x in deltas
    ]  # Keith/Toussaint's big D
    count = 0
    for i in range(len(indices)):
        this_case = indices[i] / powers_of_2[i]
        if int(this_case) != this_case:
            count += 1
    return count

has_deep_property

has_deep_property(vector: Union[list[int], tuple[int, ...]]) -> bool

So-called "Deep" rhythms have distinct numbers of each interval class among all (not-necessarily adjacent) intervals. See indicator_to_interval with the arguments wrap=True, adjacent_not_all=False.

Examples:

>>> shiko = (1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0)
>>> indicator_to_interval(shiko, wrap=True, adjacent_not_all=False)
(0, 2, 0, 3, 0, 4, 0, 1)

Note the distinct numbers in the above.

>>> has_deep_property(shiko)
True

The son clave is not deep, since multiple interval classes share the same count:

>>> son = (1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0)
>>> has_deep_property(son)
False
Source code in amads/time/rhythm.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def has_deep_property(vector: Union[list[int], tuple[int, ...]]) -> bool:
    """
    So-called "Deep" rhythms have distinct numbers of each interval class
    among all (not-necessarily adjacent) intervals.
    See `indicator_to_interval` with the arguments `wrap=True`,
    `adjacent_not_all=False`.

    Examples
    --------

    >>> shiko = (1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0)
    >>> indicator_to_interval(shiko, wrap=True, adjacent_not_all=False)
    (0, 2, 0, 3, 0, 4, 0, 1)

    Note the distinct numbers in the above.

    >>> has_deep_property(shiko)
    True

    The son clave is not deep, since multiple interval classes share the same count:

    >>> son = (1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0)
    >>> has_deep_property(son)
    False

    """
    intervals = indicator_to_interval(vector, wrap=True, adjacent_not_all=False)
    non_zero_uses = [x for x in intervals if x != 0]
    return len(non_zero_uses) == len(set(non_zero_uses))

off_beatness

off_beatness(vector: Union[list[int], tuple[int, ...]]) -> int

The "off-beatness" measure records the number of events in a rhythmic cycle at positions which cannot fall on a regular beat division of the cycle. For a more formal definition, see totatives.

This function expects an indicator vector (values of 0 or 1). Behaviour with non-indicator (weighted) vectors is undefined.

Examples:

Gomez et al. explore 10 "canonical" 12-unit rhythms of which they find the Bembé notable for being "the most frequently used" and because it realizes the "highest value of off-beatness" among these 10.

>>> bembé = (1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1)
>>> off_beatness(bembé)
3

Looking beyond these cases, the true highest value for a 12-unit cycle is 4 (using indices 1, 5, 7, 11), as shown in the minimal case here:

>>> off_beatness((0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1))
4
Source code in amads/time/rhythm.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def off_beatness(vector: Union[list[int], tuple[int, ...]]) -> int:
    """
    The "off-beatness" measure records the number of events in a rhythmic cycle
    at positions which cannot fall on a regular beat division of the cycle.
    For a more formal definition, see `totatives`.

    This function expects an indicator vector (values of 0 or 1).
    Behaviour with non-indicator (weighted) vectors is undefined.

    Examples
    --------

    Gomez et al. explore 10 "canonical" 12-unit rhythms of which they
    find the Bembé notable for being "the most frequently used" and
    because it realizes the "highest value of off-beatness" among these 10.

    >>> bembé = (1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1)
    >>> off_beatness(bembé)
    3

    Looking beyond these cases, the true highest value for a 12-unit
    cycle is 4 (using indices 1, 5, 7, 11), as shown in the minimal
    case here:

    >>> off_beatness((0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1))
    4

    """
    t = set(totatives(len(vector)))
    positions = vector_to_multiset(vector)
    return sum(1 for item in t if item in positions)

totatives

totatives(n)

Calculates the totatives of n, which are the positive integers less than n that are relatively prime to n.

Parameters:

  • n (int) –

    A positive integer. In the rhythmic case, this denotes cycle length.

Returns:

  • list[int]

    A list of integers representing the totatives of n. This list is empty if n is less than or equal to 1.

Examples:

>>> totatives(12)
[1, 5, 7, 11]
>>> len(totatives(12))
4
>>> totatives(16)
[1, 3, 5, 7, 9, 11, 13, 15]
>>> len(totatives(16))
8
Source code in amads/time/rhythm.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def totatives(n):
    """
    Calculates the totatives of n, which are the positive integers less
    than n that are relatively prime to n.

    Parameters
    ----------
    n : int
        A positive integer. In the rhythmic case, this denotes cycle length.

    Returns
    -------
    list[int]
        A list of integers representing the totatives of n.
        This list is empty if n is less than or equal to 1.

    Examples
    --------
    >>> totatives(12)
    [1, 5, 7, 11]

    >>> len(totatives(12))
    4

    >>> totatives(16)
    [1, 3, 5, 7, 9, 11, 13, 15]

    >>> len(totatives(16))
    8

    """
    if n <= 1:
        return []

    return [i for i in range(1, n) if integer_gcd_pair(n, i) == 1]

vector_to_onset_beat

vector_to_onset_beat(
    vector: Union[list[int], tuple[int, ...]], beat_unit_length: int = 2
) -> tuple

Map from a vector to onset beat data via vector_to_multiset.

Parameters:

  • vector (Union[list[int], tuple[int, ...]]) –

    An indicator vector. When representing a cycle with rotation, the final element should be a repeat of the first onset to mark the end of the cycle (hence vectors of length cycle_length + 1 are common here).

  • beat_unit_length (int, default: 2 ) –

    The number of subdivisions per beat. Default is 2.

Examples:

The son clave in 16 subdivisions. The 17th element marks the cycle end:

>>> son = [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1]
>>> vector_to_onset_beat(vector=son, beat_unit_length=4)
(Fraction(0, 1), Fraction(3, 4), Fraction(3, 2), Fraction(5, 2), Fraction(3, 1), Fraction(4, 1))
Source code in amads/time/rhythm.py
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
def vector_to_onset_beat(
    vector: Union[list[int], tuple[int, ...]], beat_unit_length: int = 2
) -> tuple:
    """
    Map from a vector to onset beat data via `vector_to_multiset`.

    Parameters
    ----------
    vector
        An indicator vector.
        When representing a cycle with rotation,
        the final element should be a repeat of the first onset to mark the end of the cycle
        (hence vectors of length `cycle_length + 1` are common here).
    beat_unit_length
        The number of subdivisions per beat.
        Default is 2.

    Examples
    --------

    The son clave in 16 subdivisions.
    The 17th element marks the cycle end:

    >>> son = [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1]
    >>> vector_to_onset_beat(vector=son, beat_unit_length=4)
    (Fraction(0, 1), Fraction(3, 4), Fraction(3, 2), Fraction(5, 2), Fraction(3, 1), Fraction(4, 1))

    """
    onsets = vector_to_multiset(vector)
    return tuple(Fraction(x, beat_unit_length) for x in onsets)