"""
Duration, offset, ratio.
"""
from __future__ import annotations
import dataclasses
import fractions
import math
import re
import typing
from . import exceptions as _exceptions
from . import math as _math
[docs]
def durations(items: list) -> list[Duration]:
"""
Changes ``items`` to durations.
.. container:: example
>>> abjad.duration.durations([(15, 8), (3, 8), abjad.TimeSignature((3, 4))])
[Duration(15, 8), Duration(3, 8), Duration(3, 4)]
"""
durations = []
for item in items:
if isinstance(item, tuple):
duration = Duration(*item)
elif callable(getattr(item, "duration", None)):
duration = item.duration()
else:
duration = Duration(item)
durations.append(duration)
return durations
[docs]
def is_duration(argument: object) -> bool:
"""
Is true when ``argument`` is duration (but not fraction).
.. container:: example
>>> abjad.duration.is_duration(abjad.Duration(1, 4))
True
>>> abjad.duration.is_duration(abjad.Fraction(1, 4))
False
>>> abjad.duration.is_duration(abjad.Offset(abjad.Fraction(1, 4)))
False
>>> abjad.duration.is_duration((1, 4))
False
>>> abjad.duration.is_duration(0)
False
"""
return type(argument).__name__ == "Duration"
[docs]
def is_fraction(argument: object) -> bool:
"""
Is true when ``argument`` is fraction (but not duration).
.. container:: example
>>> abjad.duration.is_fraction(abjad.Duration(1, 4))
False
>>> 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.Duration(1, 4))
False
>>> 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))
"""
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]
class Duration(fractions.Fraction):
"""
Duration.
.. container:: example
>>> abjad.Duration(3, 16)
Duration(3, 16)
"""
__slots__ = ()
def __abs__(self) -> typing.Self:
result = super().__abs__()
return type(self)(result)
@typing.overload
def __add__(self, argument: int | fractions.Fraction) -> typing.Self:
pass
@typing.overload
def __add__(self, argument: float) -> float:
pass
@typing.overload
def __add__(self, argument: complex) -> complex:
pass
def __add__(self, argument):
result = super().__add__(argument)
if isinstance(result, fractions.Fraction):
return type(self)(result)
return result
@typing.overload
def __mul__(self, other: int | fractions.Fraction) -> fractions.Fraction:
pass
@typing.overload
def __mul__(self, other: float) -> float:
pass
@typing.overload
def __mul__(self, other: complex) -> complex:
pass
def __mul__(self, other):
if isinstance(other, Duration):
raise TypeError("Multiplying two durations is undefined")
result = super().__mul__(other)
if isinstance(result, fractions.Fraction):
return type(self)(result)
return result
def __neg__(self) -> typing.Self:
return type(self)(super().__neg__())
@typing.overload
def __radd__(self, argument: int | fractions.Fraction) -> typing.Self:
pass
@typing.overload
def __radd__(self, argument: float) -> float:
pass
@typing.overload
def __radd__(self, argument: complex) -> complex:
pass
def __radd__(self, argument):
result = super().__radd__(argument)
if isinstance(result, fractions.Fraction):
return type(self)(result)
return result
def __repr__(self) -> str:
return f"{type(self).__name__}({self.numerator}, {self.denominator})"
@typing.overload
def __rmul__(self, other: int | fractions.Fraction) -> typing.Self:
pass
@typing.overload
def __rmul__(self, other: float) -> float:
pass
@typing.overload
def __rmul__(self, other: complex) -> complex:
pass
def __rmul__(self, argument):
if isinstance(argument, Duration):
message = f"Can not multiply two durations: {self}, {argument}"
raise TypeError(message)
result = super().__rmul__(argument)
if isinstance(result, fractions.Fraction):
return type(self)(result)
return result
@typing.overload
def __rsub__(self, other: int | fractions.Fraction) -> typing.Self:
pass
@typing.overload
def __rsub__(self, other: float) -> float:
pass
@typing.overload
def __rsub__(self, other: complex) -> complex:
pass
def __rsub__(self, other):
result = super().__rsub__(other)
if isinstance(result, fractions.Fraction):
return type(self)(result)
return result
@typing.overload
def __rtruediv__(self, other: int | fractions.Fraction) -> fractions.Fraction:
pass
@typing.overload
def __rtruediv__(self, other: float) -> float:
pass
@typing.overload
def __rtruediv__(self, other: complex) -> complex:
pass
def __rtruediv__(self, other):
return super().__rtruediv__(other)
@typing.overload
def __sub__(self, other: int | fractions.Fraction) -> typing.Self:
pass
@typing.overload
def __sub__(self, other: float) -> float:
pass
@typing.overload
def __sub__(self, other: complex) -> complex:
pass
def __sub__(self, other):
result = super().__sub__(other)
if isinstance(result, fractions.Fraction):
return type(self)(result)
return result
@typing.overload
def __truediv__(self, other: int | fractions.Fraction) -> fractions.Fraction:
pass
@typing.overload
def __truediv__(self, other: float) -> float:
pass
@typing.overload
def __truediv__(self, other: complex) -> complex:
pass
def __truediv__(self, other):
result = super().__truediv__(other)
if isinstance(other, Duration):
return fractions.Fraction(self) / fractions.Fraction(other)
if isinstance(result, fractions.Fraction) and isinstance(other, int):
return type(self)(result)
return result
[docs]
def clock_string(self) -> str:
r"""
Gets clock string.
Rounds down to nearest second.
.. container:: example
>>> abjad.Duration(117).clock_string()
"1'57''"
"""
minutes = int(self / 60)
seconds = str(int(self - minutes * 60)).zfill(2)
clock_string = f"{minutes}'{seconds}''"
return clock_string
[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):
... try:
... duration = abjad.Duration(n, 16)
... sixteenths = abjad.duration.pair_with_denominator(duration, 16)
... dot_count = duration.dot_count()
... string = f"{sixteenths[0]}/{sixteenths[1]}\t{dot_count}"
... print(string)
... except abjad.AssignabilityError:
... sixteenths = abjad.duration.pair_with_denominator(duration, 16)
... print(f"{sixteenths[0]}/{sixteenths[1]}\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
# TODO: port logic to rmakers and then remove
@staticmethod
def durations_to_nonreduced_fractions(
durations: list[Duration],
) -> list[tuple[int, int]]:
"""
Changes ``durations`` to pairs sharing least common denominator.
.. container:: example
>>> items = [abjad.Duration(2, 4), 3, (5, 16)]
>>> durations = abjad.duration.durations(items)
>>> result = abjad.Duration.durations_to_nonreduced_fractions(durations)
>>> for x in result:
... x
...
(8, 16)
(48, 16)
(5, 16)
"""
assert all(isinstance(_, Duration) for _ in durations), repr(durations)
denominators = [_.denominator for _ in durations]
lcd = _math.least_common_multiple(*denominators)
pairs = [pair_with_denominator(_, lcd) for _ in durations]
return pairs
# TODO: move to math.py or remove
[docs]
def equal_or_greater_assignable(self) -> Duration:
r"""
Gets assignable duration equal to or just greater than this duration.
.. container:: example
>>> for numerator in range(1, 16 + 1):
... duration = abjad.Duration(numerator, 16)
... result = duration.equal_or_greater_assignable()
... sixteenths = abjad.duration.pair_with_denominator(duration, 16)
... print(f"{sixteenths[0]}/{sixteenths[1]}\t{result!s}")
...
1/16 1/16
2/16 1/8
3/16 3/16
4/16 1/4
5/16 3/8
6/16 3/8
7/16 7/16
8/16 1/2
9/16 3/4
10/16 3/4
11/16 3/4
12/16 3/4
13/16 7/8
14/16 7/8
15/16 15/16
16/16 1
"""
good_denominator = _math.greatest_power_of_two_less_equal(self.denominator)
current_numerator = self.numerator
candidate = type(self)(current_numerator, good_denominator)
while not candidate.is_assignable():
current_numerator += 1
candidate = type(self)(current_numerator, good_denominator)
return candidate
# TODO: move to math.py or remove
[docs]
def equal_or_greater_power_of_two(self) -> Duration:
r"""
Gets duration equal or just greater power of two.
.. container:: example
>>> for numerator in range(1, 16 + 1):
... duration = abjad.Duration(numerator, 16)
... result = duration.equal_or_greater_power_of_two()
... sixteenths = abjad.duration.pair_with_denominator(duration, 16)
... print(f"{sixteenths[0]}/{sixteenths[1]}\t{result!s}")
...
1/16 1/16
2/16 1/8
3/16 1/4
4/16 1/4
5/16 1/2
6/16 1/2
7/16 1/2
8/16 1/2
9/16 1
10/16 1
11/16 1
12/16 1
13/16 1
14/16 1
15/16 1
16/16 1
"""
denominator_exponent = -int(math.ceil(math.log(self, 2)))
return type(self)(fractions.Fraction(1, 2) ** denominator_exponent)
# TODO: move to math.py or remove
[docs]
def equal_or_lesser_assignable(self) -> Duration:
r"""
Gets assignable duration equal or just less than this duration.
.. container:: example
>>> for numerator in range(1, 16 + 1):
... duration = abjad.Duration(numerator, 16)
... result = duration.equal_or_lesser_assignable()
... sixteenths = abjad.duration.pair_with_denominator(duration, 16)
... print(f"{sixteenths[0]}/{sixteenths[1]}\t{result!s}")
...
1/16 1/16
2/16 1/8
3/16 3/16
4/16 1/4
5/16 1/4
6/16 3/8
7/16 7/16
8/16 1/2
9/16 1/2
10/16 1/2
11/16 1/2
12/16 3/4
13/16 3/4
14/16 7/8
15/16 15/16
16/16 1
"""
good_denominator = 2 ** (int(math.ceil(math.log(self.denominator, 2))) + 0)
current_numerator = self.numerator
candidate = type(self)(current_numerator, good_denominator)
while not candidate.is_assignable():
current_numerator -= 1
candidate = type(self)(current_numerator, good_denominator)
return candidate
# TODO: move to math.py or remove
[docs]
def equal_or_lesser_power_of_two(self) -> Duration:
r"""
Gets duration of the form ``d**2`` equal to or just less than this
duration.
.. container:: example
>>> for numerator in range(1, 16 + 1):
... duration = abjad.Duration(numerator, 16)
... result = duration.equal_or_lesser_power_of_two()
... sixteenths = abjad.duration.pair_with_denominator(duration, 16)
... print(f"{sixteenths[0]}/{sixteenths[1]}\t{result!s}")
...
1/16 1/16
2/16 1/8
3/16 1/8
4/16 1/4
5/16 1/4
6/16 1/4
7/16 1/4
8/16 1/2
9/16 1/2
10/16 1/2
11/16 1/2
12/16 1/2
13/16 1/2
14/16 1/2
15/16 1/2
16/16 1
"""
return type(self)(fractions.Fraction(1, 2) ** self.exponent())
[docs]
def exponent(self) -> int:
r"""
Gets base-2 exponent.
.. container:: example
>>> for numerator in range(1, 16 + 1):
... duration = abjad.Duration(numerator, 16)
... exponent = duration.exponent()
... sixteenths = abjad.duration.pair_with_denominator(duration, 16)
... print(f"{sixteenths[0]}/{sixteenths[1]}\t{duration.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)
... sixty_fourths = abjad.duration.pair_with_denominator(duration, 64)
... print(f"{sixty_fourths[0]}/{sixty_fourths[1]}\t{duration.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)
[docs]
def fraction(self):
"""
Returns duration as fraction.
"""
return fractions.Fraction(self)
@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(0, 1)
>>> abjad.Duration.from_clock_string("0'59''")
Duration(59, 1)
>>> abjad.Duration.from_clock_string("1'00''")
Duration(60, 1)
>>> abjad.Duration.from_clock_string("1'17''")
Duration(77, 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(7, 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(3, 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)
[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)
... sixteenths = abjad.duration.pair_with_denominator(duration, 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 0 < self < 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)
... print(f"{duration!s}\t{duration.is_dyadic()}")
...
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)
# TODO: move logic to parser.py and then remove
@staticmethod
def is_token(argument) -> bool:
"""
Is true when ``argument`` correctly initializes a duration.
.. container:: example
>>> abjad.Duration.is_token('8.')
True
"""
try:
Duration.__new__(Duration, argument)
return True
except Exception:
return False
[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 = self.equal_or_lesser_power_of_two()
if undotted_rational <= 1:
undotted_duration_string = str(undotted_rational.denominator)
elif undotted_rational == type(self)(2, 1):
undotted_duration_string = r"\breve"
elif undotted_rational == type(self)(4, 1):
undotted_duration_string = r"\longa"
elif undotted_rational == type(self)(8, 1):
undotted_duration_string = r"\maxima"
else:
raise ValueError(f"can not process undotted rational: {undotted_rational}")
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 and denominator pair.
.. container:: example
>>> abjad.Duration(3, 16).pair()
(3, 16)
"""
return self.numerator, self.denominator
[docs]
def reciprocal(self) -> Duration:
"""
Gets reciprocal.
.. container:: example
>>> abjad.Duration(3, 7).reciprocal()
Duration(7, 3)
"""
return type(self)(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}"
# TODO: rename to fraction()
[docs]
def as_fraction(self):
"""
Changes (unreduced) ratio to (reduced) fraction.
.. container:: example
>>> abjad.Ratio(6, 4).as_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.as_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(-1, 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.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.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, type(self)):
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, type(self)):
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, type(self)):
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, type(self)):
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, type(self)):
raise TypeError
if self.fraction == argument.fraction:
return self._nonnone_displacement() < argument._nonnone_displacement()
return self.fraction < argument.fraction
def __mul__(self, argument):
raise NotImplementedError
def __repr__(self) -> str:
"""
Gets interpreter representation of value 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(-1, 16))
"""
string = f"{self.fraction!r}"
if self.displacement is not None:
string = string + f", displacement={self.displacement!r}"
string = f"{type(self).__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, type(self)):
fraction = self.fraction - argument.fraction
return Duration(fraction.numerator, fraction.denominator)
else:
assert isinstance(argument, int | fractions.Fraction | Duration), repr(
argument
)
result = self.fraction - argument
if isinstance(result, Duration):
fraction = result.fraction()
else:
assert isinstance(result, fractions.Fraction), repr(result)
fraction = result
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
[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(-1, 16))
>>> offset.remove_displacement()
Offset(Fraction(1, 4))
"""
return Offset(self.fraction)