Module musx.rhythm
rhythm.py provides a mapping between alternate representations of rhythmic values: fractions, rhythmic symbols, lists of the same or strings of the same.
A fraction is a ratio (or Fraction) of a whole note, e.g. 1/4=quarter note, 1/8=eighth note, 3/2= dotted whole note (three half-notes), 7/25=seven 25ths notes, and so on.
A rhythmic symbol consists of a metric letter: 'w'=whole,'h'=half, 'q'=quarter, 'e'=eighth, 's'=sixteenth, 't'=thirty-second, 'x'=sixty-fourth, optionally preceded by a tuple number (e.g. '3e') and followed by zero or more dots e.g. (3q…). Dotted values larger than the 64th are support. Examples: 'q'=quarter, '3q'=triplet quarter, 'e.'=dotted-eighth, '3s.'=triplet dotted sixteenth, 'h…'=triple dotted half.
Simple expressions involving rhythmic symbols can be formed using +, -, and * to join rhythmic tokens. Examples: 'w*4'=duration of four whole notes, 's+3q'=sixteenth plus triplet quarter, 'w-s.'=whole less a dotted sixteenth.
Expand source code
################################################################################
"""
rhythm.py provides a mapping between alternate representations of rhythmic
values: fractions, rhythmic symbols, lists of the same or strings of the same.
A fraction is a ratio (or Fraction) of a whole note, e.g.
1/4=quarter note, 1/8=eighth note, 3/2= dotted whole note (three half-notes),
7/25=seven 25ths notes, and so on.
A rhythmic symbol consists of a metric letter: 'w'=whole,'h'=half, 'q'=quarter,
'e'=eighth, 's'=sixteenth, 't'=thirty-second, 'x'=sixty-fourth, optionally
preceded by a tuple number (e.g. '3e') and followed by zero or more dots
e.g. (3q...). Dotted values larger than the 64th are support.
Examples: 'q'=quarter, '3q'=triplet quarter, 'e.'=dotted-eighth,
'3s.'=triplet dotted sixteenth, 'h...'=triple dotted half.
Simple expressions involving rhythmic symbols can be formed using +, -, and
* to join rhythmic tokens. Examples: 'w*4'=duration of four whole notes,
's+3q'=sixteenth plus triplet quarter, 'w-s.'=whole less a dotted sixteenth.
"""
from fractions import Fraction
from .tools import parse_string_sequence
def rhythm(ref, tempo=60, beat=1/4):
"""
Returns the value of ref converted to beat units (typically seconds) at a
specified tempo. Ref can be a fraction of a whole note, a rhythmic symbol,
a list of the same or a string of the same separated by commas and/or spaces.
Parameters
----------
ref : string | int | float | Fraction
A fraction of a whole note, a rhythmic symbol, list of the same
or string containing separeted by spaces and/or commas.
tempo : int | float
The metronome marking (beats per minute).
beat : int | float
The beat of the tempo. Defaults to 1/4, or quarter note.
"""
if type(ref) is str:
ref = ref.strip() # remove whitespace from start and end
try:
return (_rhythms[ref] / beat) * (60/tempo)
except:
pass
# split string if it contains commas or spaces.
#ref = ref.replace(',', ' ').split()
ref = parse_string_sequence(ref)
# if it didnt split then its a single expression,
# otherwise its a series (list) of expressions.
if len(ref) == 1:
expr = _rexpr(ref[0])
else:
expr = [_rexpr(e) for e in ref]
#print("expr -> ", expr)
return _reval(expr, beat, tempo)
elif type(ref) is list:
return [rhythm(r, tempo, beat) for r in ref]
elif type(ref) is Fraction:
return (ref / Fraction(beat)) * Fraction(60, tempo)
else:
return (ref / beat) * (60/tempo)
def _rexpr(expr, lev=0):
"""Convert rhythmic expression into prefix notation for evaluation."""
ops = ['+', '-', '*']
if lev < len(ops):
expr = expr.split(ops[lev])
for i, e in enumerate(expr):
expr[i] = _rexpr(e, lev+1)
if len(expr)>1:
# convert to prefix notation: 'q+q+q' => ['+', 'q', 'q', 'q']
expr = [ops[lev]] + expr
else:
expr = expr[0]
return expr
def _reval(expr, beat, tempo):
"""Evaluate an already parsed rhythmic expression."""
if type(expr) is str:
return (_rhythms[expr] / beat) * (60/tempo)
# expr is ['q','q'] or prefixed eval list ['+' ....]
if expr[0] == '+':
x = 0.0
for e in expr[1:]: x += _reval(e, beat,tempo)
return x
elif expr[0] == '*':
if len(expr) == 3 and expr[2].isdigit():
return _reval(expr[1], beat,tempo)
elif expr[0] == '-':
x = _reval(expr[1], beat, tempo)
for e in expr[2:]: x -= _reval(e, beat, tempo)
return x
else:
return [_reval(e, beat, tempo) for e in expr]
expr = expr[0].join(expr[1:])
raise ValueError(f"not a rhythmic expression: {expr}.")
def intempo(time, tempo):
"""
Returns a time value scaled to tempo.
Parameters
----------
time : int | float
The value to scale to tempo.
tempo : int | float
The metronome marking (beats per minute).
Returns
-------
The time value scaled to the tempo.
"""
return time * (60.0 / tempo)
def _createrhythms():
"""Create a hash table of all rhythmic values."""
#toks = [(s, Fraction(1, 2**n)) for n,s in enumerate('whqestx')]
tab = {}
# Iterate the rhythmic letters (w=whole, h=half ... x=sixty-fourth)
for i,t in enumerate('whqestx'):
sym = t
# the rhythmic value of each type w=1/1, h=1/2 ...
rat = Fraction(1, 2**i)
# create the dotted, but dont go smaller than 1/64.
# NB: when d is 0 there is no dot.
for d in range(0,7-i):
#print(sym+('.'*d), rat * (2 - Fraction(1, 2**d)))
#_rhythms[sym+('.'*d)] = rat * (2 - Fraction(1, 2**d))
tab[sym+('.'*d)] = rat * (2 - Fraction(1, 2**d))
for pre in [3]:
amt = Fraction(pre-1,pre)
tab[str(pre)+sym+('.'*d)] = (amt * rat * (2 - Fraction(1, 2**d)))
return tab
_rhythms = _createrhythms()
if __name__ == '__main__':
pass
Functions
def intempo(time, tempo)
-
Returns a time value scaled to tempo.
Parameters
time
:int | float
- The value to scale to tempo.
tempo
:int | float
- The metronome marking (beats per minute).
Returns
The time value scaled to the tempo.
Expand source code
def intempo(time, tempo): """ Returns a time value scaled to tempo. Parameters ---------- time : int | float The value to scale to tempo. tempo : int | float The metronome marking (beats per minute). Returns ------- The time value scaled to the tempo. """ return time * (60.0 / tempo)
def rhythm(ref, tempo=60, beat=0.25)
-
Returns the value of ref converted to beat units (typically seconds) at a specified tempo. Ref can be a fraction of a whole note, a rhythmic symbol, a list of the same or a string of the same separated by commas and/or spaces.
Parameters
ref
:string | int | float | Fraction
- A fraction of a whole note, a rhythmic symbol, list of the same or string containing separeted by spaces and/or commas.
tempo
:int | float
- The metronome marking (beats per minute).
beat
:int | float
- The beat of the tempo. Defaults to 1/4, or quarter note.
Expand source code
def rhythm(ref, tempo=60, beat=1/4): """ Returns the value of ref converted to beat units (typically seconds) at a specified tempo. Ref can be a fraction of a whole note, a rhythmic symbol, a list of the same or a string of the same separated by commas and/or spaces. Parameters ---------- ref : string | int | float | Fraction A fraction of a whole note, a rhythmic symbol, list of the same or string containing separeted by spaces and/or commas. tempo : int | float The metronome marking (beats per minute). beat : int | float The beat of the tempo. Defaults to 1/4, or quarter note. """ if type(ref) is str: ref = ref.strip() # remove whitespace from start and end try: return (_rhythms[ref] / beat) * (60/tempo) except: pass # split string if it contains commas or spaces. #ref = ref.replace(',', ' ').split() ref = parse_string_sequence(ref) # if it didnt split then its a single expression, # otherwise its a series (list) of expressions. if len(ref) == 1: expr = _rexpr(ref[0]) else: expr = [_rexpr(e) for e in ref] #print("expr -> ", expr) return _reval(expr, beat, tempo) elif type(ref) is list: return [rhythm(r, tempo, beat) for r in ref] elif type(ref) is Fraction: return (ref / Fraction(beat)) * Fraction(60, tempo) else: return (ref / beat) * (60/tempo)