# -----------------------------------------------------------------------------
# Name: meter.py
# Purpose: Classes for meters
#
# Authors: Christopher Ariza
# Michael Scott Asato Cuthbert
#
# Copyright: Copyright © 2009-2024 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# -----------------------------------------------------------------------------
'''
This module defines the :class:`~music21.meter.TimeSignature` object,
as well as component objects for defining nested metrical structures,
:class:`~music21.meter.MeterTerminal` and :class:`~music21.meter.MeterSequence` objects.
'''
from __future__ import annotations
from collections.abc import Sequence
import copy
import fractions
from math import gcd
import typing as t
import unittest
from music21 import base
from music21 import beam
from music21 import common
from music21.common.enums import MeterDivision
from music21.common.numberTools import opFrac
from music21 import defaults
from music21 import duration
from music21 import environment
from music21.exceptions21 import MeterException, TimeSignatureException
from music21 import style
from music21.meter.tools import slashToTuple, proportionToFraction
from music21.meter.core import MeterSequence
environLocal = environment.Environment('meter')
if t.TYPE_CHECKING:
from music21.common.types import OffsetQL
from music21 import stream
# this is just a placeholder so that .beamSequence, etc. do not need to
# be typed as Optional. It should never be touched or queried
_SENTINEL_METER_SEQUENCE = MeterSequence()
# -----------------------------------------------------------------------------
# also [pow(2,x) for x in range(8)]
MIN_DENOMINATOR_TYPE = '128th'
# store a module-level dictionary of partitioned meter sequences used
# for setting default accent weights; store as needed
_meterSequenceAccentArchetypes: dict[tuple[str, t.Any, int], MeterSequence] = {}
_meterSequenceAccentArchetypesNoneCache = ('', -1, -1) # a cache key representing None
[docs]
def bestTimeSignature(meas: stream.Stream) -> 'music21.meter.TimeSignature':
# noinspection PyShadowingNames
'''
Given a Measure (or any Stream) with elements in it, get a TimeSignature that contains all
elements.
Note: this does not yet accommodate triplets.
>>> s = converter.parse('tinynotation: C4 D4 E8 F8').flatten().notes
>>> m = stream.Measure()
>>> for el in s:
... m.insert(el.offset, el)
>>> ts = meter.bestTimeSignature(m)
>>> ts
<music21.meter.TimeSignature 3/4>
>>> s2 = converter.parse('tinynotation: C8. D16 E8 F8. G16 A8').flatten().notes
>>> m2 = stream.Measure()
>>> for el in s2:
... m2.insert(el.offset, el)
>>> ts2 = meter.bestTimeSignature(m2)
>>> ts2
<music21.meter.TimeSignature 6/8>
>>> s3 = converter.parse('C2 D2 E2', format='tinyNotation').flatten().notes
>>> m3 = stream.Measure()
>>> for el in s3:
... m3.insert(el.offset, el)
>>> ts3 = meter.bestTimeSignature(m3)
>>> ts3
<music21.meter.TimeSignature 3/2>
>>> s4 = converter.parse('C8. D16 E8 F8. G16 A8 C4. D4.', format='tinyNotation').flatten().notes
>>> m4 = stream.Measure()
>>> for el in s4:
... m4.insert(el.offset, el)
>>> ts4 = meter.bestTimeSignature(m4)
>>> ts4
<music21.meter.TimeSignature 12/8>
>>> s5 = converter.parse('C4 D2 E4 F2', format='tinyNotation').flatten().notes
>>> m5 = stream.Measure()
>>> for el in s5:
... m5.insert(el.offset, el)
>>> ts5 = meter.bestTimeSignature(m5)
>>> ts5
<music21.meter.TimeSignature 6/4>
>>> s6 = converter.parse('C4 D16.', format='tinyNotation').flatten().notes
>>> m6 = stream.Measure()
>>> for el in s6:
... m6.insert(el.offset, el)
>>> ts6 = meter.bestTimeSignature(m6)
>>> ts6
<music21.meter.TimeSignature 11/32>
Complex durations (arose in han2.abc, number 445)
>>> m7 = stream.Measure()
>>> m7.append(note.Note('D', quarterLength=3.5))
>>> m7.append(note.Note('E', quarterLength=5.5))
>>> ts7 = meter.bestTimeSignature(m7)
>>> ts7
<music21.meter.TimeSignature 9/4>
'''
# smallest denominator; start with a whole note
minDurQL = 4.0
# find sum of all durations in quarter length
# find if there are any dotted durations
minDurDots = 0
sumDurQL = opFrac(meas.duration.quarterLength)
# beatStrAvg = 0
# beatStrAvg += e.beatStrength
numerator = 0
denominator = 1
for e in meas.recurse().notesAndRests:
if e.quarterLength == 0.0:
continue # case of grace durations
if (e.quarterLength < minDurQL
and not isinstance(opFrac(e.quarterLength), fractions.Fraction)):
# no non-power2 signatures
minDurQL = e.quarterLength
minDurDots = e.duration.dots
# first, we need to evenly divide min dur into total
minDurTest = minDurQL
if isinstance(sumDurQL, fractions.Fraction):
# not a power of two -- some tuplets, etc.
numerator = sumDurQL.numerator
denominator = sumDurQL.denominator
else:
i = 10
while i > 0:
partsFloor = int(sumDurQL / minDurTest)
partsReal = opFrac(sumDurQL / minDurTest)
if (partsFloor == partsReal
or minDurTest <= duration.typeToDuration[MIN_DENOMINATOR_TYPE]):
break
# need to break down minDur until we can get a match
else:
minDurTest = minDurTest / (2 * common.dotMultiplier(minDurDots))
i -= 1
# see if we can get a type for the denominator
# if we do not have a match; we need to break down this value
match = False
durationMinLimit = duration.typeToDuration[MIN_DENOMINATOR_TYPE]
i = 10
while i > 0:
if minDurTest < durationMinLimit:
minDurTest = durationMinLimit
break
try:
dType, match = duration.quarterLengthToClosestType(minDurTest)
except ZeroDivisionError:
raise MeterException('Cannot find a good match for this measure')
if match or dType == MIN_DENOMINATOR_TYPE:
break
minDurTest = minDurTest / (2 * common.dotMultiplier(minDurDots))
i -= 1
minDurQL = minDurTest
dType, match = duration.quarterLengthToClosestType(minDurQL)
if not match: # cannot find a type for a denominator
raise MeterException(f'cannot find a type for denominator {minDurQL}')
# denominator is the numerical representation of the min type
# e.g., quarter is 4, whole is 1
floatDenominator = float(denominator)
for num, typeName in duration.typeFromNumDict.items():
if typeName == dType:
floatDenominator = num
break
# numerator is the count of min parts in the sum
multiplier = 1
while i > 0:
numerator = multiplier * sumDurQL / minDurQL
if numerator == int(numerator):
break
multiplier *= 2
i -= 1
numerator = int(numerator)
floatDenominator *= multiplier
denominator = int(floatDenominator)
# simplifies to "simplest terms," with 4 in denominator, before testing beat strengths
gcdValue = gcd(numerator, denominator)
numerator = numerator // gcdValue
denominator = denominator // gcdValue
# simplifies rare time signatures like 16/16 and 1/1 to 4/4
if numerator == denominator and numerator not in [2, 4]:
numerator = 4
denominator = 4
elif numerator != denominator and denominator == 1:
numerator *= 4
denominator *= 4
elif numerator != denominator and denominator == 2:
numerator *= 2
denominator *= 2
# a fairly accurate test of whether 3/4 or 6/8 is more appropriate (see doctests)
if numerator == 3 and denominator == 4:
ts1 = TimeSignature('3/4')
ts2 = TimeSignature('6/8')
str1 = ts1.averageBeatStrength(meas)
str2 = ts2.averageBeatStrength(meas)
if str1 <= str2:
return ts2
else:
return ts1
# tries three time signatures if "simplest" time signature is 6/4 or 3/2
elif numerator == 6 and denominator == 4:
ts1 = TimeSignature('6/4')
ts2 = TimeSignature('12/8')
ts3 = TimeSignature('3/2')
str1 = ts1.averageBeatStrength(meas)
str2 = ts2.averageBeatStrength(meas)
str3 = ts3.averageBeatStrength(meas)
m = max(str1, str2, str3)
if m == str1:
return ts1
elif m == str3:
return ts3
else:
return ts2
else:
ts = TimeSignature()
ts.load(f'{numerator}/{denominator}')
return ts
# -----------------------------------------------------------------------------
[docs]
class TimeSignatureBase(base.Music21Object):
'''
A base class for TimeSignature and SenzaMisuraTimeSignature to
inherit from.
'''
pass
[docs]
class TimeSignature(TimeSignatureBase):
r'''
The `TimeSignature` object represents time signatures in musical scores
(4/4, 3/8, 2/4+5/16, Cut, etc.).
`TimeSignatures` should be present in the first `Measure` of each `Part`
that they apply to. Alternatively you can put the time signature at the
front of a `Part` or at the beginning of a `Score`, and they will work
within music21, but they won't necessarily display properly in MusicXML,
Lilypond, etc. So best is to create structures where the TimeSignature
goes in the first Measure of the score, as below:
>>> s = stream.Score()
>>> p = stream.Part()
>>> m1 = stream.Measure()
>>> ts = meter.TimeSignature('3/4')
>>> m1.insert(0, ts)
>>> m1.insert(0, note.Note('C#3', type='half'))
>>> n = note.Note('D3', type='quarter')
>>> m1.insert(1.0, n)
>>> m1.number = 1
>>> p.insert(0, m1)
>>> s.insert(0, p)
>>> s.show('t')
{0.0} <music21.stream.Part ...>
{0.0} <music21.stream.Measure 1 offset=0.0>
{0.0} <music21.meter.TimeSignature 3/4>
{0.0} <music21.note.Note C#>
{1.0} <music21.note.Note D>
Basic operations on a TimeSignature object are designed to be very simple.
>>> ts.ratioString
'3/4'
>>> ts.numerator
3
>>> ts.beatCount
3
>>> ts.beatCountName
'Triple'
>>> ts.beatDuration.quarterLength
1.0
As an alternative to putting a `TimeSignature` in a Stream at a specific
position (offset), it can be assigned to a special property in Measure that
positions the TimeSignature at the start of a Measure. Notice that when we
`show()` the Measure (or if we iterate through it), the TimeSignature
appears as if it's in the measure itself:
>>> m2 = stream.Measure()
>>> m2.number = 2
>>> ts2 = meter.TimeSignature('2/4')
>>> m2.timeSignature = ts2
>>> m2.append(note.Note('E3', type='half'))
>>> p.append(m2)
>>> s.show('text')
{0.0} <music21.stream.Part ...>
{0.0} <music21.stream.Measure 1 offset=0.0>
{0.0} <music21.meter.TimeSignature 3/4>
{0.0} <music21.note.Note C#>
{1.0} <music21.note.Note D>
{2.0} <music21.stream.Measure 2 offset=2.0>
{0.0} <music21.meter.TimeSignature 2/4>
{0.0} <music21.note.Note E>
Once a Note has a local TimeSignature, a Note can get its beat position and
other meter-specific parameters. Remember `n`, our quarter note at offset
2.0 of `m1`, a 3/4 measure? Let's get its beat:
>>> n.beat
2.0
This feature is more useful if there are more beats:
>>> m3 = stream.Measure()
>>> m3.timeSignature = meter.TimeSignature('3/4')
>>> eighth = note.Note(type='eighth')
>>> m3.repeatAppend(eighth, 6)
>>> [thisNote.beatStr for thisNote in m3.notes]
['1', '1 1/2', '2', '2 1/2', '3', '3 1/2']
Now lets change its measure's TimeSignature and see what happens:
>>> sixEight = meter.TimeSignature('6/8')
>>> m3.timeSignature = sixEight
>>> [thisNote.beatStr for thisNote in m3.notes]
['1', '1 1/3', '1 2/3', '2', '2 1/3', '2 2/3']
TimeSignature('6/8') defaults to fast 6/8:
>>> sixEight.beatCount
2
>>> sixEight.beatDuration.quarterLength
1.5
>>> sixEight.beatDivisionCountName
'Compound'
Let's make it slow 6/8 instead:
>>> sixEight.beatCount = 6
>>> sixEight.beatDuration.quarterLength
0.5
>>> sixEight.beatDivisionCountName
'Simple'
Now let's look at the `beatStr` for each of the notes in `m3`:
>>> [thisNote.beatStr for thisNote in m3.notes]
['1', '2', '3', '4', '5', '6']
As of v7, 3/8 also defaults to fast 3/8, that is, one beat:
>>> meter.TimeSignature('3/8').beatCount
1
`TimeSignatures` can also use symbols instead of numbers
>>> tsCommon = meter.TimeSignature('c') # or common
>>> tsCommon.beatCount
4
>>> tsCommon.denominator
4
>>> tsCommon.symbol
'common'
>>> tsCut = meter.TimeSignature('cut')
>>> tsCut.beatCount
2
>>> tsCut.denominator
2
>>> tsCut.symbol
'cut'
For other time signatures, the symbol is '' (not set) or 'normal'
>>> sixEight.symbol
''
For complete details on using this object, see
:ref:`User's Guide Chapter 14: Time Signatures <usersGuide_14_timeSignatures>` and
:ref:`User's Guide Chapter 55: Advanced Meter <usersGuide_55_advancedMeter>` and
That's it for the simple aspects of `TimeSignature` objects. You know
enough to get started now!
Under the hood, they're extremely powerful. For musicians, TimeSignatures
do (at least) three different things:
* They define where the beats in the measure are and how many there are.
* They indicate how the notes should be beamed
* They give a sense of how much accent or weight each note gets, which
also defines which are important notes and which might be ornaments.
These three aspects of `TimeSignatures` are controlled by the
:attr:`~music21.meter.TimeSignature.beatSequence`,
:attr:`~music21.meter.TimeSignature.beamSequence`, and
:attr:`~music21.meter.TimeSignature.accentSequence` properties of the
`TimeSignature`. Each of them is an independent
:class:`~music21.meter.MeterSequence` element which might have nested
properties (e.g., an 11/16 meter might be beamed as {1/4+1/4+{1/8+1/16}}),
so if you want to change how beats are calculated or beams are generated
you'll want to learn more about `meter.MeterSequence` objects.
There's a fourth `MeterSequence` object inside a TimeSignature, and that is
the :attr:`~music21.meter.TimeSignature.displaySequence`. That determines
how the `TimeSignature` should actually look on paper. Normally this
`MeterSequence` is pretty simple. In '4/4' it's usually just '4/4'. But
if you have the '11/16' time above, you may want to have it displayed as
'2/4+3/16' or '11/16 (2/4+3/16)'. Or you might want the written
TimeSignature to contradict what the notes imply. All this can be done
with .displaySequence.
**Equality**
For two time signatures to be considered equal,
they have the same name and internal structure.
The name is tested by the :attr:`~music21.meter.TimeSignature.symbol`.
This helps distinguish between 'Cut' and '2/2', for example.
>>> tsCut = meter.TimeSignature('Cut')
>>> ts22 = meter.TimeSignature('2/2')
>>> tsCut == ts22
False
The internal structure is currently tested simply by the
:attr:`~music21.meter.TimeSignature.beatCount` and
:attr:`~music21.meter.TimeSignature.ratioString` attributes.
The check of :attr:`~music21.meter.TimeSignature.beatCount`
helps to distinguish the 'fast' (2-beat) from 'slow' (6-beat)
versions of 6/8, for example.
>>> fast68 = meter.TimeSignature('fast 6/8')
>>> slow68 = meter.TimeSignature('slow 6/8')
>>> fast68 == slow68
False
Complementing this,
:attr:`~music21.meter.TimeSignature.ratioString`
provides a check of the internal divsions such that
'2/8+3/8' is different from '3/8+2/8', for example,
despite the fact that they could both be written as '5/8'.
>>> ts2n3 = meter.TimeSignature('2/8+3/8')
>>> ts3n2 = meter.TimeSignature('3/8+2/8')
>>> ts2n3 == ts3n2
False
For a less restrictive test of this, see
:meth:`~music21.meter.TimeSignature.ratioEqual`
which returns True for all cases of '5/8'.
>>> ts2n3.ratioEqual(ts3n2)
True
Yes, equality is ever True:
>>> one44 = meter.TimeSignature('4/4')
>>> another44 = meter.TimeSignature() # '4/4' by default
>>> one44 == another44
True
'''
_styleClass = style.TextStyle
classSortOrder = 4
equalityAttributes = ('symbol', # '2/2' != 'Cut'
'ratioString', # '2+3' != '3+2'
'beatCount' # 'slow 6/8' != 'fast 6/8'
)
_DOC_ATTR: dict[str, str] = {
'beatSequence': 'A :class:`~music21.meter.MeterSequence` governing beat partitioning.',
'beamSequence': 'A :class:`~music21.meter.MeterSequence` governing automatic beaming.',
'accentSequence': 'A :class:`~music21.meter.MeterSequence` governing accent partitioning.',
'displaySequence': '''
A :class:`~music21.meter.MeterSequence` governing the display of the TimeSignature.''',
'symbol': '''
A string representation of how to display the TimeSignature.
can be "common", "cut", "single-number" (i.e.,
no denominator), or "normal" or "".''',
'symbolizeDenominator': '''
If set to `True` (default is `False`) then the denominator
will be displayed as a symbol rather than
a number. Hindemith uses this in his scores.
Finale and other MusicXML readers do not support this
so do not expect proper output yet.''',
}
def __init__(self, value: str = '4/4', divisions=None, **keywords):
super().__init__(**keywords)
if value is None:
value = f'{defaults.meterNumerator}/{defaults.meterDenominatorBeatType}'
self._overriddenBarDuration: duration.Duration|None = None
self.symbol: str = ''
self.displaySequence: MeterSequence = _SENTINEL_METER_SEQUENCE
self.beatSequence: MeterSequence = _SENTINEL_METER_SEQUENCE
self.accentSequence: MeterSequence = _SENTINEL_METER_SEQUENCE
self.beamSequence: MeterSequence = _SENTINEL_METER_SEQUENCE
self.symbolizeDenominator: bool = False
self.resetValues(value, divisions)
def _reprInternal(self):
return self.ratioString
[docs]
def resetValues(self, value: str = '4/4', divisions=None):
'''
reset all values according to a new value and optionally, the number of
divisions.
'''
self.symbol = '' # common, cut, single-number, normal
# a parameter to determine if the denominator is represented
# as either a symbol (a note) or as a number
self.symbolizeDenominator = False
self._overriddenBarDuration = None
# creates MeterSequence data representations
# creates .displaySequence, .beamSequence, .beatSequence, .accentSequence
self.load(value, divisions)
[docs]
def load(self, value: str, divisions=None):
'''
Load up a TimeSignature with a string value.
>>> ts = meter.TimeSignature()
>>> ts.load('4/4')
>>> ts
<music21.meter.TimeSignature 4/4>
>>> ts.load('c')
>>> ts.symbol
'common'
>>> ts.load('2/4+3/8')
>>> ts
<music21.meter.TimeSignature 2/4+3/8>
>>> ts.load('fast 6/8')
>>> ts.beatCount
2
>>> ts.load('slow 6/8')
>>> ts.beatCount
6
Loading destroys all preexisting internal representations
'''
# create parallel MeterSequence objects to provide all data
# these all refer to the same .numerator/.denominator
# relationship
# used for drawing the time signature symbol
# this is the only one that can be unlinked
if value.lower() in ('common', 'c'):
value = '4/4'
self.symbol = 'common'
elif value.lower() in ('cut', 'allabreve'):
# allaBreve is the capella version
value = '2/2'
self.symbol = 'cut'
self.displaySequence = MeterSequence(value)
# get simple representation; presently, only slashToTuple
# supports the fast/slow indication
numerator, denominator, division = slashToTuple(value)
if division == MeterDivision.NONE:
if numerator % 3 == 0 and denominator >= 8:
division = MeterDivision.FAST
elif numerator == 3:
division = MeterDivision.SLOW
favorCompound = (division != MeterDivision.SLOW)
# used for beaming
self.beamSequence = MeterSequence(value, divisions)
# used for getting beat divisions
self.beatSequence = MeterSequence(value, divisions)
# accentSequence is used for setting one level of accents
self.accentSequence = MeterSequence(value, divisions)
if divisions is None: # set default beam partitions
# beam is not adjust by tempo indication
self._setDefaultBeamPartitions()
self._setDefaultBeatPartitions(favorCompound=favorCompound)
# for some summed meters default accent weights are difficult
# to obtain
try:
self._setDefaultAccentWeights(3) # set partitions based on beat
except MeterException:
environLocal.printDebug(['cannot set default accents for:', self])
@property
def ratioString(self):
'''
Returns or sets a simple string representing the time signature ratio.
>>> threeFour = meter.TimeSignature('3/4')
>>> threeFour.ratioString
'3/4'
It can also be set to load a new one, but '.load()' is better:
>>> threeFour.ratioString = '5/8' # now this variable name is dumb!
>>> threeFour.numerator
5
>>> threeFour.denominator
8
>>> complexTime = meter.TimeSignature('2/4+3/8')
>>> complexTime.ratioString
'2/4+3/8'
For advanced users, getting the ratioString is the equivalent of
:attr:`~music21.meter.core.MeterSequence.partitionDisplay` on the displaySequence:
>>> complexTime.displaySequence.partitionDisplay
'2/4+3/8'
'''
return self.displaySequence.partitionDisplay
@ratioString.setter
def ratioString(self, newRatioString):
self.resetValues(newRatioString)
[docs]
def ratioEqual(self, other):
'''
A basic form of comparison; does not determine if any internal structures are equal; o
only outermost ratio.
'''
if other is None:
return False
if (other.numerator == self.numerator
and other.denominator == self.denominator):
return True
else:
return False
# --------------------------------------------------------------------------
# properties
@property
def numerator(self):
'''
Return the numerator of the TimeSignature as a number.
Can set the numerator for a simple TimeSignature.
To set the numerator of a complex TimeSignature, change beatCount.
(for complex TimeSignatures, note that this comes from the .beamSequence
of the TimeSignature)
>>> ts = meter.TimeSignature('3/4')
>>> ts.numerator
3
>>> ts.numerator = 5
>>> ts
<music21.meter.TimeSignature 5/4>
In this case, the TimeSignature is silently being converted to 9/8
to get a single digit numerator:
>>> ts = meter.TimeSignature('2/4+5/8')
>>> ts.numerator
9
Setting a summed time signature's numerator will change to a
simple time signature
>>> ts.numerator = 11
>>> ts
<music21.meter.TimeSignature 11/8>
'''
return self.beamSequence.numerator
@numerator.setter
def numerator(self, value):
denominator = self.denominator
newRatioString = str(value) + '/' + str(denominator)
self.resetValues(newRatioString)
@property
def denominator(self):
'''
Return the denominator of the TimeSignature as a number or set it.
(for complex TimeSignatures, note that this comes from the .beamSequence
of the TimeSignature)
>>> ts = meter.TimeSignature('3/4')
>>> ts.denominator
4
>>> ts.denominator = 8
>>> ts.ratioString
'3/8'
In this following case, the TimeSignature is silently being converted to 9/8
to get a single digit denominator:
>>> ts = meter.TimeSignature('2/4+5/8')
>>> ts.denominator
8
'''
return self.beamSequence.denominator
@denominator.setter
def denominator(self, value):
numeratorValue = self.numerator
newRatioString = str(numeratorValue) + '/' + str(value)
self.resetValues(newRatioString)
@property
def barDuration(self) -> duration.Duration:
'''
Return a :class:`~music21.duration.Duration` object equal to the
total length of this TimeSignature.
>>> ts = meter.TimeSignature('5/16')
>>> ts.barDuration
<music21.duration.Duration 1.25>
>>> ts2 = meter.TimeSignature('3/8')
>>> d = ts2.barDuration
>>> d.type
'quarter'
>>> d.dots
1
>>> d.quarterLength
1.5
This can be overridden to create different representations
or to contradict the meter.
>>> d2 = duration.Duration(1.75)
>>> ts2.barDuration = d2
>>> ts2.barDuration
<music21.duration.Duration 1.75>
An uninitialized TimeSignature returns 4.0 for 4/4
>>> meter.TimeSignature().barDuration
<music21.duration.Duration 4.0>
'''
if self._overriddenBarDuration:
return self._overriddenBarDuration
beamSequence = self.beamSequence
if beamSequence is not None:
# could come from self.beamSequence, self.accentSequence,
# self.displaySequence, self.accentSequence
return beamSequence.duration
# should never happen.
return duration.Duration(0) # pragma: no cover
@barDuration.setter
def barDuration(self, value: duration.Duration):
self._overriddenBarDuration = value
@property
def beatLengthToQuarterLengthRatio(self) -> float:
'''
Returns 4.0 / denominator. Might seem a bit silly but used often.
>>> a = meter.TimeSignature('3/2')
>>> a.beatLengthToQuarterLengthRatio
2.0
'''
return 4.0 / self.denominator
@property
def quarterLengthToBeatLengthRatio(self) -> float:
'''
Returns denominator/4.0.
'''
return self.denominator / 4.0
# --------------------------------------------------------------------------
# meter classifications used for classifying meters such as
# duple triple, etc.
@property
def beatCount(self) -> int:
'''
Return or set the count of beat units, or the number of beats in this TimeSignature.
When setting beat units, one level of sub-partitions is automatically defined.
Users can specify beat count values as integers or as lists of durations.
For more precise configuration of the beat MeterSequence,
manipulate the .beatSequence attribute directly.
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatCount
3
>>> ts.beatDuration.quarterLength
1.0
>>> ts.beatCount = [1, 1, 1, 1, 1, 1]
>>> ts.beatCount
6
>>> ts.beatDuration.quarterLength
0.5
Setting a beat-count directly is a simple, high-level way to configure the beatSequence.
Note that his may not configure lower level partitions correctly,
and will raise an error if the provided beat count is not supported by the
overall duration of the .beatSequence MeterSequence.
>>> ts = meter.TimeSignature('6/8')
>>> ts.beatCount # default is 2 beats
2
>>> ts.beatSequence
<music21.meter.core.MeterSequence {{1/8+1/8+1/8}+{1/8+1/8+1/8}}>
>>> ts.beatDivisionCountName
'Compound'
>>> ts.beatCount = 6
>>> ts.beatSequence
<music21.meter.core.MeterSequence
{{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}+{1/16+1/16}}>
>>> ts.beatDivisionCountName
'Simple'
>>> ts.beatCount = 123
Traceback (most recent call last):
music21.exceptions21.TimeSignatureException: cannot partition beat with provided value: 123
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatCount = 6
>>> ts.beatDuration.quarterLength
0.5
'''
# the default is for the beat to be defined by the first, not zero,
# level partition.
return len(self.beatSequence)
@beatCount.setter
def beatCount(self, value: int):
try:
self.beatSequence.partition(value)
except MeterException:
raise TimeSignatureException(f'cannot partition beat with provided value: {value}')
# create subdivisions using default parameters
if len(self.beatSequence) > 1: # if partitioned
self.beatSequence.subdividePartitionsEqual()
@property
def beatCountName(self) -> str:
'''
Return the beat count name, or the name given for the number of beat units.
For example, 2/4 is duple; 9/4 is triple.
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatCountName
'Triple'
>>> ts = meter.TimeSignature('6/8')
>>> ts.beatCountName
'Duple'
'''
return self.beatSequence.partitionStr
@property
def beatDuration(self) -> duration.Duration:
'''
Return a :class:`~music21.duration.Duration` object equal to the beat unit
of this Time Signature, if and only if this TimeSignature has a uniform beat unit.
Otherwise, raises an exception in v7.1 but will change to returning NaN
soon fasterwards.
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatDuration
<music21.duration.Duration 1.0>
>>> ts = meter.TimeSignature('6/8')
>>> ts.beatDuration
<music21.duration.Duration 1.5>
>>> ts = meter.TimeSignature('7/8')
>>> ts.beatDuration
<music21.duration.Duration 0.5>
>>> ts = meter.TimeSignature('3/8')
>>> ts.beatDuration
<music21.duration.Duration 1.5>
>>> ts.beatCount = 3
>>> ts.beatDuration
<music21.duration.Duration 0.5>
Cannot do this because of asymmetry
>>> ts = meter.TimeSignature('2/4+3/16')
>>> ts.beatDuration
Traceback (most recent call last):
music21.exceptions21.TimeSignatureException: non-uniform beat unit: [2.0, 0.75]
* Changed in v7: return NaN rather than raising Exception in property.
'''
post = []
for ms in self.beatSequence:
post.append(ms.duration.quarterLength)
if len(set(post)) == 1:
return self.beatSequence[0].duration # all are the same
else:
raise TimeSignatureException(f'non-uniform beat unit: {post}')
@property
def beatDivisionCount(self) -> int:
'''
Return the count of background beat units found within one beat,
or the number of subdivisions in the beat unit in this TimeSignature.
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatDivisionCount
2
>>> ts = meter.TimeSignature('6/8')
>>> ts.beatDivisionCount
3
>>> ts = meter.TimeSignature('15/8')
>>> ts.beatDivisionCount
3
>>> ts = meter.TimeSignature('3/8')
>>> ts.beatDivisionCount
1
>>> ts = meter.TimeSignature('13/8', 13)
>>> ts.beatDivisionCount
1
* Changed in v7: return 1 instead of a TimeSignatureException.
'''
# first, find if there is more than one beat and if all beats are uniformly partitioned
post = []
if len(self.beatSequence) == 1:
return 1
# need to see if first-level subdivisions are partitioned
beat_seq_0 = self.beatSequence[0]
if not isinstance(beat_seq_0, MeterSequence):
return 1
# getting length here gives number of subdivisions
for ms in self.beatSequence:
post.append(len(ms))
# convert this to a set; if length is 1, then all beats are uniform
if len(set(post)) == 1:
return len(beat_seq_0) # all are the same
else:
return 1
@property
def beatDivisionCountName(self) -> str:
'''
Return the beat count name, or the name given for the number of beat units.
For example, 2/4 is duple; 9/4 is triple.
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatDivisionCountName
'Simple'
>>> ts = meter.TimeSignature('6/8')
>>> ts.beatDivisionCountName
'Compound'
Rare cases of 5-beat divisions return 'Other', like this 10/8 divided into
5/8 + 5/8 with no further subdivisions:
>>> ts = meter.TimeSignature('10/8')
>>> ts.beatSequence.partition(2)
>>> ts.beatSequence
<music21.meter.core.MeterSequence {5/8+5/8}>
>>> for i, mt in enumerate(ts.beatSequence):
... ts.beatSequence[i] = mt.subdivideByCount(5)
>>> ts.beatSequence
<music21.meter.core.MeterSequence {{1/8+1/8+1/8+1/8+1/8}+{1/8+1/8+1/8+1/8+1/8}}>
>>> ts.beatDivisionCountName
'Other'
'''
beatDivision = self.beatDivisionCount
if beatDivision == 2:
return 'Simple'
elif beatDivision == 3:
return 'Compound'
else:
return 'Other'
@property
def beatDivisionDurations(self) -> list[duration.Duration]:
'''
Return the beat division, or the durations that make up one beat,
as a list of :class:`~music21.duration.Duration` objects, if and only if
the TimeSignature has a uniform beat division for all beats.
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatDivisionDurations
[<music21.duration.Duration 0.5>,
<music21.duration.Duration 0.5>]
>>> ts = meter.TimeSignature('6/8')
>>> ts.beatDivisionDurations
[<music21.duration.Duration 0.5>,
<music21.duration.Duration 0.5>,
<music21.duration.Duration 0.5>]
Value returned of non-uniform beat divisions will change at any time
after v7.1 to avoid raising an exception.
OMIT_FROM_DOCS
Previously a time signature with beatSequence containing only
MeterTerminals would raise exceptions.
>>> ts = meter.TimeSignature('2/128')
>>> ts.beatSequence[0]
<music21.meter.core.MeterTerminal 1/128>
>>> ts.beatDivisionDurations
[<music21.duration.Duration 0.03125>]
>>> ts = meter.TimeSignature('1/128')
>>> ts.beatSequence[0]
<music21.meter.core.MeterTerminal 1/128>
>>> ts.beatDivisionDurations
[<music21.duration.Duration 0.03125>]
'''
post = []
for mt in self.beatSequence:
if isinstance(mt, MeterSequence):
for subMt in mt:
post.append(subMt.duration.quarterLength)
else:
post.append(mt.duration.quarterLength)
if len(set(post)) == 1: # all the same
out = []
beat_seq_0 = self.beatSequence[0]
if isinstance(beat_seq_0, MeterSequence):
for subMt in beat_seq_0:
if subMt.duration is not None: # should not be:
out.append(subMt.duration)
elif beat_seq_0.duration is not None: # MeterTerminal w/ non-empty duration.
out.append(beat_seq_0.duration)
return out
else:
raise TimeSignatureException(f'non uniform beat division: {post}')
@property
def beatSubDivisionDurations(self) -> list[duration.Duration]:
'''
Return a subdivision of the beat division, or a list
of :class:`~music21.duration.Duration` objects representing each beat division
divided by two.
>>> ts = meter.TimeSignature('3/4')
>>> ts.beatSubDivisionDurations
[<music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>,
<music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>]
>>> ts = meter.TimeSignature('6/8')
>>> ts.beatSubDivisionDurations
[<music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>,
<music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>,
<music21.duration.Duration 0.25>, <music21.duration.Duration 0.25>]
'''
post = []
src = self.beatDivisionDurations
for d in src:
# this is too slow. TODO: fix, but make sure all durations are unique.
post.append(d.augmentOrDiminish(0.5))
post.append(d.augmentOrDiminish(0.5))
return post
@property
def classification(self) -> str:
'''
Return the classification of this TimeSignature,
such as Simple Triple or Compound Quadruple.
>>> ts = meter.TimeSignature('3/4')
>>> ts.classification
'Simple Triple'
>>> ts = meter.TimeSignature('6/8')
>>> ts.classification
'Compound Duple'
>>> ts = meter.TimeSignature('4/32')
>>> ts.classification
'Simple Quadruple'
'''
return f'{self.beatDivisionCountName} {self.beatCountName}'
@property
def summedNumerator(self) -> bool:
if self.displaySequence is None:
return False
return self.displaySequence.summedNumerator
@summedNumerator.setter
def summedNumerator(self, value: bool):
if self.displaySequence is not None:
self.displaySequence.summedNumerator = value
# --------------------------------------------------------------------------
# private methods -- most to be put into the various sequences.
def _setDefaultBeatPartitions(self, *, favorCompound=True) -> None:
'''
Set default beat partitions based on numerator and denominator.
>>> ts = meter.TimeSignature('3/4')
>>> len(ts.beatSequence) # first, not zeroth, level stores beat
3
>>> ts = meter.TimeSignature('6/8')
>>> len(ts.beatSequence)
2
>>> ts = meter.TimeSignature('slow 6/8')
>>> len(ts.beatSequence)
6
* Changed in v7: favorCompound is keyword only.
'''
# if a non-compound meter has been given, as in
# not 3+1/4; just 5/4
if len(self.displaySequence) == 1:
# create toplevel partitions
if self.numerator == 2: # duple meters
self.beatSequence.partition(2)
elif self.numerator == 6 and favorCompound: # duple meters
self.beatSequence.partition(2)
elif self.numerator == 3 and favorCompound: # 3/8, 3/16, but not 3/4
self.beatSequence.partition(1)
elif self.numerator == 3: # triple meters
self.beatSequence.partition([1, 1, 1])
elif self.numerator == 9 and favorCompound: # triple meters
self.beatSequence.partition([3, 3, 3])
elif self.numerator == 4: # quadruple meters
self.beatSequence.partition(4)
elif self.numerator == 12 and favorCompound:
self.beatSequence.partition(4)
elif self.numerator >= 15 and self.numerator % 3 == 0 and favorCompound:
# quintuple meters and above.
num_triples = self.numerator // 3
self.beatSequence.partition([3] * num_triples)
# skip 6 numerators; covered above
else: # case of odd meters: 11, 13
self.beatSequence.partition(self.numerator)
# if a complex meter has been given
else: # partition by display
# TODO: remove partitionByMeterSequence usage.
self.beatSequence.partition(self.displaySequence)
# create subdivisions, and thus define compound/simple distinction
if len(self.beatSequence) > 1: # if partitioned
try:
self.beatSequence.subdividePartitionsEqual()
except MeterException:
if self.denominator >= 128:
pass # do not raise an exception for unable to subdivide smaller than 128
def _setDefaultBeamPartitions(self) -> None:
'''
This method sets default beam partitions when partitionRequest is None.
'''
# beam short measures of 8ths, 16ths, or 32nds all together
if self.beamSequence.summedNumerator:
pass # do not mess with a numerator such as (3+2)/8
elif self.denominator == 8 and self.numerator in (1, 2, 3):
pass # doing nothing will beam all together
elif self.denominator == 16 and self.numerator in range(1, 6):
# 1 - 5 -- beam all together
pass
elif self.denominator == 32 and self.numerator in range(1, 12):
# 1 - 11 -- beam all together.
pass
# more general, based only on numerator
elif self.numerator in (2, 3, 4):
self.beamSequence.partition(self.numerator)
# if denominator is 4, subdivide each partition
if self.denominator == 4:
for i in range(len(self.beamSequence)): # subdivide each beat in 2
self.beamSequence[i] = self.beamSequence[i].subdivide(2)
elif self.numerator == 5:
default = [2, 3]
self.beamSequence.partition(default)
# if denominator is 4, subdivide each partition
if self.denominator == 4:
for i in range(len(self.beamSequence)): # subdivide each beat in 2
self.beamSequence[i] = self.beamSequence[i].subdivide(default[i])
elif self.numerator == 7:
self.beamSequence.partition(3) # divide into three groups
elif self.numerator in [6, 9, 12, 15, 18, 21]:
self.beamSequence.partition([3] * int(self.numerator / 3))
else:
pass # doing nothing will beam all together
# environLocal.printDebug(f'default beam partitions set to: {self.beamSequence}')
def _setDefaultAccentWeights(self, depth: int = 3) -> None:
'''
This method sets default accent weights based on common hierarchical notions for meters;
each beat is given a weight, as defined by the top level count of self.beatSequence
>>> ts1 = meter.TimeSignature('4/4')
>>> ts1._setDefaultAccentWeights(4)
>>> [mt.weight for mt in ts1.accentSequence]
[1.0, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625,
0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625]
>>> ts2 = meter.TimeSignature('3/4')
>>> ts2._setDefaultAccentWeights(4)
>>> [mt.weight for mt in ts2.accentSequence]
[1.0, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625,
0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625,
0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625]
>>> ts2._setDefaultAccentWeights(3) # lower depth
>>> [mt.weight for mt in ts2.accentSequence]
[1.0, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125]
'''
# NOTE: this is a performance critical method
firstPartitionForm: MeterSequence|int|None
# create a scratch MeterSequence for structure
tsStr = f'{self.numerator}/{self.denominator}'
if self.beatSequence.isUniformPartition():
if len(self.beatSequence) > 1:
firstPartitionForm = len(self.beatSequence)
else:
firstPartitionForm = None
cacheKey = (tsStr, firstPartitionForm, depth)
else: # derive from meter sequence
firstPartitionForm = self.beatSequence
cacheKey = _meterSequenceAccentArchetypesNoneCache # cannot cache based on beat form
# environLocal.printDebug('_setDefaultAccentWeights(): firstPartitionForm set to',
# firstPartitionForm, 'self.beatSequence: ', self.beatSequence, tsStr)
# using cacheKey speeds up TS creation from 2300 microseconds to 500microseconds
try:
self.accentSequence = copy.deepcopy(
_meterSequenceAccentArchetypes[cacheKey]
)
# environLocal.printDebug(['using stored accent archetype:'])
except KeyError:
# environLocal.printDebug(['creating a new accent archetype'])
ms = MeterSequence(tsStr)
# key operation here
# div count needs to be the number of top-level beat divisions
ms.subdivideNestedHierarchy(depth,
firstPartitionForm=firstPartitionForm)
# provide a partition for each flattened division
accentCount = len(ms.flatten())
# environLocal.printDebug(['got accentCount', accentCount, 'ms: ', ms])
divStep = self.barDuration.quarterLength / accentCount
weightInts = [0] * accentCount # weights as integer/depth counts
for i in range(accentCount):
ql = opFrac(i * divStep)
weightInts[i] = ms.offsetToDepth(ql, align='quantize', index=i)
maxInt = max(weightInts)
weightValues = {} # reference dictionary
# minimum value, something like 1/16, to be multiplied by powers of 2
weightValueMin = 1 / pow(2, maxInt - 1)
for x in range(maxInt):
# multiply base value (0.125) by 1, 2, 4
# there is never a 0 integer weight, so add 1 to dictionary
weightValues[x + 1] = weightValueMin * pow(2, x)
# set weights on accent partitions
self.accentSequence.partition([1] * accentCount)
for i in range(accentCount):
# get values from weightValues dictionary
self.accentSequence[i].weight = weightValues[weightInts[i]]
if cacheKey != _meterSequenceAccentArchetypesNoneCache:
_meterSequenceAccentArchetypes[cacheKey] = copy.deepcopy(self.accentSequence)
# --------------------------------------------------------------------------
# access data for other processing
[docs]
def getBeams(
self,
srcList: stream.Stream|t.Sequence[base.Music21Object],
measureStartOffset: OffsetQL = 0.0,
) -> list[beam.Beams|None]:
'''
Given a qLen position and an iterable of Music21Objects, return a list of Beams objects.
The iterable can be a list (of elements) or a Stream (preferably flat)
or a :class:`~music21.stream.iterator.StreamIterator` from which Durations
and information about note vs. rest will be
extracted.
Objects are assumed to be adjoining; offsets are not used, except for
measureStartOffset()
Must process a list/Stream at time, because we cannot tell when a beam ends
unless we see the context of adjoining durations.
>>> a = meter.TimeSignature('2/4', 2)
>>> a.beamSequence[0] = a.beamSequence[0].subdivide(2)
>>> a.beamSequence[1] = a.beamSequence[1].subdivide(2)
>>> a.beamSequence
<music21.meter.core.MeterSequence {{1/8+1/8}+{1/8+1/8}}>
>>> b = [note.Note(type='16th') for _ in range(8)]
>>> c = a.getBeams(b)
>>> len(c) == len(b)
True
>>> print(c)
[<music21.beam.Beams <music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/stop>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/start>>,
<music21.beam.Beams <music21.beam.Beam 1/stop>/<music21.beam.Beam 2/stop>>,
<music21.beam.Beams <music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/stop>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/start>>,
<music21.beam.Beams <music21.beam.Beam 1/stop>/<music21.beam.Beam 2/stop>>]
>>> a = meter.TimeSignature('6/8')
>>> b = [note.Note(type='eighth') for _ in range(6)]
>>> c = a.getBeams(b)
>>> print(c)
[<music21.beam.Beams <music21.beam.Beam 1/start>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>>,
<music21.beam.Beams <music21.beam.Beam 1/stop>>,
<music21.beam.Beams <music21.beam.Beam 1/start>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>>,
<music21.beam.Beams <music21.beam.Beam 1/stop>>]
>>> fourFour = meter.TimeSignature('4/4')
>>> nList = [note.Note(type=d) for d in ('eighth', 'quarter', 'eighth',
... 'eighth', 'quarter', 'eighth')]
>>> beamList = fourFour.getBeams(nList)
>>> print(beamList)
[None, None, None, None, None, None]
Pickup measure support included by taking in an additional measureStartOffset argument.
>>> twoTwo = meter.TimeSignature('2/2')
>>> nList = [note.Note(type='eighth') for _ in range(5)]
>>> beamList = twoTwo.getBeams(nList, measureStartOffset=1.5)
>>> print(beamList)
[None,
<music21.beam.Beams <music21.beam.Beam 1/start>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>>,
<music21.beam.Beams <music21.beam.Beam 1/continue>>,
<music21.beam.Beams <music21.beam.Beam 1/stop>>]
Fixed in v.7 -- incomplete final measures in 6/8:
>>> sixEight = meter.TimeSignature('6/8')
>>> nList = [note.Note(type='quarter'), note.Note(type='eighth'), note.Note(type='eighth')]
>>> beamList = sixEight.getBeams(nList)
>>> print(beamList)
[None, None, None]
And Measure objects with :attr:`~music21.stream.Measure.paddingRight` set:
>>> twoFour = meter.TimeSignature('2/4')
>>> m = stream.Measure([note.Note(type='eighth') for _ in range(3)])
>>> m.paddingRight = 0.5
>>> twoFour.getBeams(m)
[<music21.beam.Beams <music21.beam.Beam 1/start>>,
<music21.beam.Beams <music21.beam.Beam 1/stop>>,
None]
'''
from music21 import stream
if isinstance(srcList, stream.Stream):
srcStream = srcList
srcList = list(srcList) # do not change to [srcList]
elif srcList and isinstance(srcList[0], base.Music21Object):
# make into a stream to get proper offsets:
# for eventually removing measureStartOffset
srcStream = stream.Measure()
srcStream.append(srcList)
else:
return []
if len(srcList) <= 1:
return [None for _ in srcList]
beamsList = beam.Beams.naiveBeams(srcList) # hold maximum Beams objects, all with type None
beamsList = beam.Beams.removeSandwichedUnbeamables(beamsList)
def fixBeamsOneElementDepth(i: int, el: base.Music21Object, depth: int):
'''
Note that this can compute the beams for non-Note things like rests
they just cannot be applied to the object.
'''
beams = beamsList[i]
if beams is None:
return
beams = t.cast(beam.Beams, beams)
beamNumber = depth + 1
# see if there is a component defined for this beam number
# if not, continue
if beamNumber not in beams.getNumbers():
return
dur = el.duration
pos = el.offset + measureStartOffset
start = opFrac(pos)
end = opFrac(pos + dur.quarterLength)
startNext: OffsetQL = end
isLast = (i == len(srcList) - 1)
isFirst = (i == 0)
beamNext = beamsList[i + 1] if not isLast else None
beamPrevious = beamsList[i - 1] if not isFirst else None
# get an archetype of the MeterSequence for this level.
# level is the depth, starting at zero
archetype = self.beamSequence.getLevel(depth)
# span is the quarter note duration points for each partition
# at this level
archetypeSpanStart, archetypeSpanEnd = archetype.offsetToSpan(start)
# environLocal.printDebug(['at level, got archetype span', depth,
# archetypeSpan])
if beamNext is None: # last note or before a non-beamable note (half, whole, etc.)
archetypeSpanNextStart = 0.0
else:
archetypeSpanNextStart = archetype.offsetToSpan(startNext)[0]
# watch for a special case where a duration completely fills
# the archetype; this generally should not be beamed
# same if beamPrevious is None and beamNumber == 1 (quarter-eighth in 6/8)
if end == archetypeSpanEnd and (
start == archetypeSpanStart
or (beamPrevious is None and beamNumber == 1)
):
# increment position and continue loop
beamsList[i] = None # replace with None!
return
# determine beamType
# if first w/o pickup, always start
if isFirst and measureStartOffset == 0:
beamType = 'start'
# get a partial beam if we cannot continue this
if (beamNext is None
or beamNumber not in beamNext.getNumbers()):
beamType = 'partial-right'
# if last in complete measure or not in a measure, always stop
elif (isLast and (not isinstance(srcStream, stream.Measure)
or srcStream.paddingRight == 0.0)):
beamType = 'stop'
# get a partial beam if we cannot form a beam
if (beamPrevious is None
or beamNumber not in beamPrevious.getNumbers()):
# environLocal.warn(['triggering partial left where a stop normally falls'])
beamType = 'partial-left'
# here on we know that it is neither the first nor last
# if last beam was not defined, we need to either
# start or have a partial left beam
# or, if beam number was not active in last beams
elif beamPrevious is None or beamNumber not in beamPrevious.getNumbers():
if beamNumber == 1 and beamNext is None:
beamsList[i] = None
return
elif beamNext is None and beamNumber > 1:
beamType = 'partial-left'
elif startNext >= archetypeSpanEnd:
# case of where we need a partial left:
# if the next start value is outside this span (or at the
# greater boundary of this span), and we did not have a
# beam or beam number in the previous beam
# first note in pickup measures might also get 'partial-left'
# here, but this gets fixed in sanitize partial beams
# may need to check: beamNext is not None and
# beamNumber in beamNext.getNumbers()
# note: it is critical that we check archetypeSpan here
# not archetypeSpanNext
# environLocal.printDebug(['matching partial left'])
beamType = 'partial-left'
elif beamNext is None or beamNumber not in beamNext.getNumbers():
beamType = 'partial-right'
else:
beamType = 'start'
# last beams (beam?) was active, last beamNumber was active,
# and it was stopped or was a partial-left
elif (beamPrevious is not None
and beamNumber in beamPrevious.getNumbers()
and beamPrevious.getTypeByNumber(beamNumber) in ['stop', 'partial-left']):
if beamNext is not None:
beamType = 'start' if beamNumber in beamNext.getNumbers() else 'partial-right'
# last note had beams but stopped, next note cannot be beamed to
# was active, last beamNumber was active,
# and it was stopped or was a partial-left
else:
beamType = 'partial-left' # will be deleted later in the script
# if no beam is defined next (we know this already)
# then must stop
elif (beamNext is None
or beamNumber not in beamNext.getNumbers()):
beamType = 'stop'
# the last cases are when to stop, or when to continue
# when we know we have a beam next
# we continue if the next beam is in the same beaming archetype
# as this one.
# if endNext is outside the archetype span,
# not sure what to do
elif startNext < archetypeSpanEnd:
# environLocal.printDebug(['continue match: dur.type, startNext, archetypeSpan',
# dur.type, startNext, archetypeSpan])
beamType = 'continue'
# we stop if the next beam is not in the same beaming archetype
# and (as shown above) a valid beam number is not previous
elif startNext >= archetypeSpanNextStart:
beamType = 'stop'
else:
raise TimeSignatureException('cannot match beamType')
# debugging information displays:
# if beamPrevious is not None:
# environLocal.printDebug(['beamPrevious', beamPrevious,
# 'beamPrevious.getNumbers()', beamPrevious.getNumbers(),
# 'beamPrevious.getByNumber(beamNumber).type'])
#
# if beamNumber in beamPrevious.getNumbers():
# environLocal.printDebug(['beamPrevious type',
# beamPrevious.getByNumber(beamNumber).type])
# environLocal.printDebug(['beamNumber, start, archetypeSpan, beamType',
# beamNumber, start, dur.type, archetypeSpan, beamType])
beams.setByNumber(beamNumber, beamType)
# environLocal.printDebug(['beamsList', beamsList])
# iter over each Beam in beam.Beams, from top to bottom (1 through 5)
for outer_depth in range(len(beam.beamableDurationTypes)):
# increment to count from 1 not 0
# assume we are always starting at offset w/n this meter (Jose)
for outer_i, outer_el in enumerate(srcList):
fixBeamsOneElementDepth(outer_i, outer_el, outer_depth)
beamsList = beam.Beams.sanitizePartialBeams(beamsList)
beamsList = beam.Beams.mergeConnectingPartialBeams(beamsList)
return beamsList
[docs]
def setDisplay(self, value, partitionRequest=None) -> None:
'''
Set an independent display value for a meter.
>>> a = meter.TimeSignature()
>>> a.load('3/4')
>>> a.setDisplay('2/8+2/8+2/8')
>>> a.displaySequence
<music21.meter.core.MeterSequence {2/8+2/8+2/8}>
>>> a.beamSequence
<music21.meter.core.MeterSequence {{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}>
>>> a.beatSequence # a single top-level partition is default for beat
<music21.meter.core.MeterSequence {{1/8+1/8}+{1/8+1/8}+{1/8+1/8}}>
>>> a.setDisplay('3/4')
>>> a.displaySequence
<music21.meter.core.MeterSequence {3/4}>
'''
if isinstance(value, MeterSequence): # can set to an existing MeterSequence
# must make a copy
self.displaySequence = copy.deepcopy(value)
else:
# create a new object; it will not be linked
self.displaySequence = MeterSequence(value, partitionRequest)
[docs]
def getAccent(self, qLenPos: OffsetQL) -> bool:
'''
Return True or False if the qLenPos is at the start of an accent
division.
>>> a = meter.TimeSignature('3/4', 3)
>>> a.accentSequence.partition([2, 1])
>>> a.accentSequence
<music21.meter.core.MeterSequence {2/4+1/4}>
>>> a.getAccent(0.0)
True
>>> a.getAccent(1.0)
False
>>> a.getAccent(2.0)
True
'''
pos = 0
qLenPos = opFrac(qLenPos)
for i in range(len(self.accentSequence)):
if pos == qLenPos:
return True
pos += self.accentSequence[i].duration.quarterLength
return False
[docs]
def setAccentWeight(self,
weights: Sequence[float]|float,
level: int = 0) -> None:
'''
Set accent weight, or floating point scalars, for the accent MeterSequence.
Provide a list of float values; if this list is shorter than the length
of the MeterSequence, it will be looped; if this list is longer,
only the relevant values at the beginning will be used.
If the accent MeterSequence is subdivided, the level of depth to set is given by the
optional level argument.
>>> a = meter.TimeSignature('4/4', 4)
>>> len(a.accentSequence)
4
>>> a.setAccentWeight([0.8, 0.2])
>>> a.getAccentWeight(0.0)
0.8...
>>> a.getAccentWeight(0.5)
0.8...
>>> a.getAccentWeight(1.0)
0.2...
>>> a.getAccentWeight(2.5)
0.8...
>>> a.getAccentWeight(3.5)
0.2...
'''
weightList: Sequence[float]
if not isinstance(weights, Sequence):
weightList = [weights]
else:
weightList = weights
msLevel = self.accentSequence.getLevel(level)
for i in range(len(msLevel)):
msLevel[i].weight = weightList[i % len(weightList)]
[docs]
def averageBeatStrength(self, streamIn, notesOnly=True):
'''
returns a float of the average beat strength of all objects (or if notesOnly is True
[default] only the notes) in the `Stream` specified as streamIn.
>>> s = converter.parse('C4 D4 E8 F8', format='tinyNotation').flatten().notes.stream()
>>> sixEight = meter.TimeSignature('6/8')
>>> sixEight.averageBeatStrength(s)
0.4375
>>> threeFour = meter.TimeSignature('3/4')
>>> threeFour.averageBeatStrength(s)
0.5625
If `notesOnly` is `False` then test objects will give added
weight to the beginning of the measure:
>>> sixEight.averageBeatStrength(s, notesOnly=False)
0.4375
>>> s.insert(0.0, clef.TrebleClef())
>>> s.insert(0.0, clef.BassClef())
>>> sixEight.averageBeatStrength(s, notesOnly=False)
0.625
'''
if notesOnly is True:
streamIn = streamIn.notes
totalWeight = 0.0
totalObjects = len(streamIn)
if totalObjects == 0:
return 0.0 # or raise exception? add doc test
for el in streamIn:
elWeight = self.getAccentWeight(
self.getMeasureOffsetOrMeterModulusOffset(el),
forcePositionMatch=True, permitMeterModulus=False)
totalWeight += elWeight
return totalWeight / totalObjects
[docs]
def getMeasureOffsetOrMeterModulusOffset(self, el: base.Music21Object) -> OffsetQL:
'''
Return the measure offset based on a Measure, if it exists,
otherwise based on meter modulus of the TimeSignature.
>>> m = stream.Measure()
>>> ts1 = meter.TimeSignature('3/4')
>>> m.insert(0, ts1)
>>> n1 = note.Note()
>>> m.insert(2, n1)
>>> ts1.getMeasureOffsetOrMeterModulusOffset(n1)
2.0
Exceeding the range of the Measure gets a modulus
>>> n2 = note.Note()
>>> m.insert(4.0, n2)
>>> ts1.getMeasureOffsetOrMeterModulusOffset(n2)
1.0
Can be applied to Notes in a Stream with a TimeSignature.
>>> ts2 = meter.TimeSignature('5/4')
>>> s2 = stream.Stream()
>>> s2.insert(0, ts2)
>>> n3 = note.Note()
>>> s2.insert(3, n3)
>>> ts2.getMeasureOffsetOrMeterModulusOffset(n3)
3.0
>>> n4 = note.Note()
>>> s2.insert(5, n4)
>>> ts2.getMeasureOffsetOrMeterModulusOffset(n4)
0.0
'''
mOffset = el._getMeasureOffset() # TODO(msc): expose this method and remove private
tsMeasureOffset = self._getMeasureOffset(includeMeasurePadding=False)
if opFrac(mOffset + tsMeasureOffset) < self.barDuration.quarterLength:
return mOffset
else:
# must get offset relative to not just start of Stream, but the last
# time signature
post = opFrac((mOffset - tsMeasureOffset) % self.barDuration.quarterLength)
# environLocal.printDebug(['result', post])
return post
[docs]
def getAccentWeight(self, qLenPos, level=0, forcePositionMatch=False,
permitMeterModulus=False):
'''
Given a qLenPos, return an accent level. In general, accents are assumed to
define only a first-level weight.
If `forcePositionMatch` is True, an accent will only be returned if the
provided qLenPos is a near exact match to the provided quarter length. Otherwise,
half of the minimum quarter length will be provided.
If `permitMeterModulus` is True, quarter length positions greater than
the duration of the Meter will be accepted as the modulus of the total meter duration.
>>> ts1 = meter.TimeSignature('3/4')
>>> [ts1.getAccentWeight(x) for x in range(3)]
[1.0, 0.5, 0.5]
Calling getAccentWeight on durations beyond the score returns an error:
>>> [ts1.getAccentWeight(x) for x in range(6)]
Traceback (most recent call last):
music21.exceptions21.MeterException: cannot access from qLenPos 3.0
where total duration is 3.0
The error is removed if permitMeterModulus is employed:
>>> [ts1.getAccentWeight(x, permitMeterModulus=True) for x in range(6)]
[1.0, 0.5, 0.5, 1.0, 0.5, 0.5]
'''
qLenPos = opFrac(qLenPos)
# might store this weight every time it is set, rather than
# getting it here
minWeight = min(
[mt.weight for mt in self.accentSequence]) * 0.5
msLevel = self.accentSequence.getLevel(level)
if permitMeterModulus:
environLocal.printDebug(
[' self.duration.quarterLength', self.duration.quarterLength,
'self.barDuration.quarterLength', self.barDuration.quarterLength])
qLenPos = qLenPos % self.barDuration.quarterLength
if forcePositionMatch:
# only return values for qLen positions that are at the start
# of a span; for those that are not, we need to return a minWeight
localSpan = msLevel.offsetToSpan(qLenPos,
permitMeterModulus=permitMeterModulus)
if qLenPos != localSpan[0]:
return minWeight
return msLevel[msLevel.offsetToIndex(qLenPos)].weight
[docs]
def getBeat(self, offset):
'''
Given an offset (quarterLength position), get the beat, where beats count from 1
If you want a fractional number for the beat, see `getBeatProportion`.
TODO: In a future version -- getBeat will probably do what getBeatProportion does now,
but just with 1 added to it.
>>> a = meter.TimeSignature('3/4', 3)
>>> a.getBeat(0)
1
>>> a.getBeat(2.5)
3
>>> a.beatSequence.partition(['3/8', '3/8'])
>>> a.getBeat(2.5)
2
'''
return self.beatSequence.offsetToIndex(offset) + 1
[docs]
def getBeatOffsets(self):
'''
Return offset positions in a list for the start of each beat,
assuming this object is found at offset zero.
>>> a = meter.TimeSignature('3/4')
>>> a.getBeatOffsets()
[0.0, 1.0, 2.0]
>>> a = meter.TimeSignature('6/8')
>>> a.getBeatOffsets()
[0.0, 1.5]
'''
post = []
post.append(0.0)
if len(self.beatSequence) == 1:
return post
else:
endOffset = self.barDuration.quarterLength
o = 0.0
for ms in self.beatSequence:
o = opFrac(o + ms.duration.quarterLength)
if o >= endOffset:
return post # do not add offset for end of bar
post.append(o)
[docs]
def getBeatDuration(self, qLenPos):
'''
Returns a :class:`~music21.duration.Duration`
object representing the length of the beat
found at qLenPos. For most standard
meters, you can give qLenPos = 0
and get the length of any beat in
the TimeSignature; but the simpler
:attr:`music21.meter.TimeSignature.beatDuration` parameter,
will do that for you just as well.
The advantage of this method is that
it will work for asymmetrical meters, as the second
example shows.
Ex. 1: beat duration for 3/4 is always 1.0
no matter where in the meter you query.
>>> ts1 = meter.TimeSignature('3/4')
>>> ts1.getBeatDuration(0.5)
<music21.duration.Duration 1.0>
>>> ts1.getBeatDuration(2.5)
<music21.duration.Duration 1.0>
Ex. 2: same for 6/8:
>>> ts2 = meter.TimeSignature('6/8')
>>> ts2.getBeatDuration(2.5)
<music21.duration.Duration 1.5>
Ex. 3: but for a compound meter of 3/8 + 2/8,
where you ask for the beat duration
will determine the length of the beat:
>>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat
>>> ts3.getBeatDuration(0.5)
<music21.duration.Duration 1.5>
>>> ts3.getBeatDuration(1.5)
<music21.duration.Duration 1.0>
'''
return self.beatSequence[self.beatSequence.offsetToIndex(qLenPos)].duration
[docs]
def getOffsetFromBeat(self, beat):
'''
Given a beat value, convert into an offset position.
>>> ts1 = meter.TimeSignature('3/4')
>>> ts1.getOffsetFromBeat(1)
0.0
>>> ts1.getOffsetFromBeat(2)
1.0
>>> ts1.getOffsetFromBeat(3)
2.0
>>> ts1.getOffsetFromBeat(3.5)
2.5
>>> ts1.getOffsetFromBeat(3.25)
2.25
Get the offset from beat 8/3 (2.6666): give a Fraction, get a Fraction.
>>> from fractions import Fraction
>>> ts1.getOffsetFromBeat(Fraction(8, 3))
Fraction(5, 3)
>>> ts1 = meter.TimeSignature('6/8')
>>> ts1.getOffsetFromBeat(1)
0.0
>>> ts1.getOffsetFromBeat(2)
1.5
Check that 2.5 is 2.5 + (0.5 * 1.5):
>>> ts1.getOffsetFromBeat(2.5)
2.25
Decimals only need to be pretty close to work.
(But Fractions are better as demonstrated above)
>>> ts1.getOffsetFromBeat(2.33)
2.0
>>> ts1.getOffsetFromBeat(2.66)
2.5
Works for asymmetrical meters as well:
>>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat
>>> ts3.getOffsetFromBeat(1)
0.0
>>> ts3.getOffsetFromBeat(2)
1.5
>>> ts3.getOffsetFromBeat(1.66)
1.0
>>> ts3.getOffsetFromBeat(2.5)
2.0
Let's try this on a real piece, a 4/4 chorale with a one beat pickup. Here we get the
normal offset for beat 4 from the active TimeSignature, but we subtract out
the pickup length which is in a `Measure`'s :attr:`~music21.stream.Measure.paddingLeft`
property, and thus see the distance from the beginning of the measure to beat 4 in
quarter notes
>>> c = corpus.parse('bwv1.6')
>>> for m in c.parts.first().getElementsByClass(stream.Measure):
... ts = m.timeSignature or m.getContextByClass(meter.TimeSignature)
... print(m.number, ts.getOffsetFromBeat(4.0) - m.paddingLeft)
0 0.0
1 3.0
2 3.0
...
'''
# divide into integer and floating point components
beatInt, beatFraction = divmod(beat, 1)
beatInt = int(beatInt) # convert to integer
# resolve 0.33 to 0.3333333 (actually Fraction(1, 3). )
beatFraction = common.addFloatPrecision(beatFraction)
if beatInt - 1 > len(self.beatSequence) - 1:
raise TimeSignatureException(
f'requested beat value ({beatInt}) not found in beat partitions '
f'({self.beatSequence}) of ts {self}'
)
# get a duration object for the beat; will translate into quarterLength
# beat int counts from 1; subtract 1 to get index
beatDur = self.beatSequence[beatInt - 1].duration
oStart, unused_oEnd = self.beatSequence.getLevelSpan()[beatInt - 1]
post = opFrac(oStart + (beatDur.quarterLength * beatFraction))
return post
[docs]
def getBeatProgress(self, qLenPos):
'''
Given a quarterLength position, get the beat,
where beats count from 1, and return the
amount of qLen into this beat the supplied qLenPos
is.
>>> a = meter.TimeSignature('3/4', 3)
>>> a.getBeatProgress(0)
(1, 0.0)
>>> a.getBeatProgress(0.75)
(1, 0.75)
>>> a.getBeatProgress(1.0)
(2, 0.0)
>>> a.getBeatProgress(2.5)
(3, 0.5)
Works for specifically partitioned meters too:
>>> a.beatSequence.partition(['3/8', '3/8'])
>>> a.getBeatProgress(2.5)
(2, 1.0)
'''
beatIndex = self.beatSequence.offsetToIndex(qLenPos)
start, unused_end = self.beatSequence.offsetToSpan(qLenPos)
return beatIndex + 1, opFrac(qLenPos - start)
[docs]
def getBeatProportion(self, qLenPos):
'''
Given a quarter length position into the meter, return the numerical progress
through the beat (where beats count from one) with a floating-point or fractional value
between 0 and 1 appended to this value that gives the proportional progress into the beat.
For faster, integer values, use simply `.getBeat()`
>>> ts1 = meter.TimeSignature('3/4')
>>> ts1.getBeatProportion(0.0)
1.0
>>> ts1.getBeatProportion(0.5)
1.5
>>> ts1.getBeatProportion(1.0)
2.0
>>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat
>>> ts3.getBeatProportion(0.75)
1.5
>>> ts3.getBeatProportion(2.0)
2.5
'''
beatIndex = self.beatSequence.offsetToIndex(qLenPos)
start, end = self.beatSequence.offsetToSpan(qLenPos)
totalRange = end - start
progress = qLenPos - start # how far in QL
return opFrac(beatIndex + 1 + (progress / totalRange))
[docs]
def getBeatProportionStr(self, qLenPos):
'''
Return a string presentation of the beat.
>>> ts1 = meter.TimeSignature('3/4')
>>> ts1.getBeatProportionStr(0.0)
'1'
>>> ts1.getBeatProportionStr(0.5)
'1 1/2'
>>> ts1.getBeatProportionStr(1.0)
'2'
>>> ts3 = meter.TimeSignature('3/8+2/8') # will partition as 2 beat
>>> ts3.getBeatProportionStr(0.75)
'1 1/2'
>>> ts3.getBeatProportionStr(2)
'2 1/2'
>>> ts4 = meter.TimeSignature('6/8') # will partition as 2 beat
'''
beatIndex = int(self.beatSequence.offsetToIndex(qLenPos))
start, end = self.beatSequence.offsetToSpan(qLenPos)
totalRange = end - start
progress = qLenPos - start # how far in QL
if (progress / totalRange) == 0.0:
post = f'{beatIndex + 1}' # just show beat
else:
a, b = proportionToFraction(progress / totalRange)
post = f'{beatIndex + 1} {a}/{b}' # just show beat
return post
[docs]
def getBeatDepth(self, qLenPos, align='quantize'):
'''
Return the number of levels of beat partitioning given a QL into the TimeSignature.
Note that by default beat partitioning always has a single, top-level partition.
The `align` parameter is passed to the :meth:`~music21.meter.MeterSequence.offsetToDepth`
method, and can be used to find depths based on start position overlaps.
>>> a = meter.TimeSignature('3/4', 3)
>>> a.getBeatDepth(0)
1
>>> a.getBeatDepth(1)
1
>>> a.getBeatDepth(2)
1
>>> b = meter.TimeSignature('3/4', 1)
>>> b.beatSequence[0] = b.beatSequence[0].subdivide(3)
>>> b.beatSequence[0][0] = b.beatSequence[0][0].subdivide(2)
>>> b.beatSequence[0][1] = b.beatSequence[0][1].subdivide(2)
>>> b.beatSequence[0][2] = b.beatSequence[0][2].subdivide(2)
>>> b.getBeatDepth(0)
3
>>> b.getBeatDepth(0.5)
1
>>> b.getBeatDepth(1)
2
'''
return self.beatSequence.offsetToDepth(qLenPos, align)
# -----------------------------------------------------------------------------
[docs]
class SenzaMisuraTimeSignature(TimeSignatureBase):
'''
A SenzaMisuraTimeSignature represents the absence of a TimeSignature
It is NOT a TimeSignature subclass, only because it has none of the attributes
of a TimeSignature.
>>> smts = meter.SenzaMisuraTimeSignature('0')
>>> smts.text
'0'
>>> smts
<music21.meter.SenzaMisuraTimeSignature 0>
'''
def __init__(self, text=None):
super().__init__()
self.text = text
def _reprInternal(self):
if self.text is None:
return ''
else:
return str(self.text)
# TODO: Implement or delete
# class NonPowerOfTwoTimeSignature(TimeSignature):
# pass
# class AutoAdjustTimeSignature(TimeSignature):
# automatically adjusts to fit its measure context.
class Test(unittest.TestCase):
'''
All other tests moved to meter.tests
'''
def testCopyAndDeepcopy(self):
from music21.test.commonTest import testCopyAll
testCopyAll(self, globals())
# -----------------------------------------------------------------------------
# define presented order in documentation
_DOC_ORDER = [TimeSignature]
if __name__ == '__main__':
import music21
music21.mainTest()