Source code for abjad.duration

import fractions
import math
import re

from . import exceptions as _exceptions
from . import math as _math


[docs]class Duration(fractions.Fraction): """ Duration. .. container:: example Initializes from integer numerator: >>> abjad.Duration(3) Duration(3, 1) .. container:: example Initializes from integer numerator and denominator: >>> abjad.Duration(3, 16) Duration(3, 16) .. container:: example Initializes from integer-equivalent numeric numerator: >>> abjad.Duration(3.0) Duration(3, 1) .. container:: example Initializes from integer-equivalent numeric numerator and denominator: >>> abjad.Duration(3.0, 16) Duration(3, 16) .. container:: example Initializes from integer-equivalent singleton: >>> abjad.Duration((3,)) Duration(3, 1) .. container:: example Initializes from integer-equivalent pair: >>> abjad.Duration((3, 16)) Duration(3, 16) .. container:: example Initializes from other duration: >>> abjad.Duration(abjad.Duration(3, 16)) Duration(3, 16) .. container:: example Intializes from fraction: >>> import fractions >>> abjad.Duration(fractions.Fraction(3, 16)) Duration(3, 16) .. container:: example Initializes from solidus string: >>> abjad.Duration('3/16') Duration(3, 16) .. container:: example Durations inherit from built-in fraction: >>> isinstance(abjad.Duration(3, 16), fractions.Fraction) True .. container:: example Durations are numbers: >>> import numbers >>> isinstance(abjad.Duration(3, 16), numbers.Number) True """ ### CLASS VARIABLES ### __slots__ = () ### DUMMY INITIALIZER ### # dummy initializer so that mypy knows about _denominator, _numerator def __init__(self, *arguments): self._denominator = self._denominator self._numerator = self._numerator return None ### CONSTRUCTOR ###
[docs] def __new__(class_, *arguments): """ Makes new duration. """ if len(arguments) == 1: argument = arguments[0] if type(argument) is class_: return argument try: return fractions.Fraction.__new__(class_, *argument) except (AttributeError, TypeError): pass try: return fractions.Fraction.__new__(class_, argument) except (AttributeError, TypeError): pass if ( isinstance(argument, tuple) and len(argument) == 2 and _math.is_integer_equivalent(argument[0]) and _math.is_integer_equivalent(argument[1]) and not argument[1] == 0 ): return fractions.Fraction.__new__( class_, int(argument[0]), int(argument[1]) ) try: return fractions.Fraction.__new__(class_, argument.duration) except AttributeError: pass if isinstance(argument, str) and "/" not in argument: result = Duration._initialize_from_lilypond_duration_string(argument) return fractions.Fraction.__new__(class_, result) if ( isinstance(argument, tuple) and len(argument) == 1 and _math.is_integer_equivalent(argument[0]) ): return fractions.Fraction.__new__(class_, int(argument[0])) else: try: return fractions.Fraction.__new__(class_, *arguments) except TypeError: pass if _math.all_are_integer_equivalent_numbers(arguments): return fractions.Fraction.__new__(class_, *[int(x) for x in arguments]) raise ValueError(f"can not construct duration: {arguments!r}.")
### SPECIAL METHODS ###
[docs] def __abs__(self, *arguments): """ Gets absolute value of duration. Returns nonnegative duration. """ return type(self)(fractions.Fraction.__abs__(self, *arguments))
[docs] def __add__(self, *arguments): """ Adds duration to ``arguments``. .. container:: example Returns duration when ``arguments`` is a duration: >>> duration_1 = abjad.Duration(1, 2) >>> duration_2 = abjad.Duration(3, 2) >>> duration_1 + duration_2 Duration(2, 1) Returns duration. """ result = type(self)(fractions.Fraction.__add__(self, *arguments)) return result
[docs] def __div__(self, *arguments) -> fractions.Fraction: """ Divides duration by ``arguments``. .. container:: example >>> abjad.Duration(1) / abjad.Duration(3, 3) Fraction(1, 1) """ if len(arguments) == 1 and isinstance(arguments[0], type(self)): fraction = fractions.Fraction.__truediv__(self, *arguments) result = fractions.Fraction(fraction) else: result = type(self)(fractions.Fraction.__truediv__(self, *arguments)) return result
[docs] def __divmod__(self, *arguments): """ Equals the pair (duration // ``arguments``, duration % ``arguments``). Returns pair. """ truncated, residue = fractions.Fraction.__divmod__(self, *arguments) truncated = type(self)(truncated) residue = type(self)(residue) return truncated, residue
[docs] def __eq__(self, argument) -> bool: """ Is true when duration equals ``argument``. """ return fractions.Fraction.__eq__(self, argument)
[docs] def __ge__(self, argument) -> bool: """ Is true when duration is greater than or equal to ``argument``. """ return fractions.Fraction.__ge__(self, argument)
[docs] def __gt__(self, argument) -> bool: """ Is true when duration is greater than ``argument``. """ return fractions.Fraction.__gt__(self, argument)
[docs] def __hash__(self) -> int: """ Hashes duration. """ return super().__hash__()
[docs] def __le__(self, argument) -> bool: """ Is true when duration is less than or equal to ``argument``. """ return fractions.Fraction.__le__(self, argument)
[docs] def __lt__(self, argument) -> bool: """ Is true when duration is less than ``argument``. """ return fractions.Fraction.__lt__(self, argument)
[docs] def __mod__(self, *arguments): """ Modulus operator applied to duration. Returns duration. """ return type(self)(fractions.Fraction.__mod__(self, *arguments))
[docs] def __mul__(self, argument): """ Duration multiplied by ``argument``. .. container:: example Returns a new duration when ``argument`` is a duration: >>> duration_1 = abjad.Duration(1, 2) >>> duration_2 = abjad.Duration(3, 2) >>> duration_1 * duration_2 Duration(3, 4) Returns duration. """ result = type(self)(fractions.Fraction.__mul__(self, argument)) return result
[docs] def __ne__(self, argument) -> bool: """ Redefined explicitly because ``abjad.Duration`` inherits from built-in ``fractions.Fraction``: "The __ne__ method follows automatically from __eq__ only if __ne__ isn't already defined in a superclass. So, if you're inheriting from a builtin, it's best to override both." See https://bugs.python.org/issue4395#msg89533. REGRESSION: .. container:: example >>> offset_1 = abjad.Offset(1) >>> offset_2 = abjad.Offset(1, displacement=(-1, 16)) >>> offset_1 == offset_2 False >>> offset_1 != offset_2 True """ return not self == argument
[docs] def __neg__(self, *arguments): """ Negates duration. Returns new duration. """ return type(self)(fractions.Fraction.__neg__(self, *arguments))
[docs] def __pos__(self, *arguments): """ Get positive duration. Returns new duration. """ return type(self)(fractions.Fraction.__pos__(self, *arguments))
[docs] def __pow__(self, *arguments): """ Raises duration to ``arguments`` power. Returns new duration. """ return type(self)(fractions.Fraction.__pow__(self, *arguments))
[docs] def __radd__(self, *arguments): """ Adds ``arguments`` to duration. Returns new duration. """ return type(self)(fractions.Fraction.__radd__(self, *arguments))
[docs] def __rdiv__(self, *arguments): """ Divides ``arguments`` by duration. Returns new duration. """ return type(self)(fractions.Fraction.__rdiv__(self, *arguments))
[docs] def __rdivmod__(self, *arguments): """ Documentation required. """ return type(self)(fractions.Fraction.__rdivmod__(self, *arguments))
def __reduce__(self): """ Documentation required. """ return type(self), (self.numerator, self.denominator) def __reduce_ex__(self, protocol): """ Documentation required. """ return type(self), (self.numerator, self.denominator)
[docs] def __repr__(self) -> str: """ Gets repr. """ return f"{type(self).__name__}({self.numerator}, {self.denominator})"
[docs] def __rmod__(self, *arguments): """ Documentation required. """ return type(self)(fractions.Fraction.__rmod__(self, *arguments))
[docs] def __rmul__(self, *arguments): """ Multiplies ``arguments`` by duration. Returns new duration. """ return type(self)(fractions.Fraction.__rmul__(self, *arguments))
[docs] def __rpow__(self, *arguments): """ Raises ``arguments`` to the power of duration. Returns new duration. """ return type(self)(fractions.Fraction.__rpow__(self, *arguments))
[docs] def __rsub__(self, *arguments): """ Subtracts duration from ``arguments``. Returns new duration. """ return type(self)(fractions.Fraction.__rsub__(self, *arguments))
[docs] def __rtruediv__(self, *arguments): """ Documentation required. Returns new duration. """ return type(self)(fractions.Fraction.__rtruediv__(self, *arguments))
[docs] def __sub__(self, *arguments): """ Subtracts ``arguments`` from duration. .. container:: example >>> abjad.Duration(1, 2) - abjad.Duration(2, 8) Duration(1, 4) Returns new duration. """ return type(self)(fractions.Fraction.__sub__(self, *arguments))
[docs] def __truediv__(self, *arguments): """ Documentation required. """ return self.__div__(*arguments)
### PRIVATE METHODS ### @staticmethod def _initialize_from_lilypond_duration_string(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_strings = "|".join(body_strings) pattern = r"^(%s)(\.*)$" % body_strings match = re.match(pattern, duration_string) if match is None: raise TypeError(f"incorrect duration string format: {duration_string!r}.") 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 rational @staticmethod def _least_power_of_two_greater_equal(n, i=0) -> int: """ When ``i = 2``, returns the second integer power of 2 greater than the least integer power of 2 greater than or equal to ``n``, and, in general, return the ``i`` th integer power of 2 greater than the least integer power of 2 greater than or equal to ``n``. """ assert isinstance(n, int | float | fractions.Fraction), repr(n) assert 0 <= n, repr(n) result = 2 ** (int(math.ceil(math.log(n, 2))) + i) return result ### PUBLIC PROPERTIES ### @property def dot_count(self) -> int: r""" Gets dot count. .. container:: example Gets dot count: >>> for n in range(1, 16 + 1): ... try: ... duration = abjad.Duration(n, 16) ... sixteenths = abjad.duration.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.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 Dot count defined equal to number of dots required to notate duration. Raises assignability error when duration is not assignable. """ 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 @property def equal_or_greater_assignable(self) -> "Duration": r""" Gets assignable duration equal to or just greater than this duration. .. container:: example Gets equal-or-greater assignable duration: >>> for numerator in range(1, 16 + 1): ... duration = abjad.Duration(numerator, 16) ... result = duration.equal_or_greater_assignable ... sixteenths = abjad.duration.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 @property def equal_or_greater_power_of_two(self) -> "Duration": r""" Gets duration equal or just greater power of two. .. container:: example Gets equal-or-greater power-of-two: >>> for numerator in range(1, 16 + 1): ... duration = abjad.Duration(numerator, 16) ... result = duration.equal_or_greater_power_of_two ... sixteenths = abjad.duration.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)(1, 2) ** denominator_exponent @property def equal_or_lesser_assignable(self) -> "Duration": r""" Gets assignable duration equal or just less than this duration. .. container:: example Gets equal-or-lesser assignable duration: >>> for numerator in range(1, 16 + 1): ... duration = abjad.Duration(numerator, 16) ... result = duration.equal_or_lesser_assignable ... sixteenths = abjad.duration.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 = self._least_power_of_two_greater_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 @property 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 Gets equal-or-lesser power-of-two: >>> for numerator in range(1, 16 + 1): ... duration = abjad.Duration(numerator, 16) ... result = duration.equal_or_lesser_power_of_two ... sixteenths = abjad.duration.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)(1, 2) ** self.exponent @property def exponent(self) -> int: r""" Gets base-2 exponent. .. container:: example Gets equal-or-greater power-of-two: >>> for numerator in range(1, 16 + 1): ... duration = abjad.Duration(numerator, 16) ... exponent = duration.exponent ... sixteenths = abjad.duration.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))) @property def flag_count(self) -> int: r""" Gets flag count. .. container:: example Gets flag count: >>> for n in range(1, 16 + 1): ... duration = abjad.Duration(n, 64) ... sixty_fourths = abjad.duration.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 Flag count defined equal to number of flags required to notate duration. """ log = math.log(float(self.numerator) / self.denominator, 2) count = -int(math.floor(log)) - 2 return max(count, 0) @property def is_dyadic_rational(self) -> bool: r""" Is true when duration is an integer power of two. .. container:: example Is true when duration has power-of-two denominator: >>> for n in range(1, 16 + 1): ... duration = abjad.Duration(1, n) ... result = duration.is_dyadic_rational ... print(f"{duration!s}\t{result}") ... 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 """ exponent = math.log(self.denominator, 2) return int(exponent) == exponent @property def implied_prolation(self) -> fractions.Fraction: r""" Gets implied prolation. .. container:: example Gets implied prolation: >>> for denominator in range(1, 16 + 1): ... duration = abjad.Duration(1, denominator) ... result = duration.implied_prolation ... print(f"{duration!s}\t{result!s}") ... 1 1 1/2 1 1/3 2/3 1/4 1 1/5 4/5 1/6 2/3 1/7 4/7 1/8 1 1/9 8/9 1/10 4/5 1/11 8/11 1/12 2/3 1/13 8/13 1/14 4/7 1/15 8/15 1/16 1 """ numerator = _math.greatest_power_of_two_less_equal(self.denominator) return fractions.Fraction(numerator, self.denominator) @property def is_assignable(self) -> bool: r""" Is true when duration is assignable. .. container:: example Is true when duration is assignable: >>> for numerator in range(0, 16 + 1): ... duration = abjad.Duration(numerator, 16) ... sixteenths = abjad.duration.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 @property def lilypond_duration_string(self) -> str: """ Gets LilyPond duration string. .. container:: example Gets LilyPond duration string: >>> abjad.Duration(3, 16).lilypond_duration_string '8.' Raises assignability error when duration is not assignable. """ 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 @property def pair(self) -> tuple[int, int]: """ Gets numerator and denominator. .. container:: example Gets pair: >>> abjad.Duration(3, 16).pair (3, 16) """ return self.numerator, self.denominator @property def prolation_string(self) -> str: """ Gets prolation string. .. container:: example Gets prolation string: >>> abjad.Duration(3, 16).prolation_string '16:3' """ return f"{self.denominator}:{self.numerator}" @property def reciprocal(self) -> "Duration": """ Gets reciprocal. .. container:: example Gets reciprocal: >>> abjad.Duration(3, 7).reciprocal Duration(7, 3) """ return type(self)(self.denominator, self.numerator) ### PUBLIC FUNCTIONS ###
[docs] @staticmethod def durations_to_nonreduced_fractions(durations: list) -> list[tuple[int, int]]: """ Changes ``durations`` to pairs sharing least common denominator. .. container:: example >>> durations = [abjad.Duration(2, 4), 3, (5, 16)] >>> result = abjad.Duration.durations_to_nonreduced_fractions(durations) >>> for x in result: ... x ... (8, 16) (48, 16) (5, 16) """ durations_ = [Duration(_) for _ in durations] denominators = [_.denominator for _ in durations_] lcd = _math.least_common_multiple(*denominators) pairs = [with_denominator(_, lcd) for _ in durations_] return pairs
[docs] @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)
[docs] @staticmethod def from_dot_count(dot_count: int) -> "Duration": """ Makes duration from ``dot_count``. .. container:: example >>> abjad.Duration.from_dot_count(0) Duration(1, 1) >>> abjad.Duration.from_dot_count(1) Duration(3, 2) >>> abjad.Duration.from_dot_count(2) Duration(7, 4) >>> abjad.Duration.from_dot_count(3) Duration(15, 8) >>> abjad.Duration.from_dot_count(4) Duration(31, 16) """ 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 return Duration(numerator, denominator)
[docs] @staticmethod def from_lilypond_duration_string( lilypond_duration_string, ) -> "Duration": """ Initializes duration from LilyPond duration string. .. container:: example >>> abjad.Duration.from_lilypond_duration_string('8.') Duration(3, 16) """ fraction = Duration._initialize_from_lilypond_duration_string( lilypond_duration_string ) return Duration(fraction)
[docs] @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 normalized(self) -> bool: """ Is true when duration is greater than ``1/2`` and less than ``2``. .. container:: example >>> abjad.Duration(3, 2).normalized() True """ return type(self)(1, 2) < self < type(self)(2)
[docs] def to_clock_string(self) -> str: r""" Changes duration to clock string. Changes duration to clock string: .. container:: example >>> note = abjad.Note("c'4") >>> duration = abjad.Duration(117) >>> clock_string = duration.to_clock_string() >>> clock_string "1'57''" >>> string = rf"\markup {{ {clock_string} }}" >>> markup = abjad.Markup(string) >>> abjad.attach(markup, note, direction=abjad.UP) >>> abjad.show(note) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(note) >>> print(string) c'4 ^ \markup { 1'57'' } Rounds down to nearest second. """ minutes = int(self / 60) seconds = str(int(self - minutes * 60)).zfill(2) clock_string = f"{minutes}'{seconds}''" return clock_string
[docs]class Offset(Duration): """ Offset. .. container:: example Initializes from integer numerator: >>> abjad.Offset(3) Offset((3, 1)) .. container:: example Initializes from integer numerator and denominator: >>> abjad.Offset(3, 16) Offset((3, 16)) .. container:: example Initializes from integer-equivalent numeric numerator: >>> abjad.Offset(3.0) Offset((3, 1)) .. container:: example Initializes from integer-equivalent numeric numerator and denominator: >>> abjad.Offset(3.0, 16) Offset((3, 16)) .. container:: example Initializes from integer-equivalent singleton: >>> abjad.Offset((3,)) Offset((3, 1)) .. container:: example Initializes from integer-equivalent pair: >>> abjad.Offset((3, 16)) Offset((3, 16)) .. container:: example Initializes from duration: >>> abjad.Offset(abjad.Duration(3, 16)) Offset((3, 16)) .. container:: example Initializes from other offset: >>> abjad.Offset(abjad.Offset(3, 16)) Offset((3, 16)) .. container:: example Initializes from other offset with displacement: >>> offset = abjad.Offset((3, 16), displacement=(-1, 16)) >>> abjad.Offset(offset) Offset((3, 16), displacement=Duration(-1, 16)) .. container:: example Intializes from fraction: >>> import fractions >>> abjad.Offset(fractions.Fraction(3, 16)) Offset((3, 16)) .. container:: example Initializes from solidus string: >>> abjad.Offset('3/16') Offset((3, 16)) .. container:: example Offsets inherit from built-in fraction: >>> isinstance(abjad.Offset(3, 16), fractions.Fraction) True .. container:: example Offsets are numbers: >>> import numbers >>> isinstance(abjad.Offset(3, 16), numbers.Number) True """ ### CLASS VARIABLES ### __slots__ = ("_displacement",) ### DUMMY INITIALIZER ### # dummy initializer so that mypy knows about _denominator, _displacement, _numerator def __init__(self, *arguments, **keywords): self._denominator = self._denominator self._displacement = self._displacement self._numerator = self._numerator return None ### CONSTRUCTOR ###
[docs] def __new__(class_, *arguments, **keywords): displacement = None for argument in arguments: try: displacement = argument.displacement break except AttributeError: pass displacement = displacement or keywords.get("displacement") if displacement is not None: displacement = Duration(displacement) displacement = displacement or None if len(arguments) == 1 and isinstance(arguments[0], Duration): arguments = arguments[0].pair self = Duration.__new__(class_, *arguments) self._displacement = displacement return self
### SPECIAL METHODS ###
[docs] def __copy__(self, *arguments) -> "Offset": """ Copies offset. >>> import copy .. container:: example Copies offset with displacement: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_2 = copy.copy(offset_1) >>> offset_1 Offset((1, 4), displacement=Duration(-1, 16)) >>> offset_2 Offset((1, 4), displacement=Duration(-1, 16)) >>> offset_1 == offset_2 True >>> offset_1 is offset_2 False """ return type(self)(self.pair, displacement=self.displacement)
[docs] def __deepcopy__(self, *arguments) -> "Offset": """ Deep copies offset. >>> import copy .. container:: example Copies offset with displacement: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_2 = copy.deepcopy(offset_1) >>> offset_1 Offset((1, 4), displacement=Duration(-1, 16)) >>> offset_2 Offset((1, 4), displacement=Duration(-1, 16)) >>> offset_1 == offset_2 True >>> offset_1 is offset_2 False """ return self.__copy__(*arguments)
[docs] def __eq__(self, argument) -> bool: """ Is true when offset equals ``argument``. With equal numerators, denominators and displacement: .. container:: example >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 == offset_1 True >>> offset_1 == offset_2 True >>> offset_2 == offset_1 True >>> offset_2 == offset_2 True .. container:: example With equal numerators and denominators but differing grace displacements: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 8)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 == offset_1 True >>> offset_1 == offset_2 False >>> offset_2 == offset_1 False >>> offset_2 == offset_2 True .. container:: example With differing numerators and denominators. Ignores grace displacements: >>> offset_1 = abjad.Offset((1, 4)) >>> offset_2 = abjad.Offset((1, 2), displacement=(-99)) >>> offset_1 == offset_1 True >>> offset_1 == offset_2 False >>> offset_2 == offset_1 False >>> offset_2 == offset_2 True """ if isinstance(argument, type(self)) and self.pair == argument.pair: return self._get_displacement() == argument._get_displacement() return super().__eq__(argument)
[docs] def __ge__(self, argument) -> bool: """ Is true when offset is greater than or equal to ``argument``. With equal numerators, denominators and displacement: .. container:: example >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 >= offset_1 True >>> offset_1 >= offset_2 True >>> offset_2 >= offset_1 True >>> offset_2 >= offset_2 True .. container:: example With equal numerators and denominators but differing grace displacements: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 8)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 >= offset_1 True >>> offset_1 >= offset_2 False >>> offset_2 >= offset_1 True >>> offset_2 >= offset_2 True .. container:: example With differing numerators and denominators. Ignores grace displacements: >>> offset_1 = abjad.Offset((1, 4)) >>> offset_2 = abjad.Offset((1, 2), displacement=(-99)) >>> offset_1 >= offset_1 True >>> offset_1 >= offset_2 False >>> offset_2 >= offset_1 True >>> offset_2 >= offset_2 True """ if isinstance(argument, type(self)) and self.pair == argument.pair: return self._get_displacement() >= argument._get_displacement() return super().__ge__(argument)
[docs] def __gt__(self, argument) -> bool: """ Is true when offset is greater than ``argument``. With equal numerators, denominators and displacement: .. container:: example >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 > offset_1 False >>> offset_1 > offset_2 False >>> offset_2 > offset_1 False >>> offset_2 > offset_2 False .. container:: example With equal numerators and denominators but differing grace displacements: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 8)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 > offset_1 False >>> offset_1 > offset_2 False >>> offset_2 > offset_1 True >>> offset_2 > offset_2 False .. container:: example With differing numerators and denominators. Ignores grace displacements: >>> offset_1 = abjad.Offset((1, 4)) >>> offset_2 = abjad.Offset((1, 2), displacement=(-99)) >>> offset_1 > offset_1 False >>> offset_1 > offset_2 False >>> offset_2 > offset_1 True >>> offset_2 > offset_2 False """ if isinstance(argument, type(self)) and self.pair == argument.pair: return self._get_displacement() > argument._get_displacement() return Duration.__gt__(self, argument)
[docs] def __hash__(self) -> int: """ Hashes offset. """ return super().__hash__()
[docs] def __le__(self, argument) -> bool: """ Is true when offset is less than or equal to ``argument``. With equal numerators, denominators and displacement: .. container:: example >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 <= offset_1 True >>> offset_1 <= offset_2 True >>> offset_2 <= offset_1 True >>> offset_2 <= offset_2 True .. container:: example With equal numerators and denominators but differing grace displacements: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 8)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 <= offset_1 True >>> offset_1 <= offset_2 True >>> offset_2 <= offset_1 False >>> offset_2 <= offset_2 True .. container:: example With differing numerators and denominators. Ignores grace displacements: >>> offset_1 = abjad.Offset((1, 4)) >>> offset_2 = abjad.Offset((1, 2), displacement=(-99)) >>> offset_1 <= offset_1 True >>> offset_1 <= offset_2 True >>> offset_2 <= offset_1 False >>> offset_2 <= offset_2 True """ if isinstance(argument, type(self)) and self.pair == argument.pair: return self._get_displacement() <= argument._get_displacement() return super().__le__(argument)
[docs] def __lt__(self, argument) -> bool: """ Is true when offset is less than ``argument``. With equal numerators, denominators and displacement: .. container:: example >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 < offset_1 False >>> offset_1 < offset_2 False >>> offset_2 < offset_1 False >>> offset_2 < offset_2 False .. container:: example With equal numerators and denominators but differing nonzero grace displacements: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 8)) >>> offset_2 = abjad.Offset((1, 4), displacement=(-1, 16)) >>> offset_1 < offset_1 False >>> offset_1 < offset_2 True >>> offset_2 < offset_1 False >>> offset_2 < offset_2 False .. container:: example With equal numerators and denominators but differing zero-valued displacement: >>> offset_1 = abjad.Offset((1, 4), displacement=(-1, 8)) >>> offset_2 = abjad.Offset((1, 4)) >>> offset_1 < offset_1 False >>> offset_1 < offset_2 True >>> offset_2 < offset_1 False >>> offset_2 < offset_2 False .. container:: example With differing numerators and denominators. Ignores grace displacements: >>> offset_1 = abjad.Offset((1, 4)) >>> offset_2 = abjad.Offset((1, 2), displacement=(-99)) >>> offset_1 < offset_1 False >>> offset_1 < offset_2 True >>> offset_2 < offset_1 False >>> offset_2 < offset_2 False """ if isinstance(argument, type(self)) and self.pair == argument.pair: return self._get_displacement() < argument._get_displacement() return super().__lt__(argument)
[docs] def __repr__(self) -> str: """ Gets interpreter representation of offset. .. container:: example >>> abjad.Offset(1, 4) Offset((1, 4)) >>> abjad.Offset(1, 4, displacement=(-1, 16)) Offset((1, 4), displacement=Duration(-1, 16)) """ if self.displacement is None: return f"{type(self).__name__}({self.pair})" else: string = f"{self.pair}, displacement={self.displacement!r}" return f"{type(self).__name__}({string})"
[docs] def __sub__(self, argument): """ Subtracts ``argument`` from offset. .. container:: example Offset taken from offset returns duration: >>> abjad.Offset(2) - abjad.Offset(1, 2) Duration(3, 2) Duration taken from offset returns another offset: >>> abjad.Offset(2) - abjad.Duration(1, 2) Offset((3, 2)) .. container:: Coerces ``argument`` to offset when ``argument`` is neither offset nor duration: >>> import fractions >>> abjad.Offset(2) - fractions.Fraction(1, 2) Duration(3, 2) """ if isinstance(argument, type(self)): return Duration(super().__sub__(argument)) elif isinstance(argument, Duration): return super().__sub__(argument) else: argument = type(self)(argument) return self - argument
### PRIVATE METHODS ### def _get_displacement(self): if self.displacement is None: return Duration(0) return self.displacement ### PUBLIC PROPERTIES ### @property def displacement(self): """ Gets displacement. .. container:: example Gets displacement equal to none: >>> offset = abjad.Offset(1, 4) >>> offset.displacement is None True .. container:: example Gets displacement equal to a negative sixteenth: >>> offset = abjad.Offset(1, 4, displacement=(-1, 16)) >>> offset.displacement Duration(-1, 16) .. container:: example Stores zero-valued displacement as none: >>> offset = abjad.Offset(1, 4, displacement=0) >>> offset.displacement is None True >>> offset Offset((1, 4)) """ return self._displacement
[docs]def add_pairs(pair_1, pair_2): assert isinstance(pair_1, tuple), repr(pair_1) assert isinstance(pair_2, tuple), repr(pair_2) if pair_1[1] == pair_2[1]: numerator = pair_1[0] + pair_2[0] pair = (numerator, pair_1[1]) else: denominators = [pair_1[1], pair_2[1]] denominator = _math.least_common_multiple(*denominators) self_multiplier = denominator // pair_1[1] argument_multiplier = denominator // pair_2[1] self_numerator = self_multiplier * pair_1[0] argument_numerator = argument_multiplier * pair_2[0] pair = (self_numerator + argument_numerator, denominator) return pair
[docs]def divide_pair(pair, n) -> tuple[int, int]: """ Divides ``pair`` by integer ``n``. """ assert isinstance(pair, tuple), repr(pair) fraction = fractions.Fraction(*pair) / n denominator = _math.least_common_multiple(pair[1], fraction.denominator) pair = with_denominator(fraction, denominator) return pair
[docs]def durations(durations) -> list[Duration]: """ Changes ``durations`` to durations. .. container:: example >>> abjad.durations([(15, 8), (3, 8)]) [Duration(15, 8), Duration(3, 8)] """ durations = [Duration(_) for _ in durations] return durations
[docs]def pair(argument) -> tuple[int, int]: """ Changes ``argument`` to pair. .. container:: example >>> abjad.duration.pair((3, 6)) (3, 6) >>> abjad.duration.pair(abjad.Fraction(3, 6)) (1, 2) >>> abjad.duration.pair(abjad.Duration(3, 6)) (1, 2) >>> abjad.duration.pair(abjad.Offset((3, 6))) (1, 2) >>> abjad.duration.pair(abjad.TimeSignature((3, 6))) (3, 6) """ if hasattr(argument, "numerator"): return argument.numerator, argument.denominator if isinstance(argument, tuple) and len(argument) == 2: return argument[0], argument[1] raise ValueError(argument)
[docs]def with_denominator(duration, denominator) -> tuple[int, int]: """ Spells ``duration`` as pair with ``denominator``. .. container:: example >>> abjad.duration.with_denominator(abjad.Duration(3, 6), 12) (6, 12) .. container:: example >>> for numerator in range(12): ... fraction = abjad.Fraction(numerator, 6) ... print(fraction, abjad.duration.with_denominator(fraction, 12)) ... 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) >>> for numerator in range(12): ... pair = (numerator, 6) ... print(pair, abjad.duration.with_denominator(pair, 8)) ... (0, 6) (0, 8) (1, 6) (1, 6) (2, 6) (2, 6) (3, 6) (4, 8) (4, 6) (4, 6) (5, 6) (5, 6) (6, 6) (8, 8) (7, 6) (7, 6) (8, 6) (8, 6) (9, 6) (12, 8) (10, 6) (10, 6) (11, 6) (11, 6) >>> for numerator in range(12): ... pair = (numerator, 6) ... print(pair, abjad.duration.with_denominator(pair, 12)) ... (0, 6) (0, 12) (1, 6) (2, 12) (2, 6) (4, 12) (3, 6) (6, 12) (4, 6) (8, 12) (5, 6) (10, 12) (6, 6) (12, 12) (7, 6) (14, 12) (8, 6) (16, 12) (9, 6) (18, 12) (10, 6) (20, 12) (11, 6) (22, 12) """ if isinstance(duration, tuple): current_numerator, current_denominator = duration else: current_numerator, current_denominator = ( duration.numerator, duration.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