"""
Duration, offset, ratio.
"""
from __future__ import annotations
import dataclasses
import fractions
import functools
import math
import re
import typing
from . import exceptions as _exceptions
from . import math as _math
[docs]
def is_fraction(argument: object) -> bool:
"""
Is true when ``argument`` is fraction (but not duration).
.. container:: example
>>> abjad.duration.is_fraction(abjad.Fraction(1, 4))
True
>>> abjad.duration.is_fraction(abjad.Offset(abjad.Fraction(1, 4)))
False
>>> abjad.duration.is_fraction((1, 4))
False
>>> abjad.duration.is_fraction(0)
False
"""
return type(argument).__name__ == "Fraction"
[docs]
def is_offset(argument: object) -> bool:
"""
Is true when ``argument`` is offset.
.. container:: example
>>> abjad.duration.is_offset(abjad.Fraction(1, 4))
False
>>> abjad.duration.is_offset(abjad.Offset(abjad.Fraction(1, 4)))
True
>>> abjad.duration.is_offset((1, 4))
False
>>> abjad.duration.is_offset(0)
False
"""
return type(argument).__name__ == "Offset"
[docs]
def offset(n: int, d: int | None = None) -> Offset:
"""
Makes offset from ``n``.
.. container:: example
>>> abjad.duration.offset(1)
Offset(Fraction(1, 1))
"""
assert isinstance(n, int), repr(n)
if d is not None:
assert isinstance(d, int), repr(d)
fraction = fractions.Fraction(n, d)
return Offset(fraction)
[docs]
def pair_with_denominator(
fraction: fractions.Fraction, denominator: int
) -> tuple[int, int]:
"""
Spells ``fraction`` as pair with ``denominator``.
.. container:: example
>>> for numerator in range(12):
... fraction = abjad.Fraction(numerator, 6)
... pair = abjad.duration.pair_with_denominator(fraction, 6)
... print(fraction, pair)
...
0 (0, 6)
1/6 (1, 6)
1/3 (2, 6)
1/2 (3, 6)
2/3 (4, 6)
5/6 (5, 6)
1 (6, 6)
7/6 (7, 6)
4/3 (8, 6)
3/2 (9, 6)
5/3 (10, 6)
11/6 (11, 6)
>>> for numerator in range(12):
... fraction = abjad.Fraction(numerator, 6)
... pair = abjad.duration.pair_with_denominator(fraction, 8)
... print(fraction, pair)
...
0 (0, 8)
1/6 (1, 6)
1/3 (1, 3)
1/2 (4, 8)
2/3 (2, 3)
5/6 (5, 6)
1 (8, 8)
7/6 (7, 6)
4/3 (4, 3)
3/2 (12, 8)
5/3 (5, 3)
11/6 (11, 6)
>>> for numerator in range(12):
... fraction = abjad.Fraction(numerator, 6)
... pair = abjad.duration.pair_with_denominator(fraction, 12)
... print(fraction, pair)
...
0 (0, 12)
1/6 (2, 12)
1/3 (4, 12)
1/2 (6, 12)
2/3 (8, 12)
5/6 (10, 12)
1 (12, 12)
7/6 (14, 12)
4/3 (16, 12)
3/2 (18, 12)
5/3 (20, 12)
11/6 (22, 12)
"""
assert isinstance(fraction, fractions.Fraction), repr(fraction)
current_numerator = fraction.numerator
current_denominator = fraction.denominator
multiplier = fractions.Fraction(denominator, current_denominator)
new_numerator = multiplier * current_numerator
new_denominator = multiplier * current_denominator
if new_numerator.denominator == 1 and new_denominator.denominator == 1:
pair = (new_numerator.numerator, new_denominator.numerator)
else:
pair = (current_numerator, current_denominator)
return pair
[docs]
def durations(items: list) -> list[Duration]:
"""
Changes ``items`` to durations.
.. container:: example
>>> items = [(15, 8), (3, 8), abjad.TimeSignature((3, 4))]
>>> abjad.duration.durations(items)
[Duration(numerator=15, denominator=8), Duration(numerator=3, denominator=8), Duration(numerator=3, denominator=4)]
"""
durations = []
for item in items:
if isinstance(item, tuple):
duration = Duration(*item)
elif callable(getattr(item, "duration", None)):
duration = item.duration()
assert isinstance(duration, Duration), repr(duration)
elif callable(getattr(item, "as_integer_ratio", None)):
duration = Duration(*item.as_integer_ratio())
else:
duration = Duration(item)
durations.append(duration)
return durations
[docs]
@functools.total_ordering
@dataclasses.dataclass(frozen=True, slots=True, unsafe_hash=True)
class Duration:
"""
Value duration.
.. container:: example
>>> abjad.Duration(2, 8)
Duration(numerator=1, denominator=4)
"""
numerator: int
denominator: int = 1
def __post_init__(self) -> None:
assert self.denominator != 0, repr(self.denominator)
numerator, denominator = self.numerator, self.denominator
if denominator < 0:
numerator, denominator = -numerator, -denominator
if numerator == 0:
numerator, denominator = 0, 1
else:
g = math.gcd(numerator, denominator)
numerator //= g
denominator //= g
object.__setattr__(self, "numerator", numerator)
object.__setattr__(self, "denominator", denominator)
def __abs__(self) -> Duration:
numerator, denominator = abs(self.numerator), self.denominator
return Duration(numerator, denominator)
def __add__(self, other: int | Duration) -> Duration:
if isinstance(other, int):
other = Duration(other)
if isinstance(other, Duration):
a, b = self.numerator, self.denominator
c, d = other.numerator, other.denominator
return Duration(a * d + c * b, b * d)
return NotImplemented
def __eq__(self, other: object) -> bool:
if isinstance(other, Duration):
return self.as_integer_ratio() == other.as_integer_ratio()
return NotImplemented
def __float__(self) -> float:
return float(fractions.Fraction(self.numerator, self.denominator))
def __lt__(self, other: object) -> bool:
if not isinstance(other, Duration):
return NotImplemented
return self.numerator * other.denominator < other.numerator * self.denominator
def __neg__(self) -> Duration:
numerator, denominator = self.numerator, self.denominator
return Duration(-numerator, denominator)
def __radd__(self, other: int | Duration) -> Duration:
return self.__add__(other)
def __rmul__(self, other: int | fractions.Fraction) -> Duration:
if isinstance(other, int):
other = fractions.Fraction(other, 1)
if isinstance(other, fractions.Fraction):
a, b = self.numerator, self.denominator
c, d = other.numerator, other.denominator
return Duration(a * c, b * d)
return NotImplemented
def __sub__(self, other: Duration) -> Duration:
if isinstance(other, Duration):
a, b = self.numerator, self.denominator
c, d = other.numerator, other.denominator
return Duration(a * d - c * b, b * d)
return NotImplemented
@typing.overload
def __truediv__(self, other: Duration) -> fractions.Fraction:
pass
@typing.overload
def __truediv__(self, other: int | fractions.Fraction) -> Duration:
pass
def __truediv__(self, other):
if isinstance(other, int):
other = fractions.Fraction(other, 1)
if isinstance(other, Duration):
a, b = self.numerator, self.denominator
c, d = other.numerator, other.denominator
return fractions.Fraction(a * d, b * c)
if isinstance(other, fractions.Fraction):
a, b = self.numerator, self.denominator
c, d = other.numerator, other.denominator
return Duration(a * d, b * c)
return NotImplemented
[docs]
def as_fraction(self) -> fractions.Fraction:
"""
Returns value duration as fraction.
.. container:: example
>>> abjad.Duration(1, 4).as_fraction()
Fraction(1, 4)
"""
return fractions.Fraction(*self.as_integer_ratio())
[docs]
def as_integer_ratio(self) -> tuple[int, int]:
"""
Changes duration to (numerator, denominator) pair.
.. container:: example
>>> abjad.Duration(1, 4).as_integer_ratio()
(1, 4)
"""
return self.numerator, self.denominator
[docs]
def dot_count(self) -> int:
r"""
Gets dot count.
.. container:: example
Dot count defined equal to number of dots required to notate
duration. Raises assignability error when duration is not
assignable.
>>> for n in range(1, 16 + 1):
... duration = abjad.Duration(n, 16)
... fraction = duration.as_fraction()
... sixteenths = abjad.duration.pair_with_denominator(fraction, 16)
... numerator, denominator = sixteenths
... try:
... dot_count = duration.dot_count()
... string = f"{numerator}/{denominator}\t{dot_count}"
... print(string)
... except abjad.AssignabilityError:
... print(f"{numerator}/{denominator}\t--")
...
1/16 0
2/16 0
3/16 1
4/16 0
5/16 --
6/16 1
7/16 2
8/16 0
9/16 --
10/16 --
11/16 --
12/16 1
13/16 --
14/16 2
15/16 3
16/16 0
"""
if not self.is_assignable():
raise _exceptions.AssignabilityError
binary_string = _math.integer_to_binary_string(self.numerator)
digit_sum = sum([int(x) for x in list(binary_string)])
dot_count = digit_sum - 1
return dot_count
[docs]
def exponent(self) -> int:
r"""
Gets base-2 exponent.
.. container:: example
>>> for numerator in range(1, 16 + 1):
... duration = abjad.Duration(numerator, 16)
... fraction = duration.as_fraction()
... exponent = duration.exponent()
... sixteenths = abjad.duration.pair_with_denominator(fraction, 16)
... numerator, denominator = sixteenths
... print(f"{numerator}/{denominator}\t{exponent!s}")
...
1/16 4
2/16 3
3/16 3
4/16 2
5/16 2
6/16 2
7/16 2
8/16 1
9/16 1
10/16 1
11/16 1
12/16 1
13/16 1
14/16 1
15/16 1
16/16 0
"""
return -int(math.floor(math.log(self, 2)))
[docs]
def flag_count(self) -> int:
r"""
Gets flag count.
.. container:: example
Flag count defined equal to number of flags required to notate
duration.
>>> for n in range(1, 16 + 1):
... duration = abjad.Duration(n, 64)
... fraction = duration.as_fraction()
... sixty_fourths = abjad.duration.pair_with_denominator(fraction, 64)
... numerator, denominator = sixty_fourths
... flag_count = duration.flag_count()
... print(f"{numerator}/{denominator}\t{flag_count}")
...
1/64 4
2/64 3
3/64 3
4/64 2
5/64 2
6/64 2
7/64 2
8/64 1
9/64 1
10/64 1
11/64 1
12/64 1
13/64 1
14/64 1
15/64 1
16/64 0
"""
log = math.log(float(self.numerator) / self.denominator, 2)
count = -int(math.floor(log)) - 2
return max(count, 0)
@staticmethod
def from_clock_string(clock_string) -> Duration:
"""
Initializes duration (in seconds) from ``clock_string``.
.. container:: example
>>> abjad.Duration.from_clock_string("0'00''")
Duration(numerator=0, denominator=1)
>>> abjad.Duration.from_clock_string("0'59''")
Duration(numerator=59, denominator=1)
>>> abjad.Duration.from_clock_string("1'00''")
Duration(numerator=60, denominator=1)
>>> abjad.Duration.from_clock_string("1'17''")
Duration(numerator=77, denominator=1)
"""
minutes = 0
if "'" in clock_string:
tick_index = clock_string.find("'")
minutes = clock_string[:tick_index]
minutes = int(minutes)
seconds = clock_string[-4:-2]
seconds = int(seconds)
seconds = 60 * minutes + seconds
return Duration(seconds)
@staticmethod
def from_dot_count(dot_count: int) -> Duration:
"""
Initializes duration from ``dot_count``.
.. container::
>>> abjad.Duration.from_dot_count(2)
Duration(numerator=7, denominator=4)
"""
assert isinstance(dot_count, int), repr(dot_count)
assert 0 <= dot_count, repr(dot_count)
denominator = 2**dot_count
numerator = 2 ** (dot_count + 1) - 1
duration = Duration(numerator, denominator)
return duration
@staticmethod
def from_lilypond_duration_string(lilypond_duration_string: str) -> Duration:
"""
Initializes duration from LilyPond duration string.
.. container:: example
>>> abjad.Duration.from_lilypond_duration_string("8.")
Duration(numerator=3, denominator=16)
"""
assert isinstance(lilypond_duration_string, str), repr(lilypond_duration_string)
numeric_body_strings = [str(2**n) for n in range(8)]
other_body_strings = [r"\\breve", r"\\longa", r"\\maxima"]
body_strings = numeric_body_strings + other_body_strings
body_string = "|".join(body_strings)
pattern = r"^(%s)(\.*)$" % body_string
match = re.match(pattern, lilypond_duration_string)
if match is None:
message = f"incorrect duration string format: {lilypond_duration_string!r}."
raise TypeError(message)
body_string, dots_string = match.groups()
try:
body_denominator = int(body_string)
body_duration = fractions.Fraction(1, body_denominator)
except ValueError:
if body_string == r"\breve":
body_duration = fractions.Fraction(2)
elif body_string == r"\longa":
body_duration = fractions.Fraction(4)
elif body_string == r"\maxima":
body_duration = fractions.Fraction(8)
else:
raise ValueError(f"unknown body string: {body_string!r}.")
rational = body_duration
for n in range(len(dots_string)):
exponent = n + 1
denominator = 2**exponent
multiplier = fractions.Fraction(1, denominator)
addend = multiplier * body_duration
rational += addend
return Duration(*rational.as_integer_ratio())
[docs]
def is_assignable(self) -> bool:
r"""
Is true when duration is assignable.
.. container:: example
>>> for numerator in range(0, 16 + 1):
... duration = abjad.Duration(numerator, 16)
... fraction = duration.as_fraction()
... sixteenths = abjad.duration.pair_with_denominator(fraction, 16)
... print(f"{sixteenths[0]}/{sixteenths[1]}\t{duration.is_assignable()}")
...
0/16 False
1/16 True
2/16 True
3/16 True
4/16 True
5/16 False
6/16 True
7/16 True
8/16 True
9/16 False
10/16 False
11/16 False
12/16 True
13/16 False
14/16 True
15/16 True
16/16 True
"""
if Duration(0) < self < Duration(16):
if _math.is_nonnegative_integer_power_of_two(self.denominator):
if _math.is_assignable_integer(self.numerator):
return True
return False
[docs]
def is_dyadic(self) -> bool:
r"""
Is true when denominator of duration is integer power of two.
.. container:: example
>>> for n in range(1, 16 + 1):
... duration = abjad.Duration(1, n)
... result = duration.is_dyadic()
... print(f"1/{n}\t{result}")
...
1/1 True
1/2 True
1/3 False
1/4 True
1/5 False
1/6 False
1/7 False
1/8 True
1/9 False
1/10 False
1/11 False
1/12 False
1/13 False
1/14 False
1/15 False
1/16 True
"""
return _math.is_nonnegative_integer_power_of_two(self.denominator)
[docs]
def lilypond_duration_string(self) -> str:
"""
Gets LilyPond duration string.
.. container:: example
>>> abjad.Duration(3, 16).lilypond_duration_string()
'8.'
"""
if not self.is_assignable():
raise _exceptions.AssignabilityError(self)
undotted_rational = fractions.Fraction(1, 2) ** self.exponent()
if undotted_rational <= 1:
undotted_duration_string = str(undotted_rational.denominator)
elif undotted_rational == fractions.Fraction(2, 1):
undotted_duration_string = r"\breve"
elif undotted_rational == fractions.Fraction(4, 1):
undotted_duration_string = r"\longa"
elif undotted_rational == fractions.Fraction(8, 1):
undotted_duration_string = r"\maxima"
else:
message = f"can not process undotted rational: {undotted_rational!r}"
raise ValueError(message)
dot_count = self.dot_count()
dot_string = "." * dot_count
dotted_duration_string = undotted_duration_string + dot_string
return dotted_duration_string
[docs]
def pair(self) -> tuple[int, int]:
"""
Gets (numerator, denominator) pair.
"""
return self.numerator, self.denominator
[docs]
def reciprocal(self) -> Duration:
"""
Gets reciprocal.
.. container:: example
>>> abjad.Duration(3, 7).reciprocal()
Duration(numerator=7, denominator=3)
"""
return Duration(self.denominator, self.numerator)
[docs]
@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Ratio:
"""
Ratio.
.. container:: example
>>> abjad.Ratio(6, 4)
Ratio(numerator=6, denominator=4)
Abjad ratios are two-term, unreduced integer ratios of the form ``n:d``.
These are used primarily in modeling tuplet ratios like ``3:2`` and ``6:4``.
"""
numerator: int
denominator: int
def __post_init__(self):
assert isinstance(self.numerator, int), repr(self.numerator)
assert isinstance(self.denominator, int), repr(self.denominator)
def __mul__(self, n: int) -> Ratio:
"""
Multiplies numerator and denominator of ratio by ``n``.
.. container:: example
>>> abjad.Ratio(3, 2) * 2
Ratio(numerator=6, denominator=4)
"""
assert isinstance(n, int), repr(n)
return Ratio(n * self.numerator, n * self.denominator)
def __truediv__(self, n: int) -> Ratio:
"""
Divides numerator and denominator of ratio by ``n``.
.. container:: example
>>> abjad.Ratio(6, 4) / 2
Ratio(numerator=3, denominator=2)
"""
assert isinstance(n, int), repr(n)
assert self.numerator % n == 0, repr((self.numerator, n))
assert self.denominator % n == 0, repr((self.denominator, n))
return Ratio(self.numerator // n, self.denominator // n)
def __rmul__(self, n: int) -> Ratio:
"""
Multiplies numerator and denominator of ratio by ``n``.
.. container:: example
>>> 2 * abjad.Ratio(3, 2)
Ratio(numerator=6, denominator=4)
"""
assert isinstance(n, int), repr(n)
return Ratio(n * self.numerator, n * self.denominator)
def __str__(self) -> str:
"""
Gets colon-delimited string format of ratio.
.. container:: example
>>> str(abjad.Ratio(6, 4))
'6:4'
"""
return f"{self.numerator}:{self.denominator}"
[docs]
def fraction(self):
"""
Changes (unreduced) ratio to (reduced) fraction.
.. container:: example
>>> abjad.Ratio(6, 4).fraction()
Fraction(3, 2)
"""
return fractions.Fraction(self.numerator, self.denominator)
[docs]
def is_augmented(self) -> bool:
"""
Is true when ratio numerator is less than ratio denominator.
.. container:: example
>>> abjad.Ratio(6, 4).is_augmented()
False
>>> abjad.Ratio(3, 2).is_augmented()
False
>>> abjad.Ratio(4, 6).is_augmented()
True
>>> abjad.Ratio(2, 3).is_augmented()
True
>>> abjad.Ratio(2, 2).is_augmented()
False
>>> abjad.Ratio(1, 1).is_augmented()
False
"""
return self.numerator < self.denominator
[docs]
def is_canonical(self) -> bool:
"""
Is true when ratio is normalized diminution.
.. container:: example
>>> abjad.Ratio(6, 4).is_canonical()
True
>>> abjad.Ratio(3, 2).is_canonical()
True
>>> abjad.Ratio(4, 6).is_canonical()
False
>>> abjad.Ratio(2, 3).is_canonical()
False
>>> abjad.Ratio(2, 2).is_canonical()
False
>>> abjad.Ratio(1, 1).is_canonical()
False
"""
return self.is_normalized() and self.is_diminished()
[docs]
def is_diminished(self) -> bool:
"""
Is true when ratio denominator is less than ratio numerator.
.. container:: example
>>> abjad.Ratio(6, 4).is_diminished()
True
>>> abjad.Ratio(3, 2).is_diminished()
True
>>> abjad.Ratio(4, 6).is_diminished()
False
>>> abjad.Ratio(2, 3).is_diminished()
False
>>> abjad.Ratio(2, 2).is_diminished()
False
>>> abjad.Ratio(1, 1).is_diminished()
False
"""
return self.denominator < self.numerator
[docs]
def is_dyadic(self) -> bool:
"""
Is true when ratio denominator is nonnegative integer power of two.
.. container:: example
>>> abjad.Ratio(6, 4).is_dyadic()
True
>>> abjad.Ratio(3, 2).is_dyadic()
True
>>> abjad.Ratio(4, 6).is_dyadic()
False
>>> abjad.Ratio(2, 3).is_dyadic()
False
>>> abjad.Ratio(2, 2).is_dyadic()
True
>>> abjad.Ratio(1, 1).is_dyadic()
True
"""
return _math.is_nonnegative_integer_power_of_two(self.denominator)
[docs]
def is_normalized(self) -> bool:
"""
Is true when fraction form of ratio is greater than ``1/2`` and less
than ``2``.
.. container:: example
>>> abjad.Ratio(6, 4).is_normalized()
True
>>> abjad.Ratio(3, 2).is_normalized()
True
>>> abjad.Ratio(4, 6).is_normalized()
True
>>> abjad.Ratio(2, 3).is_normalized()
True
>>> abjad.Ratio(2, 2).is_normalized()
True
>>> abjad.Ratio(1, 1).is_normalized()
True
.. container:: example
>>> abjad.Ratio(10, 4).is_normalized()
False
>>> abjad.Ratio(4, 10).is_normalized()
False
>>> abjad.Ratio(9, 4).is_normalized()
False
>>> abjad.Ratio(4, 9).is_normalized()
False
"""
self_fraction = fractions.Fraction(self.numerator, self.denominator)
return fractions.Fraction(1, 2) < self_fraction < fractions.Fraction(2)
[docs]
def is_reduced(self) -> bool:
"""
Is true when ratio numerator and denominator are relatively prime.
.. container:: example
>>> abjad.Ratio(6, 4).is_reduced()
False
>>> abjad.Ratio(3, 2).is_reduced()
True
>>> abjad.Ratio(4, 6).is_reduced()
False
>>> abjad.Ratio(2, 3).is_reduced()
True
>>> abjad.Ratio(2, 2).is_reduced()
False
>>> abjad.Ratio(1, 1).is_reduced()
True
"""
fraction = self.fraction()
if self.numerator == fraction.numerator:
if self.denominator == fraction.denominator:
return True
return False
[docs]
def is_trivial(self) -> bool:
"""
Is true when ratio numerator equals ratio denominator.
.. container:: example
>>> abjad.Ratio(6, 4).is_trivial()
False
>>> abjad.Ratio(3, 2).is_trivial()
False
>>> abjad.Ratio(4, 6).is_trivial()
False
>>> abjad.Ratio(2, 3).is_trivial()
False
>>> abjad.Ratio(2, 2).is_trivial()
True
>>> abjad.Ratio(1, 1).is_trivial()
True
"""
return self.numerator == self.denominator
[docs]
def reciprocal(self) -> Ratio:
"""
Get reciprocal of ratio.
.. container:: example
>>> abjad.Ratio(6, 4).reciprocal()
Ratio(numerator=4, denominator=6)
"""
return Ratio(self.denominator, self.numerator)
[docs]
@dataclasses.dataclass(frozen=True, slots=True, unsafe_hash=True)
class Offset:
"""
Offset.
.. container:: example
>>> fraction = abjad.Fraction(3, 16)
>>> abjad.Offset(fraction)
Offset(Fraction(3, 16))
>>> displacement = abjad.Duration(-1, 16)
>>> abjad.Offset(fraction, displacement=displacement)
Offset(Fraction(3, 16), displacement=Duration(numerator=-1, denominator=16))
"""
fraction: fractions.Fraction
displacement: Duration | None = None
def __post_init__(self):
assert type(self.fraction).__name__ == "Fraction", repr(self.fraction)
if self.displacement is not None:
assert isinstance(self.displacement, Duration), repr(self.displacement)
def __add__(self, argument: int | fractions.Fraction | Duration) -> Offset:
assert isinstance(argument, int | fractions.Fraction | Duration), repr(argument)
if isinstance(argument, Duration):
fraction = argument.as_fraction()
else:
fraction = fractions.Fraction(argument)
offset = Offset(self.fraction + fraction, displacement=self.displacement)
return offset
def __radd__(self, argument: int | fractions.Fraction | Duration) -> Offset:
assert isinstance(argument, int | fractions.Fraction | Duration), repr(argument)
if isinstance(argument, Duration):
fraction = argument.as_fraction()
else:
fraction = fractions.Fraction(argument)
offset = Offset(fraction + self.fraction, displacement=self.displacement)
return offset
def __eq__(self, argument: object) -> bool:
if isinstance(argument, Offset):
if self.fraction == argument.fraction:
if self._nonnone_displacement() == argument._nonnone_displacement():
return True
return False
def __ge__(self, argument: object) -> bool:
if not isinstance(argument, Offset):
raise TypeError
if self.fraction == argument.fraction:
return self._nonnone_displacement() >= argument._nonnone_displacement()
return self.fraction >= argument.fraction
def __gt__(self, argument: object) -> bool:
if not isinstance(argument, Offset):
raise TypeError
if self.fraction == argument.fraction:
return self._nonnone_displacement() > argument._nonnone_displacement()
return self.fraction > argument.fraction
def __le__(self, argument: object) -> bool:
if not isinstance(argument, Offset):
raise TypeError
if self.fraction == argument.fraction:
return self._nonnone_displacement() <= argument._nonnone_displacement()
return self.fraction <= argument.fraction
def __lt__(self, argument: object) -> bool:
if not isinstance(argument, Offset):
raise TypeError
if self.fraction == argument.fraction:
return self._nonnone_displacement() < argument._nonnone_displacement()
return self.fraction < argument.fraction
def __mod__(self, argument: int | fractions.Fraction) -> Offset:
fraction = self.fraction % argument
return Offset(fraction=fraction, displacement=self.displacement)
def __mul__(self, argument):
raise NotImplementedError
def __repr__(self) -> str:
"""
Gets interpreter representation of offset.
.. container:: example
>>> abjad.Offset(abjad.Fraction(1, 4))
Offset(Fraction(1, 4))
>>> duration = abjad.Duration(-1, 16)
>>> abjad.Offset(abjad.Fraction(1, 4), displacement=duration)
Offset(Fraction(1, 4), displacement=Duration(numerator=-1, denominator=16))
"""
string = f"{self.fraction!r}"
if self.displacement is not None:
string = string + f", displacement={self.displacement!r}"
string = f"{Offset.__name__}({string})"
return string
__rmul__ = __mul__
def __str__(self) -> str:
return str(self.fraction)
@typing.overload
def __sub__(self, argument: typing.Self) -> Duration:
pass
@typing.overload
def __sub__(self, argument: int | fractions.Fraction | Duration) -> typing.Self:
pass
def __sub__(self, argument):
if isinstance(argument, Offset):
fraction = self.fraction - argument.fraction
return Duration(fraction.numerator, fraction.denominator)
else:
assert isinstance(argument, Duration), repr(argument)
result = Duration(*self.fraction.as_integer_ratio()) - argument
fraction = result.as_fraction()
return Offset(fraction, displacement=self.displacement)
def __truediv__(self, argument):
raise NotImplementedError
def _nonnone_displacement(self):
if self.displacement is None:
return Duration(0)
return self.displacement
# TODO: change to as_duration()
[docs]
def duration(self) -> Duration:
"""
Changes offset to duration.
.. container:: example
>>> fraction = abjad.Fraction(1, 4)
>>> offset = abjad.Offset(fraction)
>>> offset.duration()
Duration(numerator=1, denominator=4)
"""
assert self.displacement is None, repr(self)
return Duration(*self.fraction.as_integer_ratio())
[docs]
def remove_displacement(self):
"""
Makes new offset equal to this offset without displacement.
.. container:: example
>>> duration = abjad.Duration(-1, 16)
>>> offset = abjad.Offset(abjad.Fraction(1, 4), displacement=duration)
>>> offset
Offset(Fraction(1, 4), displacement=Duration(numerator=-1, denominator=16))
>>> offset.remove_displacement()
Offset(Fraction(1, 4))
"""
return Offset(self.fraction)