MetricalDuration

MetricalDuration(
    numerator: int,
    denominator: int,
    dots: int = 0,
    ratio: TemporalRatio | None = None,
)

The duration of notes, rests, or other temporal musical items as understood and notated in Western Standard Music Notation.

Duration values are stored as nominal values, so a dotted quarter note is stored as numerator 1, denominator 4, dots 1. Actual temporal values (3/8) are calculated when queried.

Notes longer than a whole note (breve, longa, maxima) are stored with a power-of-two numerator and a denominator of 1, so a breve is (2, 1) and a dotted longa is (4, 1, dots=1).

Tuplet values are handled by storing a TemporalRatio, which is (n, TemporalElements) against (m, TemporalElements).

A quarter note inside a standard quarter-note triplet would then be:

>>> from openmusickit.values.time.duration import TemporalRatio, TemporalUnit
>>> quarter = MetricalDuration(1, 4)
>>> triplet = TemporalRatio(TemporalUnit(3, quarter), TemporalUnit(2, quarter))
>>> MetricalDuration(1, 4, ratio=triplet).rational_length
Fraction(1, 6)

MetricalDurations compare, sort, and hash by their real length, so MetricalDuration(3, 8) == MetricalDuration(1, 4, dots=1).

A negative numerator is a negative duration: the same notated value in the opposite direction, produced by -duration or by subtraction. It is a signed quantity for arithmetic (an edge displacement, for instance), never the length of an event.

>>> quarter - MetricalDuration(1, 2)
MetricalDuration(-1, 4)

Attributes

Name Description
__slots__
numerator
denominator
dots
ratio
temporal_system
nominal_length The notated value, including dots but ignoring any tuplet ratio.
rational_length The real length: the notated value times the tuplet ratio, if any.

Methods

Name Description
__eq__
__lt__ Shorter than other, by rational length, within compatible systems.
__hash__
__sub__ self + (-other): the difference of two durations, in whatever notation it takes.
from_fraction Create a MetricalDuration from its full nominal value (e.g. 3/8 -> dotted quarter).
from_length Resolve any positive rational length into notation:
scale Returns this duration augmented or diminished by scalar.
__neg__ The same notated value in the opposite direction.
__add__ Add two durations.
__radd__ Lets sum(durations) work from its default start value of 0.
__repr__

__eq__

__eq__(other)

__lt__

__lt__(other)

Shorter than other, by rational length, within compatible systems.

>>> from openmusickit.systems.wsmn.temporal.symbols import quarter, dotted_quarter
>>> quarter < dotted_quarter, dotted_quarter < quarter
(True, False)

__hash__

__hash__()

__sub__

__sub__(other)

self + (-other): the difference of two durations, in whatever notation it takes.

>>> from openmusickit.systems.wsmn.temporal.symbols import half, quarter
>>> half - quarter, quarter - half
(MetricalDuration(1, 4), MetricalDuration(-1, 4))

from_fraction

from_fraction(value: Rational, *, ratio: TemporalRatio | None = None)

Create a MetricalDuration from its full nominal value (e.g. 3/8 -> dotted quarter).

>>> MetricalDuration.from_fraction(F(3, 8))
MetricalDuration(1, 4, dots=1)
>>> MetricalDuration.from_fraction(F(5, 8))
Traceback (most recent call last):
...
ValueError: 5/8 is not a single notatable duration. ...

Raises

Name Type Description
ValueError if the value is not a single notatable symbol (use from_length for any length).

from_length

from_length(length: Rational, *, ratio: TemporalRatio | None = None)

Resolve any positive rational length into notation: a single MetricalDuration where one exists, a tuplet member where the length needs one, and a TiedDuration where it needs a tie.

This is the front door for lengths that arrive as numbers rather than as notation: quantized MIDI or audio, dividing a bar into n equal parts, scaling by 3, and so on. It always succeeds for a positive rational, and it always picks the canonical spelling described below, so the result is predictable but not necessarily the one a composer would choose in a particular metrical context (see “When not to use”).

Examples

>>> from fractions import Fraction as F
>>> from openmusickit.systems.wsmn.temporal.symbols import *

A notatable length comes back as one symbol (equivalent to from_fraction):

>>> MetricalDuration.from_length(F(3, 8))
MetricalDuration(1, 4, dots=1)
>>> MetricalDuration.from_length(2)
MetricalDuration(2, 1)

An odd factor in the denominator means a tuplet. 1/3 is a half note in a half-note triplet (3 halves in the time of 2):

>>> third = MetricalDuration.from_length(F(1, 3))
>>> third
MetricalDuration(1, 2, ratio=TemporalRatio(TemporalUnit(3, MetricalDuration(1, 2)), TemporalUnit(2, MetricalDuration(1, 2))))
>>> third.rational_length
Fraction(1, 3)
>>> third == half_in_triplet
True

1/6 is a quarter in a quarter-note triplet, 1/5 a quarter in a 5:4 quintuplet, 1/7 a quarter in a 7:4 septuplet, 1/9 an eighth in a 9:8 tuplet:

>>> MetricalDuration.from_length(F(1, 6)) == quarter_in_triplet
True
>>> [MetricalDuration.from_length(F(1, k)).ratio.multiplier for k in (5, 7, 9)]
[Fraction(4, 5), Fraction(4, 7), Fraction(8, 9)]

Lengths that need a tie come back as a TiedDuration, split greedily largest-first (dotted values included):

>>> MetricalDuration.from_length(F(5, 8))
TiedDuration([MetricalDuration(1, 2), MetricalDuration(1, 8)])
>>> MetricalDuration.from_length(F(13, 16))
TiedDuration([MetricalDuration(1, 2, dots=1), MetricalDuration(1, 16)])
>>> MetricalDuration.from_length(F(5, 8)).rational_length
Fraction(5, 8)

Ties and tuplets combine: 5/24 is a tied quarter + sixteenth inside a triplet.

>>> tied = MetricalDuration.from_length(F(5, 24))
>>> [m.nominal_length for m in tied], tied[0].ratio.multiplier, tied.rational_length
([Fraction(1, 4), Fraction(1, 16)], Fraction(2, 3), Fraction(5, 24))

With ratio, length is the notated value inside an existing tuplet (as in the constructor), so no new ratio is synthesized unless that notated value itself needs one (which produces a nested tuplet):

>>> MetricalDuration.from_length(F(1, 4), ratio=triplet(quarter)) == quarter_in_triplet
True
>>> nested = MetricalDuration.from_length(F(1, 12), ratio=triplet(quarter))
>>> nested.rational_length
Fraction(1, 18)

Dividing a bar of 4/4 into five equal parts:

>>> part = MetricalDuration.from_length(four_four.rational_length / 5)
>>> part.rational_length
Fraction(1, 5)
>>> part.ratio.multiplier
Fraction(4, 5)
>>> TemporalUnit(5, part) == four_four
True

Canonical spelling

  1. Reduce the fraction. If it is a single symbol (n = 2^k over 1, or n = 2^(dots+1) - 1 over a power of two), return it.
  2. If the denominator has an odd factor k, the length is a tuplet member: k in the time of c, where c is the largest power of two below k (3:2, 5:4, 7:4, 9:8, 11:8, …). The ratio’s unit is the largest plain note value that fits the notated length, and the notated length is then resolved by rule 1 or 3.
  3. Otherwise split largest-first into single symbols and tie them.

When not to use

  • You already know the notation. MetricalDuration(1, 4, dots=1) says exactly what is written; from_length(F(3, 8)) merely happens to agree. Prefer the constructor (or from_fraction, which raises if the value is not one symbol) whenever the spelling is known.

  • The tuplet already exists. Members of a tuplet should share the group’s TemporalRatio; pass it as ratio rather than letting this method invent a ratio that nothing else references.

  • Metrical context matters. The greedy tie split (5/8 = half + eighth) and the simple-meter tuplet convention (7:4 rather than 7:6) ignore the time signature and beat position, and a single symbol always wins over a tuplet: a bar of 6/8 divided by four gives dotted eighths, where an engraver would write a quadruplet. Notation-quality spelling is a job for a meter-aware layer built on top of this.

    >>> MetricalDuration.from_length(six_eight.rational_length / 4)
    MetricalDuration(1, 8, dots=1)
  • Floats. Pass Fraction or int. A float is converted exactly, and binary floats have power-of-two denominators, so from_length(1/3) is not a triplet but a chain of 27 tied notes ending in a 1/18014398509481984th. Quantize first, e.g. Fraction(x).limit_denominator(64).

Raises

Name Type Description
ValueError If length is not positive (use ZeroDuration for zero).

scale

scale(scalar: int | F)

Returns this duration augmented or diminished by scalar.

A power of two keeps the same spelling (a dotted eighth doubled is a dotted quarter). Any other positive scalar is resolved with from_length on the notated value, so the result may be a different single symbol (quarter * 3 = dotted half), a tuplet member (quarter * 2/3 = triplet quarter), or a TiedDuration (quarter * 5 = whole tied to quarter). An existing tuplet ratio is kept.

>>> from openmusickit.systems.wsmn.temporal.symbols import quarter, dotted_eighth, quarter_in_triplet
>>> quarter.scale(2), dotted_eighth.scale(2)
(MetricalDuration(1, 2), MetricalDuration(1, 4, dots=1))
>>> quarter.scale(3)
MetricalDuration(1, 2, dots=1)
>>> quarter.scale(F(2, 3)) == quarter_in_triplet
True
>>> quarter.scale(5)
TiedDuration([MetricalDuration(1, 1), MetricalDuration(1, 4)])

Raises

Name Type Description
ScalingError if scalar is not a positive rational.

__neg__

__neg__()

The same notated value in the opposite direction.

>>> from openmusickit.systems.wsmn.temporal.symbols import quarter
>>> -quarter, (-quarter).rational_length
(MetricalDuration(-1, 4), Fraction(-1, 4))

__add__

__add__(other)

Add two durations.

Returns a single MetricalDuration when the sum is notatable as one symbol (quarter + eighth = dotted quarter), otherwise a TiedDuration. Durations in different tuplets always produce a TiedDuration. Durations of opposite sign are resolved by length, so the result is the canonical spelling of the difference (from_length), or ZeroDuration when they cancel.

>>> from openmusickit.systems.wsmn.temporal.symbols import quarter, eighth, half, quarter_in_triplet
>>> quarter + eighth
MetricalDuration(1, 4, dots=1)
>>> half + eighth
TiedDuration([MetricalDuration(1, 2), MetricalDuration(1, 8)])
>>> len(quarter_in_triplet + quarter)  # different tuplets never merge
2
>>> half + (-eighth), quarter + (-quarter)
(MetricalDuration(1, 4, dots=1), ZeroDuration())

A tie on the right is folded in member by member, so the sum is the same whichever side the tie is on:

>>> quarter + (half + eighth), (half + eighth) + quarter
(MetricalDuration(1, 2, dots=2), MetricalDuration(1, 2, dots=2))

Raises

Name Type Description
TemporalCompatibilityError if other is a Duration of another temporal system (clock time).
>>> from openmusickit.values.time.clock_time import ClockDuration
>>> quarter + ClockDuration(5)
Traceback (most recent call last):
…
openmusickit.errors.TemporalCompatibilityError: Cannot add a ClockDuration to a MetricalDuration: Clock time is not Western Standard Music Notation.

__radd__

__radd__(other)

Lets sum(durations) work from its default start value of 0.

>>> from openmusickit.systems.wsmn.temporal.symbols import quarter, eighth
>>> sum([quarter, eighth, eighth])
MetricalDuration(1, 2)

__repr__

__repr__()