import copy
import dataclasses
import fractions
import functools
import math
import numbers
import re
import typing
from . import enums as _enums
from . import math as _math
from . import string as _string
### MAPPINGS (most will become functions) ###
_direction_number_to_direction_symbol = {0: "", 1: "+", -1: "-"}
# TODO: change to function; accommodate arbitrarily long names
_accidental_abbreviation_to_name = {
"ss": "double sharp",
"tqs": "three-quarters sharp",
"s": "sharp",
"qs": "quarter sharp",
"": "natural",
"qf": "quarter flat",
"f": "flat",
"tqf": "three-quarters flat",
"ff": "double flat",
}
# TODO: change to function; accommodate arbitrarily long names
_accidental_abbreviation_to_semitones = {
"ff": -2,
"tqf": -1.5,
"f": -1,
"qf": -0.5,
"": 0,
"qs": 0.5,
"s": 1,
"tqs": 1.5,
"ss": 2,
}
# TODO: possibly remove?
_accidental_abbreviation_to_symbol = {
"ff": "bb",
"tqf": "b~",
"f": "b",
"qf": "~",
"": "",
"qs": "+",
"s": "#",
"tqs": "#+",
"ss": "##",
}
# TODO: change to function; accommodate arbitrarily long names
_accidental_name_to_abbreviation = {
"double sharp": "ss",
"three-quarters sharp": "tqs",
"sharp": "s",
"quarter sharp": "qs",
"natural": "",
"quarter flat": "qf",
"flat": "f",
"three-quarters flat": "tqf",
"double flat": "ff",
}
# TODO: change to function; accommodate arbitrary numeric input
_accidental_semitones_to_abbreviation = {
-2.0: "ff",
-1.5: "tqf",
-1.0: "f",
-0.5: "qf",
0.0: "",
0.5: "qs",
1.0: "s",
1.5: "tqs",
2.0: "ss",
}
_diatonic_pc_name_to_diatonic_pc_number = {
"c": 0,
"d": 1,
"e": 2,
"f": 3,
"g": 4,
"a": 5,
"b": 6,
}
_diatonic_pc_name_to_pitch_class_number = {
"c": 0,
"d": 2,
"e": 4,
"f": 5,
"g": 7,
"a": 9,
"b": 11,
}
_diatonic_pc_number_to_diatonic_pc_name = {
0: "c",
1: "d",
2: "e",
3: "f",
4: "g",
5: "a",
6: "b",
}
_diatonic_pc_number_to_pitch_class_number = {
0: 0,
1: 2,
2: 4,
3: 5,
4: 7,
5: 9,
6: 11,
}
_pitch_class_number_to_diatonic_pc_number = {
0: 0,
2: 1,
4: 2,
5: 3,
7: 4,
9: 5,
11: 6,
}
_pitch_class_number_to_pitch_class_name = {
0.0: "c",
0.5: "cqs",
1.0: "cs",
1.5: "dqf",
2.0: "d",
2.5: "dqs",
3.0: "ef",
3.5: "eqf",
4.0: "e",
4.5: "eqs",
5.0: "f",
5.5: "fqs",
6.0: "fs",
6.5: "gqf",
7.0: "g",
7.5: "gqs",
8.0: "af",
8.5: "aqf",
9.0: "a",
9.5: "aqs",
10.0: "bf",
10.5: "bqf",
11.0: "b",
11.5: "bqs",
}
_pitch_class_number_to_pitch_class_name_with_flats = {
0.0: "c",
0.5: "dtqf",
1.0: "df",
1.5: "dqf",
2.0: "d",
2.5: "etqf",
3.0: "ef",
3.5: "eqf",
4.0: "e",
4.5: "fqf",
5.0: "f",
5.5: "gtqf",
6.0: "gf",
6.5: "gqf",
7.0: "g",
7.5: "atqf",
8.0: "af",
8.5: "aqf",
9.0: "a",
9.5: "btqf",
10.0: "bf",
10.5: "bqf",
11.0: "b",
11.5: "cqf",
}
_pitch_class_number_to_pitch_class_name_with_sharps = {
0.0: "c",
0.5: "cqs",
1.0: "cs",
1.5: "ctqs",
2.0: "d",
2.5: "dqs",
3.0: "ds",
3.5: "dtqs",
4.0: "e",
4.5: "eqs",
5.0: "f",
5.5: "fqs",
6.0: "fs",
6.5: "ftqs",
7.0: "g",
7.5: "gqs",
8.0: "gs",
8.5: "gtqs",
9.0: "a",
9.5: "aqs",
10.0: "as",
10.5: "atqs",
11.0: "b",
11.5: "bqs",
}
_diatonic_number_to_quality_dictionary = {
1: {"d": -1, "P": 0, "A": 1},
2: {"d": 0, "m": 1, "M": 2, "A": 3},
3: {"d": 2, "m": 3, "M": 4, "A": 5},
4: {"d": 4, "P": 5, "A": 6},
5: {"d": 6, "P": 7, "A": 8},
6: {"d": 7, "m": 8, "M": 9, "A": 10},
7: {"d": 9, "m": 10, "M": 11, "A": 12},
8: {"d": 11, "P": 12, "A": 13},
}
def _diatonic_number_to_octaves_and_diatonic_remainder(number):
"""
Captures the idea that diatonic numbers 1 (unison), 8 (octave), 15 (two octaves)
are all "types of octave."
Likewise, diatonic numbers 2 (second), 9 (second + octave), 16 (second + two octaves)
are all "types of (diatonic) second."
.. container:: example
>>> function = abjad.pitch._diatonic_number_to_octaves_and_diatonic_remainder
>>> for number in range(1, 22 + 1):
... octaves, diatonic_remainder = function(number)
... print(f"{number}: {octaves} octave(s) + {diatonic_remainder}")
1: 0 octave(s) + 1
2: 0 octave(s) + 2
3: 0 octave(s) + 3
4: 0 octave(s) + 4
5: 0 octave(s) + 5
6: 0 octave(s) + 6
7: 0 octave(s) + 7
8: 1 octave(s) + 1
9: 1 octave(s) + 2
10: 1 octave(s) + 3
11: 1 octave(s) + 4
12: 1 octave(s) + 5
13: 1 octave(s) + 6
14: 1 octave(s) + 7
15: 2 octave(s) + 1
16: 2 octave(s) + 2
17: 2 octave(s) + 3
18: 2 octave(s) + 4
19: 2 octave(s) + 5
20: 2 octave(s) + 6
21: 2 octave(s) + 7
22: 3 octave(s) + 1
"""
octaves = (number - 1) // 7
remainder = number - 7 * octaves
return octaves, remainder
def _diatonic_number_and_quality_to_semitones(number, quality):
"""
Setup:
>>> function = abjad.pitch._diatonic_number_and_quality_to_semitones
Number of semitones in perfect octaves:
>>> function(1, "P")
0
>>> function(8, "P")
12
>>> function(15, "P")
24
Number of semitones in diminished octaves:
>>> function(1, "d")
-1
>>> function(8, "d")
11
>>> function(15, "d")
23
Number of semitones in minor seconds:
>>> function(2, "m")
1
>>> function(9, "m")
13
>>> function(16, "m")
25
Number of semitones in major seconds:
>>> function(2, "M")
2
>>> function(9, "M")
14
>>> function(16, "M")
26
"""
octaves, diatonic_remainder = _diatonic_number_to_octaves_and_diatonic_remainder(
number
)
quality_to_semitones = _diatonic_number_to_quality_dictionary[diatonic_remainder]
semitones = quality_to_semitones[quality]
semitones += 12 * octaves
return semitones
# TODO: change to function; accommodate arbitrarily large number of semitones
_semitones_to_quality_and_diatonic_number = {
0: ("P", 1),
1: ("m", 2),
2: ("M", 2),
3: ("m", 3),
4: ("M", 3),
5: ("P", 4),
6: ("d", 5),
7: ("P", 5),
8: ("m", 6),
9: ("M", 6),
10: ("m", 7),
11: ("M", 7),
12: ("P", 8),
}
# TODO: change to function; accommodate arbitrarily large strings like "ddd"
_quality_abbreviation_to_quality_string = {
"M": "major",
"m": "minor",
"P": "perfect",
"aug": "augmented",
"dim": "diminished",
"A": "augmented",
"d": "diminished",
}
# TODO: change to function; accommodate strings like "double-diminished"
_quality_string_to_quality_abbreviation = {
"major": "M",
"minor": "m",
"perfect": "P",
"augmented": "A",
"diminished": "d",
}
# TODO: change to function; accommodate arbitrarily large number of semitones
_semitones_to_quality_string_and_number = {
0: ("perfect", 1),
1: ("minor", 2),
2: ("major", 2),
3: ("minor", 3),
4: ("major", 3),
5: ("perfect", 4),
6: ("diminished", 5),
7: ("perfect", 5),
8: ("minor", 6),
9: ("major", 6),
10: ("minor", 7),
11: ("major", 7),
}
### REGEX ATOMS ###
_integer_regex_atom = r"-?\d+"
_alphabetic_accidental_regex_atom = (
"(?P<alphabetic_accidental>[s]*(qs)?|[f]*(qf)?|t?q?[fs]|)"
)
_symbolic_accidental_regex_atom = "(?P<symbolic_accidental>[#]+[+]?|[b]+[~]?|[+]|[~]|)"
_octave_number_regex_atom = f"(?P<octave_number>{_integer_regex_atom}|)"
_octave_tick_regex_atom = "(?P<octave_tick>,+|'+|)"
_diatonic_pc_name_regex_atom = "(?P<diatonic_pc_name>[A-Ga-g])"
### REGEX BODIES ###
_comprehensive_accidental_regex_body = ("(?P<comprehensive_accidental>{}|{})").format(
_alphabetic_accidental_regex_atom, _symbolic_accidental_regex_atom
)
_comprehensive_octave_regex_body = ("(?P<comprehensive_octave>{}|{})").format(
_octave_number_regex_atom, _octave_tick_regex_atom
)
_comprehensive_pitch_class_name_regex_body = (
"(?P<comprehensive_pitch_class_name>{}{})"
).format(_diatonic_pc_name_regex_atom, _comprehensive_accidental_regex_body)
_comprehensive_pitch_name_regex_body = ("(?P<comprehensive_pitch_name>{}{}{})").format(
_diatonic_pc_name_regex_atom,
_comprehensive_accidental_regex_body,
_comprehensive_octave_regex_body,
)
_pitch_class_name_regex_body = ("(?P<pitch_class_name>{}{})").format(
_diatonic_pc_name_regex_atom, _alphabetic_accidental_regex_atom
)
_pitch_name_regex_body = ("(?P<pitch_name>{}{}{})").format(
_diatonic_pc_name_regex_atom,
_alphabetic_accidental_regex_atom,
_octave_tick_regex_atom,
)
_pitch_class_octave_number_regex_body = (
"(?P<pitch_class_octave_number>{}{}{})"
).format(
_diatonic_pc_name_regex_atom,
_comprehensive_accidental_regex_body,
_octave_number_regex_atom,
)
_pitch_class_octave_number_regex = re.compile(
"^{}$".format(_pitch_class_octave_number_regex_body), re.VERBOSE
)
_interval_name_abbreviation_regex_body = r"""
(?P<direction>[+,-]?) # one plus, one minus, or neither
(?P<quality> # exactly one quality abbreviation
M| # major
m| # minor
P| # perfect
aug| # augmented
A+| # (possibly) multi-augmented
dim| # dimished
d+ # (possibly) multi-diminished
)
(?P<quartertone>[+~]?) # followed by an optional quartertone inflection
(?P<number>\d+) # followed by one or more digits
"""
_range_string_regex_body = r"""
(?P<open_bracket>
[\[(] # open bracket or open parenthesis
)
(?P<start_pitch>
{}|{}|(?P<start_pitch_number>-?\d+) # start pitch
)
, # comma
[ ]* # any amount of whitespace
(?P<stop_pitch>
{}|{}|(?P<stop_pitch_number>-?\d+) # stop pitch
)
(?P<close_bracket>
[\])] # close bracket or close parenthesis
)
""".format(
_pitch_class_octave_number_regex_body.replace("<", "<us_start_"),
_pitch_name_regex_body.replace("<", "<ly_start_"),
_pitch_class_octave_number_regex_body.replace("<", "<us_stop_"),
_pitch_name_regex_body.replace("<", "<ly_stop_"),
)
_range_string_regex = re.compile("^{}$".format(_range_string_regex_body), re.VERBOSE)
### REGEX PATTERNS ###
_alphabetic_accidental_regex = re.compile(
"^{}$".format(_alphabetic_accidental_regex_atom), re.VERBOSE
)
_symbolic_accidental_regex = re.compile(
"^{}$".format(_symbolic_accidental_regex_atom), re.VERBOSE
)
_comprehensive_accidental_regex = re.compile(
"^{}$".format(_comprehensive_accidental_regex_body), re.VERBOSE
)
_octave_tick_regex = re.compile("^{}$".format(_octave_tick_regex_atom), re.VERBOSE)
_octave_number_regex = re.compile("^{}$".format(_octave_number_regex_atom), re.VERBOSE)
_diatonic_pc_name_regex = re.compile(
"^{}$".format(_diatonic_pc_name_regex_atom), re.VERBOSE
)
_comprehensive_accidental_regex = re.compile(
"^{}$".format(_comprehensive_accidental_regex_body), re.VERBOSE
)
_comprehensive_octave_regex = re.compile(
"^{}$".format(_comprehensive_octave_regex_body), re.VERBOSE
)
_comprehensive_pitch_class_name_regex = re.compile(
"^{}$".format(_comprehensive_pitch_class_name_regex_body), re.VERBOSE
)
_comprehensive_pitch_name_regex = re.compile(
"^{}$".format(_comprehensive_pitch_name_regex_body), re.VERBOSE
)
_pitch_class_name_regex = re.compile(
"^{}$".format(_pitch_class_name_regex_body), re.VERBOSE
)
_pitch_name_regex = re.compile("^{}$".format(_pitch_name_regex_body), re.VERBOSE)
_interval_name_abbreviation_regex = re.compile(
"^{}$".format(_interval_name_abbreviation_regex_body), re.VERBOSE
)
[docs]@functools.total_ordering
@dataclasses.dataclass(slots=True, unsafe_hash=True)
class Accidental:
"""
Accidental.
.. container:: example
>>> abjad.Accidental("ff")
Accidental(name='double flat')
>>> abjad.Accidental("tqf")
Accidental(name='three-quarters flat')
>>> abjad.Accidental("f")
Accidental(name='flat')
>>> abjad.Accidental("")
Accidental(name='natural')
>>> abjad.Accidental("qs")
Accidental(name='quarter sharp')
>>> abjad.Accidental("s")
Accidental(name='sharp')
>>> abjad.Accidental("tqs")
Accidental(name='three-quarters sharp')
>>> abjad.Accidental("ss")
Accidental(name='double sharp')
.. container:: example
Generalized accidentals are allowed:
>>> abjad.Accidental("ssss")
Accidental(name='ssss')
.. container:: example
Less than is true when ``argument`` is an accidental with semitones greater
than those of this accidental:
>>> accidental_1 = abjad.Accidental("f")
>>> accidental_2 = abjad.Accidental("f")
>>> accidental_3 = abjad.Accidental("s")
>>> accidental_1 < accidental_1
False
>>> accidental_1 < accidental_2
False
>>> accidental_1 < accidental_3
True
>>> accidental_2 < accidental_1
False
>>> accidental_2 < accidental_2
False
>>> accidental_2 < accidental_3
True
>>> accidental_3 < accidental_1
False
>>> accidental_3 < accidental_2
False
>>> accidental_3 < accidental_3
False
.. container:: example
>>> abjad.Accidental("ff").semitones
-2
>>> abjad.Accidental("tqf").semitones
-1.5
>>> abjad.Accidental("f").semitones
-1
>>> abjad.Accidental("").semitones
0
>>> abjad.Accidental("qs").semitones
0.5
>>> abjad.Accidental("s").semitones
1
>>> abjad.Accidental("tqs").semitones
1.5
>>> abjad.Accidental("ss").semitones
2
.. container:: example
>>> abjad.Accidental("ff").name
'double flat'
>>> abjad.Accidental("tqf").name
'three-quarters flat'
>>> abjad.Accidental("f").name
'flat'
>>> abjad.Accidental("").name
'natural'
>>> abjad.Accidental("qs").name
'quarter sharp'
>>> abjad.Accidental("s").name
'sharp'
>>> abjad.Accidental("tqs").name
'three-quarters sharp'
>>> abjad.Accidental("ss").name
'double sharp'
"""
name: str = "natural"
arrow: bool = dataclasses.field(default=False, repr=False)
semitones: int = dataclasses.field(compare=False, init=False, repr=False)
[docs] def __post_init__(self):
semitones = 0
_arrow = None
if self.name is None:
pass
elif isinstance(self.name, str):
if self.name in _accidental_name_to_abbreviation:
self.name = _accidental_name_to_abbreviation[self.name]
semitones = _accidental_abbreviation_to_semitones[self.name]
else:
match = _comprehensive_accidental_regex.match(self.name)
group_dict = match.groupdict()
if group_dict["alphabetic_accidental"]:
prefix, _, suffix = self.name.partition("q")
if prefix.startswith("s"):
semitones += len(prefix)
elif prefix.startswith("f"):
semitones -= len(prefix)
if suffix == "s":
semitones += 0.5
if prefix == "t":
semitones += 1
elif suffix == "f":
semitones -= 0.5
if prefix == "t":
semitones -= 1
elif group_dict["symbolic_accidental"]:
semitones += self.name.count("#")
semitones -= self.name.count("b")
if self.name.endswith("+"):
semitones += 0.5
elif self.name.endswith("~"):
semitones -= 0.5
elif isinstance(self.name, numbers.Number):
semitones = float(self.name)
assert (semitones % 1.0) in (0.0, 0.5)
elif hasattr(self.name, "accidental"):
_arrow = self.name.accidental.arrow
semitones = self.name.accidental.semitones
elif isinstance(self.name, type(self)):
_arrow = self.name.arrow
semitones = self.name.semitones
semitones = _math.integer_equivalent_number_to_integer(semitones)
self.semitones = semitones
if self.arrow is not None:
self.arrow = _string.to_tridirectional_ordinal_constant(self.arrow)
if self.arrow is _enums.CENTER:
self.arrow = None
else:
self.arrow = _arrow
try:
abbreviation = _accidental_semitones_to_abbreviation[self.semitones]
name = _accidental_abbreviation_to_name[abbreviation]
except KeyError:
name = str(self)
self.name = name
[docs] def __add__(self, argument):
"""
Adds ``argument`` to accidental.
.. container:: example
>>> accidental = abjad.Accidental("qs")
>>> accidental + accidental
Accidental(name='sharp')
>>> accidental + accidental + accidental
Accidental(name='three-quarters sharp')
Returns new accidental.
"""
if not isinstance(argument, type(self)):
raise TypeError("can only add accidental to other accidental.")
semitones = self.semitones + argument.semitones
return type(self)(semitones)
[docs] def __call__(self, argument):
"""
Calls accidental on ``argument``.
>>> accidental = abjad.Accidental("s")
.. container:: example
Calls accidental on pitches:
>>> accidental(abjad.NamedPitch("c''"))
NamedPitch("cs''")
>>> accidental(abjad.NamedPitch("cqs''"))
NamedPitch("ctqs''")
>>> accidental(abjad.NumberedPitch(12))
NumberedPitch(13)
>>> accidental(abjad.NumberedPitch(12.5))
NumberedPitch(13.5)
.. container:: example
Calls accidental on pitch-classes:
>>> accidental(abjad.NamedPitchClass("c"))
NamedPitchClass('cs')
>>> accidental(abjad.NamedPitchClass("cqs"))
NamedPitchClass('ctqs')
>>> accidental(abjad.NumberedPitchClass(0))
NumberedPitchClass(1)
>>> accidental(abjad.NumberedPitchClass(0.5))
NumberedPitchClass(1.5)
Returns new object of ``argument`` type.
"""
if not hasattr(argument, "_apply_accidental"):
raise TypeError(f"do not know how to apply accidental to {argument!r}.")
return argument._apply_accidental(self)
[docs] def __lt__(self, argument):
"""
Returns true or false.
"""
return self.semitones < argument.semitones
[docs] def __neg__(self):
"""
Negates accidental.
.. container:: example
>>> -abjad.Accidental("ff")
Accidental(name='double sharp')
>>> -abjad.Accidental("tqf")
Accidental(name='three-quarters sharp')
>>> -abjad.Accidental("f")
Accidental(name='sharp')
>>> -abjad.Accidental("")
Accidental(name='natural')
>>> -abjad.Accidental("qs")
Accidental(name='quarter flat')
>>> -abjad.Accidental("s")
Accidental(name='flat')
>>> -abjad.Accidental("tqs")
Accidental(name='three-quarters flat')
>>> -abjad.Accidental("ss")
Accidental(name='double flat')
Returns new accidental.
"""
return type(self)(-self.semitones)
[docs] def __radd__(self, argument):
"""
Raises not implemented error on accidental.
"""
raise NotImplementedError
# TODO: remove?
[docs] def __str__(self) -> str:
"""
Gets string representation of accidental.
.. container:: example
>>> str(abjad.Accidental("ff"))
'ff'
>>> str(abjad.Accidental("tqf"))
'tqf'
>>> str(abjad.Accidental("f"))
'f'
>>> str(abjad.Accidental(""))
''
>>> str(abjad.Accidental("qs"))
'qs'
>>> str(abjad.Accidental("s"))
's'
>>> str(abjad.Accidental("tqs"))
'tqs'
>>> str(abjad.Accidental("ss"))
'ss'
"""
if self.semitones in _accidental_semitones_to_abbreviation:
return _accidental_semitones_to_abbreviation[self.semitones]
character = "s"
if self.semitones < 0:
character = "f"
semitones, remainder = divmod(abs(self.semitones), 1.0)
abbreviation = character * int(semitones)
if remainder:
abbreviation += f"q{character}"
return abbreviation
[docs] def __sub__(self, argument):
"""
Subtracts ``argument`` from accidental.
.. container:: example
>>> accidental = abjad.Accidental("qs")
>>> accidental - accidental
Accidental(name='natural')
>>> accidental - accidental - accidental
Accidental(name='quarter flat')
Returns new accidental.
"""
if not isinstance(argument, type(self)):
raise TypeError("can only subtract accidental from other accidental.")
semitones = self.semitones - argument.semitones
return type(self)(semitones)
@staticmethod
def _get_all_accidental_abbreviations(class_):
return list(_accidental_abbreviation_to_symbol.keys())
@staticmethod
def _get_all_accidental_names(class_):
return list(_accidental_name_to_abbreviation.keys())
staticmethod
def _get_all_accidental_semitone_values(class_):
return list(_accidental_semitones_to_abbreviation.keys())
def _get_lilypond_format(self):
return self._abbreviation
@classmethod
def _is_abbreviation(class_, argument):
if not isinstance(argument, str):
return False
return bool(_alphabetic_accidental_regex.match(argument))
@classmethod
def _is_symbol(class_, argument):
if not isinstance(argument, str):
return False
return bool(_symbolic_accidental_regex.match(argument))
@property
def symbol(self) -> str:
"""
Gets symbol of accidental.
.. container:: example
>>> abjad.Accidental("ff").symbol
'bb'
>>> abjad.Accidental("tqf").symbol
'b~'
>>> abjad.Accidental("f").symbol
'b'
>>> abjad.Accidental("").symbol
''
>>> abjad.Accidental("qs").symbol
'+'
>>> abjad.Accidental("s").symbol
'#'
>>> abjad.Accidental("tqs").symbol
'#+'
>>> abjad.Accidental("ss").symbol
'##'
"""
abbreviation = _accidental_semitones_to_abbreviation[self.semitones]
symbol = _accidental_abbreviation_to_symbol[abbreviation]
return symbol
[docs]@dataclasses.dataclass(order=True, slots=True, unsafe_hash=True)
class Octave:
"""
Octave.
.. container:: example:
Initializes octave from integer:
>>> abjad.Octave(4)
Octave(number=4)
.. container:: example
Initializes octave from named pitch:
>>> abjad.Octave(abjad.NamedPitch("cs''"))
Octave(number=5)
.. container:: example
Initializes octave from other octave:
>>> abjad.Octave(abjad.Octave(2))
Octave(number=2)
.. container:: example
>>> octave_1 = abjad.Octave(4)
>>> octave_2 = abjad.Octave(4)
>>> octave_3 = abjad.Octave(5)
>>> octave_1 == octave_1
True
>>> octave_1 == octave_2
True
>>> octave_1 == octave_3
False
>>> octave_2 == octave_1
True
>>> octave_2 == octave_2
True
>>> octave_2 == octave_3
False
>>> octave_3 == octave_1
False
>>> octave_3 == octave_2
False
>>> octave_3 == octave_3
True
"""
number: int = 4
[docs] def __post_init__(self):
if isinstance(self.number, int | float):
self.number = int(self.number)
elif hasattr(self.number, "octave"):
self.number = self.number.octave.number
elif isinstance(self.number, type(self)):
self.number = self.number.number
else:
raise Exception(f"must be number or string: {self.number!r}.")
# TODO: remove
[docs] def __float__(self):
"""
Get octave number as float.
Returns float.
"""
return float(self.number)
# TODO: remove
[docs] def __int__(self):
"""
Get octave number integer.
Returns integer.
"""
return int(self.number)
def _is_tick_string(argument):
if not isinstance(argument, str):
return False
return bool(_octave_tick_regex.match(argument))
# TODO: replace with Octave.pitch that returns abjad.NamedPitch
@property
def pitch_number(self) -> int:
"""
Gets pitch number of first note in octave.
.. container:: example
>>> abjad.Octave(4).pitch_number
0
>>> abjad.Octave(5).pitch_number
12
>>> abjad.Octave(3).pitch_number
-12
"""
return (self.number - 4) * 12
@property
def ticks(self) -> str:
"""
Gets LilyPond octave tick string.
.. container:: example
>>> for i in range(-1, 9):
... print(i, abjad.Octave(i).ticks)
-1 ,,,,
0 ,,,
1 ,,
2 ,
3
4 '
5 ''
6 '''
7 ''''
8 '''''
"""
if 3 < self.number:
return "'" * (self.number - 3)
elif self.number < 3:
return "," * abs(3 - self.number)
return ""
[docs] @classmethod
def from_pitch(class_, pitch) -> "Octave":
"""
Makes octave from ``pitch``.
.. container:: example
>>> abjad.Octave.from_pitch("cs")
Octave(number=3)
>>> abjad.Octave.from_pitch("cs'")
Octave(number=4)
>>> abjad.Octave.from_pitch(1)
Octave(number=4)
>>> abjad.Octave.from_pitch(13)
Octave(number=5)
"""
if isinstance(pitch, int | float):
number = int(math.floor(pitch / 12)) + 4
return class_(number)
if hasattr(pitch, "name"):
name = pitch.name
elif isinstance(pitch, str):
name = pitch
else:
raise TypeError(pitch)
match = re.match(r"^([a-z]+)(\,*|'*)$", name)
assert match is not None, repr(match)
name, ticks = match.groups()
return class_.from_ticks(ticks)
[docs] @classmethod
def from_ticks(class_, ticks: str) -> "Octave":
"""
Makes octave from ``ticks`` string.
.. container:: example
>>> abjad.Octave.from_ticks("'")
Octave(number=4)
>>> abjad.Octave.from_ticks(",,")
Octave(number=1)
"""
assert isinstance(ticks, str), repr(ticks)
number = 3
if ticks.startswith("'"):
number += ticks.count("'")
else:
number -= ticks.count(",")
return class_(number)
[docs]@functools.total_ordering
class IntervalClass:
"""
Abstract interval-class.
"""
__slots__ = ()
_is_abstract = True
def __init__(self, argument):
if isinstance(argument, str):
match = _interval_name_abbreviation_regex.match(argument)
if match is None:
try:
argument = float(argument)
self._from_number(argument)
return
except ValueError:
message = (
f"can not initialize {type(self).__name__} from {argument!r}."
)
raise ValueError(message)
message = f"can not initialize {type(self).__name__} from {argument!r}."
raise ValueError(message)
group_dict = match.groupdict()
direction = group_dict["direction"]
if direction == "-":
direction = -1
else:
direction = 1
quality = group_dict["quality"]
diatonic_number = int(group_dict["number"])
quality = self._validate_quality_and_diatonic_number(
quality, diatonic_number
)
quartertone = group_dict["quartertone"]
quality += quartertone
self._from_named_parts(direction, quality, diatonic_number)
elif isinstance(argument, tuple) and len(argument) == 2:
quality, number = argument
direction = _math.sign(number)
diatonic_number = abs(number)
quality = self._validate_quality_and_diatonic_number(
quality, diatonic_number
)
self._from_named_parts(direction, quality, diatonic_number)
elif isinstance(argument, numbers.Number):
self._from_number(argument)
elif isinstance(argument, Interval | IntervalClass):
self._from_interval_or_interval_class(argument)
else:
message = f"can not initialize {type(self).__name__} from {argument!r}."
raise ValueError(message)
[docs] def __abs__(self):
"""
Gets absolute value of interval-class.
Returns new interval-class.
"""
return type(self)(abs(self._number))
[docs] def __eq__(self, argument) -> bool:
"""
Compares ``number``.
"""
if isinstance(argument, type(self)):
return self.number == argument.number
return False
[docs] def __hash__(self) -> int:
"""
Hashes interval-class.
"""
return hash(self.__class__.__name__ + repr(self))
[docs] def __lt__(self, argument) -> bool:
"""
Compares ``number``.
"""
assert isinstance(argument, type(self))
return self.number < argument.number
@staticmethod
def _named_to_numbered(direction, quality, diatonic_number):
octave_number = 0
diatonic_pc_number = abs(diatonic_number)
while diatonic_pc_number >= 8:
diatonic_pc_number -= 7
octave_number += 1
quartertone = ""
if quality.endswith(("+", "~")):
quality, quartertone = quality[:-1], quality[-1]
base_quality = quality
if base_quality == "P" and octave_number and diatonic_pc_number == 1:
return 12 * direction
if len(quality) > 1:
base_quality = quality[0]
semitones = _diatonic_number_and_quality_to_semitones(
diatonic_pc_number,
base_quality,
)
if base_quality == "d":
semitones -= len(quality) - 1
elif base_quality == "A":
semitones += len(quality) - 1
if quartertone == "+":
semitones += 0.5
elif quartertone == "~":
semitones -= 0.5
if abs(diatonic_number) == 1:
semitones = abs(semitones)
while abs(semitones) > 12:
semitones = (abs(semitones) - 12) * _math.sign(semitones)
semitones *= direction
return _math.integer_equivalent_number_to_integer(semitones)
@classmethod
def _numbered_to_named(cls, number):
number = cls._to_nearest_quarter_tone(float(number))
direction = _math.sign(number)
octaves, semitones = divmod(abs(number), 12)
if semitones == 0 and octaves:
semitones = 12
quartertone = ""
if semitones % 1:
semitones -= 0.5
quartertone = "+"
(
quality,
diatonic_pc_number,
) = _semitones_to_quality_and_diatonic_number[semitones]
quality += quartertone
diatonic_pc_number = cls._to_nearest_quarter_tone(diatonic_pc_number)
return direction, quality, diatonic_pc_number
@staticmethod
def _to_nearest_quarter_tone(number):
number = round(float(number) * 4) / 4
div, mod = divmod(number, 1)
if mod == 0.75:
div += 1
elif mod == 0.5:
div += 0.5
return _math.integer_equivalent_number_to_integer(div)
@classmethod
def _validate_quality_and_diatonic_number(cls, quality, diatonic_number):
if quality in _quality_string_to_quality_abbreviation:
quality = _quality_string_to_quality_abbreviation[quality]
if quality == "aug":
quality = "A"
if quality == "dim":
quality = "d"
octaves = 0
diatonic_pc_number = diatonic_number
while diatonic_pc_number > 7:
diatonic_pc_number -= 7
octaves += 1
quality_to_semitones = _diatonic_number_to_quality_dictionary[
diatonic_pc_number
]
if quality[0] not in quality_to_semitones:
name = cls.__name__
number = diatonic_number
message = f"can not initialize {name} from {quality!r} and {number!r}."
raise ValueError(message)
return quality
@property
def number(self):
"""
Gets number of interval-class.
Returns number.
"""
return self._number
[docs] def transpose(self, pitch_carrier):
"""
Transposes ``pitch_carrier`` by interval-class.
Returns new pitch carrier.
"""
if hasattr(pitch_carrier, "transpose"):
return pitch_carrier.transpose(self)
elif hasattr(pitch_carrier, "written_pitch"):
new_note = copy.copy(pitch_carrier)
new_pitch = pitch_carrier.written_pitch.transpose(self)
new_note.written_pitch = new_pitch
return new_note
elif hasattr(pitch_carrier, "written_pitches"):
new_chord = copy.copy(pitch_carrier)
pairs = zip(new_chord.note_heads, pitch_carrier.note_heads)
for new_nh, old_nh in pairs:
new_pitch = old_nh.written_pitch.transpose(self)
new_nh.written_pitch = new_pitch
return new_chord
return pitch_carrier
[docs]class NamedIntervalClass(IntervalClass):
"""
Named interval-class.
.. container:: example
Initializes from name:
>>> abjad.NamedIntervalClass("-M9")
NamedIntervalClass('-M2')
"""
__slots__ = ("_number", "_quality")
def __init__(self, name="P1"):
super().__init__(name or "P1")
[docs] def __abs__(self):
"""
Gets absolute value of named interval-class.
.. container:: example
>>> abs(abjad.NamedIntervalClass("-M9"))
NamedIntervalClass('+M2')
Returns new named interval-class.
"""
return type(self)((self.quality, abs(self.number)))
[docs] def __add__(self, argument):
"""
Adds ``argument`` to named interval-class.
Returns new named interval-class.
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
dummy_pitch = NamedPitch(0)
new_pitch = dummy_pitch + self + argument
interval = NamedInterval.from_pitch_carriers(dummy_pitch, new_pitch)
return type(self)(interval)
[docs] def __eq__(self, argument):
"""
Is true when ``argument`` is a named interval-class with direction
number, quality string and number equal to those of this named
interval-class.
.. container:: example
>>> interval_class_1 = abjad.NamedIntervalClass("P1")
>>> interval_class_2 = abjad.NamedIntervalClass("P1")
>>> interval_class_3 = abjad.NamedIntervalClass("m2")
>>> interval_class_1 == interval_class_1
True
>>> interval_class_1 == interval_class_2
True
>>> interval_class_1 == interval_class_3
False
>>> interval_class_2 == interval_class_1
True
>>> interval_class_2 == interval_class_2
True
>>> interval_class_2 == interval_class_3
False
>>> interval_class_3 == interval_class_1
False
>>> interval_class_3 == interval_class_2
False
>>> interval_class_3 == interval_class_3
True
Returns true or false.
"""
return super().__eq__(argument)
# TODO: remove
[docs] def __float__(self):
"""
Coerce to float.
Returns float.
"""
return float(
self._named_to_numbered(
self.direction_number, self._quality, abs(self._number)
)
)
[docs] def __hash__(self):
"""
Hashes named interval-class.
Returns integer.
"""
return super().__hash__()
[docs] def __lt__(self, argument) -> bool:
"""
Is true when ``argument`` is a named interval class with a number greater than
that of this named interval.
.. container:: example
>>> interval_class_1 = abjad.NamedIntervalClass("P1")
>>> interval_class_2 = abjad.NamedIntervalClass("P1")
>>> interval_class_3 = abjad.NamedIntervalClass("m2")
>>> interval_class_1 < interval_class_1
False
>>> interval_class_1 < interval_class_2
False
>>> interval_class_1 < interval_class_3
True
>>> interval_class_2 < interval_class_1
False
>>> interval_class_2 < interval_class_2
False
>>> interval_class_2 < interval_class_3
True
>>> interval_class_3 < interval_class_1
False
>>> interval_class_3 < interval_class_2
False
>>> interval_class_3 < interval_class_3
False
"""
try:
argument = type(self)(argument)
except Exception:
return False
if self.number == argument.number:
self_semitones = NamedInterval(self).semitones
argument_semitones = NamedInterval(argument).semitones
return self_semitones < argument_semitones
return self.number < argument.number
[docs] def __radd__(self, argument):
"""
Adds interval-class to ``argument``
Returns new named interval-class.
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return argument.__add__(self)
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.name!r})"
[docs] def __sub__(self, argument):
"""
Subtracts ``argument`` from named interval-class.
Returns new named interval-class.
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
dummy_pitch = NamedPitch(0)
new_pitch = dummy_pitch + self - argument
interval = NamedInterval.from_pitch_carriers(dummy_pitch, new_pitch)
return type(self)(interval)
def _from_interval_or_interval_class(self, argument):
try:
quality = argument.quality
diatonic_number = abs(argument.number)
direction = _math.sign(argument.number)
except AttributeError:
direction, quality, diatonic_number = self._numbered_to_named(argument)
self._from_named_parts(direction, quality, diatonic_number)
def _from_named_parts(self, direction, quality, diatonic_number):
self._quality = quality
diatonic_pc_number = diatonic_number
while diatonic_pc_number > 7:
diatonic_pc_number -= 7
if diatonic_pc_number == 1 and diatonic_number >= 8:
if quality == "P":
diatonic_pc_number = 8
elif quality.startswith("d") or quality == "P~":
direction *= -1
if not (diatonic_number == 1 and quality == "P"):
diatonic_pc_number *= direction
self._number = diatonic_pc_number
def _from_number(self, argument):
direction, quality, diatonic_number = self._numbered_to_named(argument)
self._from_named_parts(direction, quality, diatonic_number)
@property
def direction_number(self):
"""
Gets direction number of named interval-class.
.. container:: example
>>> abjad.NamedIntervalClass("P1").direction_number
0
>>> abjad.NamedIntervalClass("+M2").direction_number
1
>>> abjad.NamedIntervalClass("-M2").direction_number
-1
Returns -1, 0 or 1.
"""
if self.quality == "P" and abs(self.number) == 1:
return 0
return _math.sign(self.number)
@property
def name(self):
"""
Gets name of named interval-class.
.. container:: example
>>> abjad.NamedIntervalClass("-M9").name
'-M2'
Returns string.
"""
return "{}{}{}".format(
_direction_number_to_direction_symbol[self.direction_number],
self._quality,
abs(self.number),
)
@property
def quality(self):
"""
Gets quality of named interval-class.
Returns string.
"""
return self._quality
[docs] @classmethod
def from_pitch_carriers(class_, pitch_carrier_1, pitch_carrier_2):
"""
Makes named interval-class from ``pitch_carrier_1`` and
``pitch_carrier_2``
.. container:: example
>>> abjad.NamedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(-2),
... abjad.NamedPitch(12),
... )
NamedIntervalClass('+M2')
.. container:: example
>>> abjad.NamedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(0),
... abjad.NamedPitch(12),
... )
NamedIntervalClass('+P8')
.. container:: example
>>> abjad.NamedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(12),
... )
NamedIntervalClass('P1')
.. container:: example
>>> abjad.NamedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(-3),
... )
NamedIntervalClass('-m3')
.. container:: example
>>> abjad.NamedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(9),
... )
NamedIntervalClass('-m3')
Returns newly constructed named interval-class.
"""
named_interval = NamedInterval.from_pitch_carriers(
pitch_carrier_1, pitch_carrier_2
)
return class_(named_interval)
[docs]class NamedInversionEquivalentIntervalClass(NamedIntervalClass):
"""
Named inversion-equivalent interval-class.
.. container:: example
Initializes from string:
>>> abjad.NamedInversionEquivalentIntervalClass("-m14")
NamedInversionEquivalentIntervalClass('+M2')
.. container:: example
Initializes from pair:
>>> abjad.NamedInversionEquivalentIntervalClass(("perfect", 1))
NamedInversionEquivalentIntervalClass('P1')
>>> abjad.NamedInversionEquivalentIntervalClass(("perfect", -1))
NamedInversionEquivalentIntervalClass('P1')
>>> abjad.NamedInversionEquivalentIntervalClass(("augmented", 4))
NamedInversionEquivalentIntervalClass('+A4')
>>> abjad.NamedInversionEquivalentIntervalClass(("augmented", -4))
NamedInversionEquivalentIntervalClass('+A4')
>>> abjad.NamedInversionEquivalentIntervalClass(("augmented", 11))
NamedInversionEquivalentIntervalClass('+A4')
>>> abjad.NamedInversionEquivalentIntervalClass(("augmented", -11))
NamedInversionEquivalentIntervalClass('+A4')
.. container:: example
Initializes from other interval-class:
>>> interval_class = abjad.NamedInversionEquivalentIntervalClass("P1")
>>> abjad.NamedInversionEquivalentIntervalClass(interval_class)
NamedInversionEquivalentIntervalClass('P1')
"""
__slots__ = ()
def __init__(self, name="P1"):
super().__init__(name or "P1")
self._quality, self._number = self._process_quality_and_number(
self._quality, self._number
)
[docs] def __eq__(self, argument):
"""
Compares ``name``.
.. container:: example
>>> class_ = abjad.NamedInversionEquivalentIntervalClass
>>> interval_class_1 = class_("P1")
>>> interval_class_2 = class_("P1")
>>> interval_class_3 = class_("m2")
>>> interval_class_1 == interval_class_1
True
>>> interval_class_1 == interval_class_2
True
>>> interval_class_1 == interval_class_3
False
>>> interval_class_2 == interval_class_1
True
>>> interval_class_2 == interval_class_2
True
>>> interval_class_2 == interval_class_3
False
>>> interval_class_3 == interval_class_1
False
>>> interval_class_3 == interval_class_2
False
>>> interval_class_3 == interval_class_3
True
Returns true or false.
"""
return super().__eq__(argument)
[docs] def __hash__(self):
"""
Hashes named inversion-equivalent interval-class.
Returns integer.
"""
return super().__hash__()
@classmethod
def _invert_quality_string(class_, quality):
inversions = {"M": "m", "m": "M", "P": "P"}
if quality in inversions:
return inversions[quality]
if quality[0] == "A":
return "d" * len(quality)
return "A" * len(quality)
@classmethod
def _is_representative_number(class_, argument):
if isinstance(argument, numbers.Number):
if 1 <= argument <= 4 or argument == 8:
return True
return False
@classmethod
def _process_quality_and_number(class_, quality, number):
if number == 0:
raise ValueError("named interval can not equal zero.")
elif abs(number) == 1:
number = 1
elif abs(number) % 7 == 0:
number = 7
elif abs(number) % 7 == 1:
number = 8
else:
number = abs(number) % 7
if class_._is_representative_number(number):
quality = quality
number = number
else:
quality = class_._invert_quality_string(quality)
number = 9 - number
return quality, number
[docs] @classmethod
def from_pitch_carriers(class_, pitch_carrier_1, pitch_carrier_2):
"""
Makes named inversion-equivalent interval-class from ``pitch_carrier_1`` and
``pitch_carrier_2``.
.. container:: example
>>> class_ = abjad.NamedInversionEquivalentIntervalClass
>>> class_.from_pitch_carriers(
... abjad.NamedPitch(-2),
... abjad.NamedPitch(12),
... )
NamedInversionEquivalentIntervalClass('+M2')
Returns new named inversion-equivalent interval-class.
"""
named_interval = NamedInterval.from_pitch_carriers(
pitch_carrier_1, pitch_carrier_2
)
string = named_interval.name
return class_(string)
[docs]class NumberedIntervalClass(IntervalClass):
"""
Numbered interval-class.
.. container:: example
Initializes from integer:
>>> abjad.NumberedIntervalClass(-14)
NumberedIntervalClass(-2)
.. container:: example
Initializes from float:
>>> abjad.NumberedIntervalClass(-14.5)
NumberedIntervalClass(-2.5)
.. container:: example
Initializes from string:
>>> abjad.NumberedIntervalClass("-14.5")
NumberedIntervalClass(-2.5)
>>> abjad.NumberedIntervalClass("P8")
NumberedIntervalClass(12)
>>> abjad.NumberedIntervalClass("-P8")
NumberedIntervalClass(-12)
"""
__slots__ = ("_number",)
def __init__(self, number=0):
super().__init__(number or 0)
[docs] def __abs__(self):
"""
Gets absolute value of numbered interval-class.
Returns new numbered interval-class.
"""
return type(self)(abs(self.number))
[docs] def __add__(self, argument):
"""
Adds ``argument`` to numbered interval-class.
Returns new numbered interval-class.
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return type(self)(float(self) + float(argument))
[docs] def __eq__(self, argument) -> bool:
"""
Compares ``number``.
.. container:: example
>>> interval_class_1 = abjad.NumberedIntervalClass(0)
>>> interval_class_2 = abjad.NumberedIntervalClass(0)
>>> interval_class_3 = abjad.NumberedIntervalClass(1)
>>> interval_class_1 == interval_class_1
True
>>> interval_class_1 == interval_class_2
True
>>> interval_class_1 == interval_class_3
False
>>> interval_class_2 == interval_class_1
True
>>> interval_class_2 == interval_class_2
True
>>> interval_class_2 == interval_class_3
False
>>> interval_class_3 == interval_class_1
False
>>> interval_class_3 == interval_class_2
False
>>> interval_class_3 == interval_class_3
True
"""
return super().__eq__(argument)
# TODO: remove
[docs] def __float__(self):
"""
Coerce to semitones as float.
Returns float.
"""
return float(self._number)
[docs] def __hash__(self):
"""
Hashes numbered interval-class.
Returns integer.
"""
return super().__hash__()
[docs] def __lt__(self, argument):
"""
Compares ``number``.
.. container:: example
>>> interval_class_1 = abjad.NumberedIntervalClass(0)
>>> interval_class_2 = abjad.NumberedIntervalClass(0)
>>> interval_class_3 = abjad.NumberedIntervalClass(1)
>>> interval_class_1 < interval_class_1
False
>>> interval_class_1 < interval_class_2
False
>>> interval_class_1 < interval_class_3
True
>>> interval_class_2 < interval_class_1
False
>>> interval_class_2 < interval_class_2
False
>>> interval_class_2 < interval_class_3
True
>>> interval_class_3 < interval_class_1
False
>>> interval_class_3 < interval_class_2
False
>>> interval_class_3 < interval_class_3
False
Returns true or false.
"""
try:
argument = type(self)(argument)
except Exception:
return False
return self.number < argument.number
[docs] def __radd__(self, argument):
"""
Adds ``argument`` to numbered interval-class.
Returns new numbered interval-class.
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return type(self)(float(self) + float(argument))
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.number!r})"
[docs] def __sub__(self, argument):
"""
Subtracts ``argument`` from numbered interval-class.
Returns new numbered interval-class.
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return type(self)(float(self) - float(argument))
def _from_interval_or_interval_class(self, argument):
self._from_number(float(argument))
def _from_named_parts(self, direction, quality, diatonic_number):
self._number = self._named_to_numbered(direction, quality, diatonic_number)
def _from_number(self, argument):
direction = _math.sign(argument)
number = self._to_nearest_quarter_tone(abs(argument))
pc_number = number % 12
if pc_number == 0 and number:
pc_number = 12
self._number = pc_number * direction
@property
def direction_number(self) -> int:
"""
Gets direction number of numbered interval-class.
Returns -1, 0 or 1.
"""
if self.number < 1:
return -1
elif self.number == 1:
return 0
else:
return 1
@property
def signed_string(self):
"""
Gets signed string.
"""
direction_symbol = _direction_number_to_direction_symbol[
_math.sign(self.number)
]
return f"{direction_symbol}{abs(self.number)}"
[docs] @classmethod
def from_pitch_carriers(
class_, pitch_carrier_1, pitch_carrier_2
) -> "NumberedIntervalClass":
"""
Makes numbered interval-class from ``pitch_carrier_1`` and ``pitch_carrier_2``
.. container:: example
>>> abjad.NumberedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(-2),
... abjad.NamedPitch(12),
... )
NumberedIntervalClass(2)
>>> abjad.NumberedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(0),
... abjad.NamedPitch(12),
... )
NumberedIntervalClass(12)
>>> abjad.NumberedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(9),
... abjad.NamedPitch(12),
... )
NumberedIntervalClass(3)
>>> abjad.NumberedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(9),
... )
NumberedIntervalClass(-3)
>>> abjad.NumberedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(12),
... )
NumberedIntervalClass(0)
>>> abjad.NumberedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(24),
... abjad.NamedPitch(0),
... )
NumberedIntervalClass(-12)
>>> abjad.NumberedIntervalClass.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(-2),
... )
NumberedIntervalClass(-2)
Returns numbered interval-class.
"""
interval = NumberedInterval.from_pitch_carriers(
pitch_carrier_1, pitch_carrier_2
)
return class_(interval)
[docs]class NumberedInversionEquivalentIntervalClass(NumberedIntervalClass):
"""
Numbered inversion-equivalent interval-class.
.. container:: example
Initializes from integer:
>>> abjad.NumberedInversionEquivalentIntervalClass(0)
NumberedInversionEquivalentIntervalClass(0)
>>> abjad.NumberedInversionEquivalentIntervalClass(1)
NumberedInversionEquivalentIntervalClass(1)
.. container:: example
Initializes from float:
>>> abjad.NumberedInversionEquivalentIntervalClass(1.5)
NumberedInversionEquivalentIntervalClass(1.5)
.. container:: example
Initializes from string:
>>> abjad.NumberedInversionEquivalentIntervalClass("1")
NumberedInversionEquivalentIntervalClass(1)
"""
__slots__ = ()
def __init__(self, number=0):
super().__init__(number or 0)
self._number %= 12
if 6 < self._number:
self._number = 12 - self._number
[docs] def __abs__(self):
"""
Gets absolute value of numbered inversion-equivalent
interval-class.
.. container:: example
>>> abs(abjad.NumberedInversionEquivalentIntervalClass(0))
NumberedInversionEquivalentIntervalClass(0)
>>> abs(abjad.NumberedInversionEquivalentIntervalClass(1.5))
NumberedInversionEquivalentIntervalClass(1.5)
Returns new numbered inversion-equivalent interval-class.
"""
return type(self)(abs(self.number))
[docs] def __lt__(self, argument):
"""
Compares ``number``.
"""
if isinstance(argument, type(self)):
return self.number < argument.number
return False
[docs] def __neg__(self):
"""
Negates numbered inversion-equivalent interval-class.
.. container:: example
>>> -abjad.NumberedInversionEquivalentIntervalClass(0)
NumberedInversionEquivalentIntervalClass(0)
>>> -abjad.NumberedInversionEquivalentIntervalClass(1.5)
NumberedInversionEquivalentIntervalClass(1.5)
Returns new numbered inversion-equivalent interval-class.
"""
return type(self)(self.number)
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.number!r})"
[docs]@functools.total_ordering
class Interval:
"""
Abstract interval.
"""
__slots__ = ("_interval_class", "_octaves")
_is_abstract = True
def __init__(self, argument):
if isinstance(argument, str):
match = _interval_name_abbreviation_regex.match(argument)
if match is None:
try:
argument = float(argument)
self._from_number(argument)
return
except ValueError:
class_name = type(self).__name__
message = f"can not initialize {class_name} from {argument!r}."
raise ValueError(message)
class_name = type(self).__name__
message = f"can not initialize {class_name} from {argument!r}."
raise ValueError(message)
group_dict = match.groupdict()
direction = group_dict["direction"]
if direction == "-":
direction = -1
else:
direction = 1
quality = group_dict["quality"]
diatonic_number = int(group_dict["number"])
quality = self._validate_quality_and_diatonic_number(
quality, diatonic_number
)
quartertone = group_dict["quartertone"]
quality += quartertone
self._from_named_parts(direction, quality, diatonic_number)
elif isinstance(argument, tuple) and len(argument) == 2:
quality, number = argument
direction = _math.sign(number)
diatonic_number = abs(number)
quality = self._validate_quality_and_diatonic_number(
quality, diatonic_number
)
self._from_named_parts(direction, quality, diatonic_number)
elif isinstance(argument, numbers.Number):
self._from_number(argument)
else:
self._from_interval_or_interval_class(argument)
[docs] def __eq__(self, argument) -> bool:
"""
Compares repr formats.
"""
if isinstance(argument, type(self)):
return repr(self) == repr(argument)
return False
[docs] def __hash__(self) -> int:
"""
Hashes interval.
"""
return hash(self.__class__.__name__ + repr(self))
[docs] def __lt__(self, argument):
"""
Is true when interval is less than ``argument``
Returns true or false.
"""
raise NotImplementedError
def _from_interval_or_interval_class(self, argument):
raise NotImplementedError
def _from_named_parts(self, direction, quality, diatonic_number):
raise NotImplementedError
def _from_number(self, argument):
raise NotImplementedError
@staticmethod
def _named_to_numbered(direction, quality, diatonic_number):
octave_number = 0
diatonic_pc_number = abs(diatonic_number)
while diatonic_pc_number >= 8:
diatonic_pc_number -= 7
octave_number += 1
quartertone = ""
if quality.endswith(("+", "~")):
quality, quartertone = quality[:-1], quality[-1]
base_quality = quality
if len(quality) > 1:
base_quality = quality[0]
semitones = _diatonic_number_and_quality_to_semitones(
diatonic_pc_number, base_quality
)
if base_quality == "d":
semitones -= len(quality) - 1
elif base_quality == "A":
semitones += len(quality) - 1
if quartertone == "+":
semitones += 0.5
elif quartertone == "~":
semitones -= 0.5
if abs(diatonic_number) == 1:
semitones = abs(semitones)
else:
semitones += octave_number * 12
semitones *= direction
return _math.integer_equivalent_number_to_integer(semitones)
@classmethod
def _numbered_to_named(cls, number):
number = cls._to_nearest_quarter_tone(float(number))
direction = _math.sign(number)
octaves, semitones = divmod(abs(number), 12)
quartertone = ""
if semitones % 1:
semitones -= 0.5
quartertone = "+"
(
quality,
diatonic_number,
) = _semitones_to_quality_and_diatonic_number[semitones]
quality += quartertone
diatonic_number += octaves * 7
diatonic_number = cls._to_nearest_quarter_tone(diatonic_number)
return direction, quality, diatonic_number
@staticmethod
def _to_nearest_quarter_tone(number):
number = round(float(number) * 4) / 4
div, mod = divmod(number, 1)
if mod == 0.75:
div += 1
elif mod == 0.5:
div += 0.5
return _math.integer_equivalent_number_to_integer(div)
@classmethod
def _validate_quality_and_diatonic_number(cls, quality, diatonic_number):
if quality in _quality_string_to_quality_abbreviation:
quality = _quality_string_to_quality_abbreviation[quality]
if quality == "aug":
quality = "A"
if quality == "dim":
quality = "d"
octaves = 0
diatonic_pc_number = diatonic_number
while diatonic_pc_number > 7:
diatonic_pc_number -= 7
octaves += 1
quality_to_semitones = _diatonic_number_to_quality_dictionary[
diatonic_pc_number
]
if quality[0] not in quality_to_semitones:
name = cls.__name__
number = diatonic_number
message = f"can not initialize {name} from {quality!r} and {number!r}."
raise ValueError(message)
return quality
@property
def cents(self):
"""
Gets cents of interval.
Returns nonnegative number.
"""
return 100 * self.semitones
@property
def direction_number(self):
"""
Gets direction number of interval
Returns integer.
"""
raise NotImplementedError
@property
def interval_class(self):
"""
Gets interval-class of interval.
Returns interval-class.
"""
raise NotImplementedError
@property
def number(self):
"""
Gets number of interval.
Returns integer.
"""
raise NotImplementedError
@property
def octaves(self):
"""
Gets octaves of interval.
Returns nonnegative number.
"""
raise NotImplementedError
@property
def semitones(self):
"""
Gets semitones of interval.
Returns integer or float.
"""
raise NotImplementedError
[docs] def transpose(self, pitch_carrier):
"""
Transposes ``pitch_carrier`` by interval.
Returns new pitch carrier.
"""
if hasattr(pitch_carrier, "transpose"):
return pitch_carrier.transpose(self)
elif hasattr(pitch_carrier, "written_pitch"):
new_note = copy.copy(pitch_carrier)
new_pitch = pitch_carrier.written_pitch.transpose(self)
new_note.written_pitch = new_pitch
return new_note
elif hasattr(pitch_carrier, "written_pitches"):
new_chord = copy.copy(pitch_carrier)
pairs = zip(new_chord.note_heads, pitch_carrier.note_heads)
for new_nh, old_nh in pairs:
new_pitch = old_nh.written_pitch.transpose(self)
new_nh.written_pitch = new_pitch
return new_chord
return pitch_carrier
[docs]class NamedInterval(Interval):
"""
Named interval.
.. container:: example
Initializes ascending major ninth from string:
>>> abjad.NamedInterval("+M9")
NamedInterval('+M9')
.. container:: example
Initializes descending major third from number of semitones:
>>> abjad.NamedInterval(-4)
NamedInterval('-M3')
.. container:: example
Initializes from other named interval:
>>> abjad.NamedInterval(abjad.NamedInterval(-4))
NamedInterval('-M3')
.. container:: example
Initializes from numbered interval:
>>> abjad.NamedInterval(abjad.NumberedInterval(3))
NamedInterval('+m3')
.. container:: example
Initializes from pair of quality and diatonic number:
>>> abjad.NamedInterval(("M", 3))
NamedInterval('+M3')
"""
__slots__ = ()
def __init__(self, name="P1"):
super().__init__(name or "P1")
[docs] def __abs__(self) -> "NamedInterval":
"""
Gets absolute value of named interval.
.. container:: example
>>> abs(abjad.NamedInterval("+M9"))
NamedInterval('+M9')
>>> abs(abjad.NamedInterval("-M9"))
NamedInterval('+M9')
"""
return type(self)((self.quality, abs(self.number)))
[docs] def __add__(self, argument) -> "NamedInterval":
"""
Adds ``argument`` to named interval.
.. container:: example
>>> abjad.NamedInterval("M9") + abjad.NamedInterval("M2")
NamedInterval('+M10')
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
dummy_pitch = NamedPitch(0)
new_pitch = dummy_pitch + self + argument
return NamedInterval.from_pitch_carriers(dummy_pitch, new_pitch)
[docs] def __copy__(self, *arguments) -> "NamedInterval":
"""
Copies named interval.
>>> import copy
.. container:: example
>>> copy.copy(abjad.NamedInterval("+M9"))
NamedInterval('+M9')
"""
return type(self)((self.quality, self.number))
[docs] def __eq__(self, argument) -> bool:
"""
Compares ``name``.
.. container:: example
>>> interval_1 = abjad.NamedInterval("m2")
>>> interval_2 = abjad.NamedInterval("m2")
>>> interval_3 = abjad.NamedInterval("m9")
>>> interval_1 == interval_1
True
>>> interval_1 == interval_2
True
>>> interval_1 == interval_3
False
>>> interval_2 == interval_1
True
>>> interval_2 == interval_2
True
>>> interval_2 == interval_3
False
>>> interval_3 == interval_1
False
>>> interval_3 == interval_2
False
>>> interval_3 == interval_3
True
"""
if isinstance(argument, type(self)):
return self.name == argument.name
return False
# TODO: remove
[docs] def __float__(self):
"""
Coerce to semitones as float.
Returns float.
"""
return float(self.semitones)
[docs] def __hash__(self):
"""
Hashes named interval.
Returns number.
"""
return super().__hash__()
[docs] def __lt__(self, argument) -> bool:
"""
Compares ``semitones``.
.. container:: example
>>> abjad.NamedInterval("+M9") < abjad.NamedInterval("+M10")
True
>>> abjad.NamedInterval("+m9") < abjad.NamedInterval("+M9")
True
>>> abjad.NamedInterval("+M9") < abjad.NamedInterval("+M2")
False
"""
if isinstance(argument, type(self)):
return self.semitones < argument.semitones
return False
[docs] def __mul__(self, argument) -> "NamedInterval":
"""
Multiplies named interval by ``argument``
.. container:: example
>>> 3 * abjad.NamedInterval("+M9")
NamedInterval('+A25')
"""
if not isinstance(argument, int):
raise TypeError(f"must be integer: {argument!r}.")
dummy_pitch = NamedPitch(0)
for i in range(abs(argument)):
dummy_pitch += self
result = NamedInterval.from_pitch_carriers(NamedPitch(0), dummy_pitch)
if argument < 0:
return -result
return result
[docs] def __neg__(self) -> "NamedInterval":
"""
Negates named interval.
.. container:: example
>>> -abjad.NamedInterval("+M9")
NamedInterval('-M9')
>>> -abjad.NamedInterval("-M9")
NamedInterval('+M9')
"""
return type(self)((self.quality, -self.number))
[docs] def __radd__(self, argument) -> "NamedInterval":
"""
Adds named interval to ``argument``
.. container:: example
>>> abjad.NamedInterval("M9") + abjad.NamedInterval("M2")
NamedInterval('+M10')
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return argument.__add__(self)
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.name!r})"
[docs] def __rmul__(self, argument) -> "NamedInterval":
"""
Multiplies ``argument`` by named interval.
.. container:: example
>>> abjad.NamedInterval("+M9") * 3
NamedInterval('+A25')
"""
return self * argument
[docs] def __sub__(self, argument) -> "NamedInterval":
"""
Subtracts ``argument`` from named interval.
.. container:: example
>>> abjad.NamedInterval("+M9") - abjad.NamedInterval("+M2")
NamedInterval('+P8')
>>> abjad.NamedInterval("+M2") - abjad.NamedInterval("+M9")
NamedInterval('-P8')
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
dummy_pitch = NamedPitch(0)
new_pitch = dummy_pitch + self - argument
return NamedInterval.from_pitch_carriers(dummy_pitch, new_pitch)
def _from_interval_or_interval_class(self, argument):
try:
quality = argument.quality
diatonic_number = abs(argument.number)
direction = _math.sign(argument.number)
except AttributeError:
direction, quality, diatonic_number = self._numbered_to_named(argument)
self._from_named_parts(direction, quality, diatonic_number)
def _from_named_parts(self, direction, quality, diatonic_number):
octaves = 0
diatonic_pc_number = abs(diatonic_number)
while diatonic_pc_number > 7:
octaves += 1
diatonic_pc_number -= 7
if diatonic_pc_number == 1 and quality == "P" and diatonic_number >= 8:
octaves -= 1
diatonic_pc_number = 8
self._octaves = octaves
if direction:
diatonic_pc_number *= direction
self._interval_class = NamedIntervalClass((quality, diatonic_pc_number))
def _from_number(self, argument):
direction, quality, diatonic_number = self._numbered_to_named(argument)
self._from_named_parts(direction, quality, diatonic_number)
@property
def direction_number(self) -> int:
"""
Gets direction number of named interval.
.. container:: example
>>> abjad.NamedInterval("+M9").direction_number
1
>>> abjad.NamedInterval("+dim2").direction_number
1
>>> abjad.NamedInterval("+A1").direction_number
1
>>> abjad.NamedInterval("P1").direction_number
0
>>> abjad.NamedInterval("-m3").direction_number
-1
"""
if self.quality == "P" and abs(self.number) == 1:
return 0
return _math.sign(self.number)
@property
def interval_class(self) -> "NamedIntervalClass":
"""
Gets named interval class.
.. container:: example
>>> abjad.NamedInterval("+M9").interval_class
NamedIntervalClass('+M2')
>>> abjad.NamedInterval("-M9").interval_class
NamedIntervalClass('-M2')
>>> abjad.NamedInterval("P1").interval_class
NamedIntervalClass('P1')
>>> abjad.NamedInterval("+P8").interval_class
NamedIntervalClass('+P8')
"""
return self._interval_class
@property
def name(self) -> str:
"""
Gets name of named interval.
.. container:: example
>>> abjad.NamedInterval("+M9").name
'+M9'
"""
direction_symbol = _direction_number_to_direction_symbol[self.direction_number]
return "{}{}{}".format(
direction_symbol, self._interval_class.quality, abs(self.number)
)
@property
def number(self) -> int | float:
"""
Gets number of named interval.
.. container:: example
>>> abjad.NamedInterval("+M9").number
9
"""
number = self._interval_class._number
direction = _math.sign(number)
number = abs(number) + (7 * self.octaves)
return number * direction
@property
def octaves(self) -> int:
"""
Gets octaves of interval.
"""
return self._octaves
@property
def quality(self) -> str:
"""
Gets quality of named interval.
"""
return self._interval_class.quality
@property
def semitones(self) -> int:
"""
Gets semitones of named interval.
.. container:: example
>>> abjad.NamedInterval("+M9").semitones
14
>>> abjad.NamedInterval("-M9").semitones
-14
>>> abjad.NamedInterval("P1").semitones
0
>>> abjad.NamedInterval("+P8").semitones
12
>>> abjad.NamedInterval("-P8").semitones
-12
"""
direction = self.direction_number
diatonic_number = abs(self._interval_class._number)
quality = self._validate_quality_and_diatonic_number(
self.quality, diatonic_number
)
diatonic_number += 7 * self._octaves
return self._named_to_numbered(direction, quality, diatonic_number)
@property
def staff_spaces(self) -> float | int:
"""
Gets staff spaces of named interval.
.. container:: example
>>> abjad.NamedInterval("+M9").staff_spaces
8
>>> abjad.NamedInterval("-M9").staff_spaces
-8
>>> abjad.NamedInterval("P1").staff_spaces
0
>>> abjad.NamedInterval("+P8").staff_spaces
7
>>> abjad.NamedInterval("-P8").staff_spaces
-7
"""
if self.direction_number == -1:
return self.number + 1
elif not self.direction_number:
return 0
else:
assert self.direction_number == 1
return self.number - 1
[docs] @classmethod
def from_pitch_carriers(
class_, pitch_carrier_1, pitch_carrier_2
) -> "NamedInterval":
"""
Makes named interval calculated from ``pitch_carrier_1`` to ``pitch_carrier_2``.
.. container:: example
>>> abjad.NamedInterval.from_pitch_carriers(
... abjad.NamedPitch(-2),
... abjad.NamedPitch(12),
... )
NamedInterval('+M9')
>>> abjad.NamedInterval.from_pitch_carriers(
... abjad.NamedPitch("css'"),
... abjad.NamedPitch("cff'"),
... )
NamedInterval('-AAAA1')
>>> abjad.NamedInterval.from_pitch_carriers(
... abjad.NamedPitch("ds'"),
... abjad.NamedPitch("ef''"),
... )
NamedInterval('+d9')
>>> abjad.NamedInterval.from_pitch_carriers("c'", "cs'''")
NamedInterval('+A15')
>>> abjad.NamedInterval.from_pitch_carriers("c", "cqs")
NamedInterval('+P+1')
>>> abjad.NamedInterval.from_pitch_carriers("cf'", "bs")
NamedInterval('-dd2')
>>> abjad.NamedInterval.from_pitch_carriers("cff'", "aqs")
NamedInterval('-ddd+3')
>>> abjad.NamedInterval.from_pitch_carriers("cff'", "atqs")
NamedInterval('-dddd+3')
>>> abjad.NamedInterval.from_pitch_carriers("c'", "d''")
NamedInterval('+M9')
>>> abjad.NamedInterval.from_pitch_carriers("c'", "df''")
NamedInterval('+m9')
>>> abjad.NamedInterval.from_pitch_carriers("c'", "dff''")
NamedInterval('+d9')
>>> abjad.NamedInterval.from_pitch_carriers("c'", "dfff''")
NamedInterval('+dd9')
>>> abjad.NamedInterval.from_pitch_carriers("c'", "dffff''")
NamedInterval('+ddd9')
"""
pitch_1 = NamedPitch(pitch_carrier_1)
pitch_2 = NamedPitch(pitch_carrier_2)
degree_1 = pitch_1._get_diatonic_pitch_number()
degree_2 = pitch_2._get_diatonic_pitch_number()
named_sign = _math.sign(degree_1 - degree_2)
named_i_number = abs(degree_1 - degree_2) + 1
numbered_sign = _math.sign(
float(NumberedPitch(pitch_1)) - float(NumberedPitch(pitch_2))
)
numbered_i_number = abs(
float(NumberedPitch(pitch_1)) - float(NumberedPitch(pitch_2))
)
(
octaves,
named_ic_number,
) = _diatonic_number_to_octaves_and_diatonic_remainder(named_i_number)
numbered_ic_number = numbered_i_number - 12 * octaves
# multiply-diminished intervals can have opposite signs
if named_sign and (named_sign == -numbered_sign):
numbered_ic_number *= -1
quartertone = ""
if numbered_ic_number % 1:
quartertone = "+"
numbered_ic_number -= 0.5
quality_to_semitones = _diatonic_number_to_quality_dictionary[named_ic_number]
semitones_to_quality: dict = {
value: key for key, value in quality_to_semitones.items()
}
quality = ""
while numbered_ic_number > max(semitones_to_quality):
numbered_ic_number -= 1
quality += "A"
while numbered_ic_number < min(semitones_to_quality):
numbered_ic_number += 1
quality += "d"
quality += semitones_to_quality[numbered_ic_number]
quality += quartertone
direction = 1
if pitch_2 < pitch_1:
direction = -1
return class_((quality, named_i_number * direction))
[docs] def transpose(self, pitch_carrier):
"""
Transposes ``pitch_carrier`` by named interval.
.. container:: example
Transposes chord:
>>> chord = abjad.Chord("<c' e' g'>4")
>>> interval = abjad.NamedInterval("+m2")
>>> interval.transpose(chord)
Chord("<df' f' af'>4")
Returns new (copied) object of ``pitch_carrier`` type.
"""
return super().transpose(pitch_carrier)
[docs]class NumberedInterval(Interval):
"""
Numbered interval.
.. container:: example
Initializes from number of semitones:
>>> abjad.NumberedInterval(-14)
NumberedInterval(-14)
Initializes from other numbered interval:
>>> abjad.NumberedInterval(abjad.NumberedInterval(-14))
NumberedInterval(-14)
Initializes from named interval:
>>> abjad.NumberedInterval(abjad.NamedInterval("-P4"))
NumberedInterval(-5)
Initializes from interval string:
>>> abjad.NumberedInterval("-P4")
NumberedInterval(-5)
"""
__slots__ = ()
def __init__(self, number=0):
super().__init__(number or 0)
[docs] def __abs__(self) -> "NumberedInterval":
"""
Absolute value of numbered interval.
.. container:: example
>>> abs(abjad.NumberedInterval(-14))
NumberedInterval(14)
"""
return type(self)(abs(self.number))
[docs] def __add__(self, argument) -> "NumberedInterval":
"""
Adds ``argument`` to numbered interval.
.. container:: example
>>> abjad.NumberedInterval(3) + abjad.NumberedInterval(14)
NumberedInterval(17)
>>> abjad.NumberedInterval(3) + abjad.NumberedInterval(-14)
NumberedInterval(-11)
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return type(self)(float(self) + float(argument))
[docs] def __copy__(self) -> "NumberedInterval":
"""
Copies numbered interval.
>>> import copy
.. container:: example
>>> copy.copy(abjad.NumberedInterval(-14))
NumberedInterval(-14)
"""
return type(self)(self.number)
[docs] def __eq__(self, argument) -> bool:
"""
Compares ``number``.
.. container:: example
>>> interval_1 = abjad.NumberedInterval(12)
>>> interval_2 = abjad.NumberedInterval(12)
>>> interval_3 = abjad.NumberedInterval(13)
>>> interval_1 == interval_1
True
>>> interval_1 == interval_2
True
>>> interval_1 == interval_3
False
>>> interval_2 == interval_1
True
>>> interval_2 == interval_2
True
>>> interval_2 == interval_3
False
>>> interval_3 == interval_1
False
>>> interval_3 == interval_2
False
>>> interval_3 == interval_3
True
"""
if isinstance(argument, type(self)):
return self.number == argument.number
return False
# TODO: remove
[docs] def __float__(self):
"""
Coerce to float.
Returns float.
"""
return float(self.number)
[docs] def __hash__(self):
"""
Hashes numbered interval.
Returns integer.
"""
return super().__hash__()
[docs] def __lt__(self, argument) -> bool:
"""
Is true when ``argument`` is a numbered interval with same direction number as
this numbered interval and with number greater than that of this numbered
interval.
.. container:: example
>>> interval_1 = abjad.NumberedInterval(12)
>>> interval_2 = abjad.NumberedInterval(12)
>>> interval_3 = abjad.NumberedInterval(13)
>>> interval_1 < interval_1
False
>>> interval_1 < interval_2
False
>>> interval_1 < interval_3
True
>>> interval_2 < interval_1
False
>>> interval_2 < interval_2
False
>>> interval_2 < interval_3
True
>>> interval_3 < interval_1
False
>>> interval_3 < interval_2
False
>>> interval_3 < interval_3
False
Returns true or false.
"""
if isinstance(argument, type(self)):
return self.number < argument.number
return False
[docs] def __neg__(self) -> "NumberedInterval":
"""
Negates numbered interval.
.. container:: example
>>> -abjad.NumberedInterval(-14)
NumberedInterval(14)
"""
return type(self)(-self.number)
[docs] def __radd__(self, argument) -> "NumberedInterval":
"""
Adds numbered interval to ``argument``
.. container:: example
>>> interval = abjad.NumberedInterval(14)
>>> abjad.NumberedInterval(3).__radd__(interval)
NumberedInterval(17)
>>> interval = abjad.NumberedInterval(-14)
>>> abjad.NumberedInterval(3).__radd__(interval)
NumberedInterval(-11)
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return type(self)(float(self) + float(argument))
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.number!r})"
[docs] def __sub__(self, argument) -> "NumberedInterval":
"""
Subtracts ``argument`` from numbered interval.
"""
try:
argument = type(self)(argument)
except Exception:
return NotImplemented
return type(self)(float(self) - float(argument))
def _from_interval_or_interval_class(self, argument):
self._from_number(float(argument))
def _from_named_parts(self, direction, quality, diatonic_number):
self._from_number(self._named_to_numbered(direction, quality, diatonic_number))
def _from_number(self, argument):
number = self._to_nearest_quarter_tone(argument)
direction = _math.sign(number)
octaves = 0
pc_number = abs(number)
while pc_number > 12:
pc_number -= 12
octaves += 1
self._octaves = octaves
self._interval_class = NumberedIntervalClass(pc_number * direction)
@property
def direction_number(self) -> int:
"""
Gets direction number of numbered interval.
.. container:: example
>>> abjad.NumberedInterval(-14).direction_number
-1
>>> abjad.NumberedInterval(0).direction_number
0
>>> abjad.NumberedInterval(6).direction_number
1
"""
return _math.sign(self.number)
@property
def interval_class(self) -> "NumberedIntervalClass":
"""
Gets numbered interval class.
"""
return self._interval_class
@property
def number(self) -> float | int:
"""
Gets number of numbered interval.
.. container:: example
>>> abjad.NumberedInterval(-14).number
-14
>>> abjad.NumberedInterval(-2).number
-2
>>> abjad.NumberedInterval(0).number
0
"""
number = self._interval_class._number
direction = _math.sign(number)
number = abs(number) + (12 * self.octaves)
return number * direction
@property
def octaves(self) -> int:
"""
Gets octaves of interval.
"""
return self._octaves
@property
def semitones(self) -> int | float:
"""
Gets semitones corresponding to numbered interval.
.. container:: example
>>> abjad.NumberedInterval(-14).semitones
-14
"""
return self.number
@property
def signed_string(self):
"""
Gets signed string.
"""
direction_symbol = _direction_number_to_direction_symbol[
_math.sign(self.number)
]
return f"{direction_symbol}{abs(self.number)}"
[docs] @classmethod
def from_pitch_carriers(
class_, pitch_carrier_1, pitch_carrier_2
) -> "NumberedInterval":
"""
Makes numbered interval from ``pitch_carrier_1`` and ``pitch_carrier_2``.
.. container:: example
>>> abjad.NumberedInterval.from_pitch_carriers(
... abjad.NamedPitch(-2),
... abjad.NamedPitch(12),
... )
NumberedInterval(14)
>>> abjad.NumberedInterval.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(12),
... )
NumberedInterval(0)
>>> abjad.NumberedInterval.from_pitch_carriers(
... abjad.NamedPitch(9),
... abjad.NamedPitch(12),
... )
NumberedInterval(3)
>>> abjad.NumberedInterval.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(9),
... )
NumberedInterval(-3)
>>> abjad.NumberedInterval.from_pitch_carriers(
... abjad.NamedPitch(12),
... abjad.NamedPitch(-2),
... )
NumberedInterval(-14)
"""
pitch_1 = NamedPitch(pitch_carrier_1)
pitch_2 = NamedPitch(pitch_carrier_2)
number = NumberedPitch(pitch_2).number - NumberedPitch(pitch_1).number
number = _math.integer_equivalent_number_to_integer(number)
return class_(number)
[docs] def transpose(self, pitch_carrier):
"""
Transposes ``pitch_carrier``
.. container:: example
Transposes chord:
>>> chord = abjad.Chord("<c' e' g'>4")
>>> interval = abjad.NumberedInterval(1)
>>> interval.transpose(chord)
Chord("<df' f' af'>4")
Returns newly constructed object of ``pitch_carrier`` type.
"""
return super().transpose(pitch_carrier)
[docs]@functools.total_ordering
class PitchClass:
"""
Abstract pitch-class.
"""
__slots__ = ()
_is_abstract = True
def __init__(self, argument):
if isinstance(argument, str):
match = _comprehensive_pitch_name_regex.match(argument)
if not match:
match = _comprehensive_pitch_class_name_regex.match(argument)
if not match:
class_name = type(self).__name__
message = f"can not instantiate {class_name} from {argument!r}."
raise ValueError(message)
group_dict = match.groupdict()
dpc_name = group_dict["diatonic_pc_name"].lower()
dpc_number = _diatonic_pc_name_to_diatonic_pc_number[dpc_name]
alteration = Accidental(group_dict["comprehensive_accidental"]).semitones
self._from_named_parts(dpc_number, alteration)
elif isinstance(argument, numbers.Number):
self._from_number(argument)
elif isinstance(argument, Pitch | PitchClass):
self._from_pitch_or_pitch_class(argument)
else:
try:
pitch = NamedPitch(argument)
self._from_pitch_or_pitch_class(pitch)
except Exception:
class_name = type(self).__name__
message = f"can not instantiate {class_name} from {argument!r}."
raise ValueError(message)
[docs] def __eq__(self, argument) -> bool:
"""
Compares reprs.
"""
if isinstance(argument, type(self)):
return repr(self) == repr(argument)
return False
# TODO: remove
[docs] def __float__(self):
"""
Coerce to float.
Returns float.
"""
return float(self.number)
[docs] def __hash__(self) -> int:
"""
Hashes pitch-class.
"""
return hash(self.__class__.__name__ + repr(self))
[docs] def __lt__(self, argument):
"""
Is true when pitch-class is less than ``argument``.
Returns true or false.
"""
raise NotImplementedError
def _from_named_parts(self, dpc_number, alteration):
raise NotImplementedError
def _from_number(self, number):
raise NotImplementedError
def _from_pitch_or_pitch_class(self, pitch_or_pitch_class):
raise NotImplementedError
def _get_alteration(self):
raise NotImplementedError
def _get_diatonic_pc_number(self):
raise NotImplementedError
def _get_lilypond_format(self):
raise NotImplementedError
@staticmethod
def _to_nearest_quarter_tone(number):
number = round((float(number) % 12) * 4) / 4
div, mod = divmod(number, 1)
if mod == 0.75:
div += 1
elif mod == 0.5:
div += 0.5
div %= 12
return _math.integer_equivalent_number_to_integer(div)
[docs] def accidental(self):
"""
Gets accidental of pitch-class.
"""
raise NotImplementedError
[docs] def pitch_class_label(self):
"""
Gets pitch-class label of pitch-class.
"""
raise NotImplementedError
[docs] def invert(self, axis=None):
"""
Inverts pitch-class about ``axis``.
Returns new pitch-class.
"""
raise NotImplementedError
[docs] def multiply(self, n=1):
"""
Multiplies pitch-class by ``n``.
Returns new pitch-class.
"""
raise NotImplementedError
[docs] def transpose(self, n=0):
"""
Transposes pitch-class by index ``n``.
Returns new pitch-class.
"""
raise NotImplementedError
[docs]class NamedPitchClass(PitchClass):
"""
Named pitch-class.
.. container:: example
Initializes from pitch-class name:
>>> abjad.NamedPitchClass("cs")
NamedPitchClass('cs')
>>> abjad.NamedPitchClass("cqs")
NamedPitchClass('cqs')
Initializes from number of semitones:
>>> abjad.NamedPitchClass(14)
NamedPitchClass('d')
>>> abjad.NamedPitchClass(14.5)
NamedPitchClass('dqs')
Initializes from named pitch:
>>> abjad.NamedPitchClass(abjad.NamedPitch("d"))
NamedPitchClass('d')
>>> abjad.NamedPitchClass(abjad.NamedPitch("dqs"))
NamedPitchClass('dqs')
Initializes from numbered pitch:
>>> abjad.NamedPitchClass(abjad.NumberedPitch(14))
NamedPitchClass('d')
>>> abjad.NamedPitchClass(abjad.NumberedPitch(14.5))
NamedPitchClass('dqs')
Initializes from numbered pitch-class:
>>> abjad.NamedPitchClass(abjad.NumberedPitchClass(2))
NamedPitchClass('d')
>>> abjad.NamedPitchClass(abjad.NumberedPitchClass(2.5))
NamedPitchClass('dqs')
Initializes from pitch-class / octave-number string:
>>> abjad.NamedPitchClass("C#5")
NamedPitchClass('cs')
>>> abjad.NamedPitchClass("Cs5")
NamedPitchClass('cs')
Initializes quartertone from pitch-class / octave-number string:
>>> abjad.NamedPitchClass("C+5")
NamedPitchClass('cqs')
>>> abjad.NamedPitchClass("Cqs5")
NamedPitchClass('cqs')
Initializes from pitch-class string:
>>> abjad.NamedPitchClass("C#")
NamedPitchClass('cs')
>>> abjad.NamedPitchClass("Cs")
NamedPitchClass('cs')
>>> abjad.NamedPitchClass("cs")
NamedPitchClass('cs')
Initializes quartertone from pitch-class string
>>> abjad.NamedPitchClass("C+")
NamedPitchClass('cqs')
>>> abjad.NamedPitchClass("Cqs")
NamedPitchClass('cqs')
>>> abjad.NamedPitchClass("cqs")
NamedPitchClass('cqs')
Initializes from note:
>>> abjad.NamedPitchClass(abjad.Note("d''8."))
NamedPitchClass('d')
>>> abjad.NamedPitchClass(abjad.Note("dqs''8."))
NamedPitchClass('dqs')
"""
__slots__ = ("_diatonic_pc_number", "_accidental")
def __init__(self, name="c", *, accidental=None, arrow=None):
super().__init__(name or "c")
if accidental is not None:
self._accidental = type(self._accidental)(accidental)
if arrow is not None:
self._accidental = type(self._accidental)(self._accidental, arrow=arrow)
[docs] def __add__(self, named_interval) -> "NamedPitchClass":
"""
Adds ``named_interval`` to named pitch-class.
.. container:: example
>>> abjad.NamedPitchClass("cs") + abjad.NamedInterval("+M9")
NamedPitchClass('ds')
>>> abjad.NamedPitchClass("cs") + abjad.NamedInterval("-M9")
NamedPitchClass('b')
"""
dummy_pitch = NamedPitch((self.name, 4))
pitch = named_interval.transpose(dummy_pitch)
return type(self)(pitch)
[docs] def __eq__(self, argument) -> bool:
"""
Compares string formats.
.. container:: example
>>> pitch_class_1 = abjad.NamedPitchClass("cs")
>>> pitch_class_2 = abjad.NamedPitchClass("cs")
>>> pitch_class_3 = abjad.NamedPitchClass("df")
>>> pitch_class_1 == pitch_class_1
True
>>> pitch_class_1 == pitch_class_2
True
>>> pitch_class_1 == pitch_class_3
False
>>> pitch_class_2 == pitch_class_1
True
>>> pitch_class_2 == pitch_class_2
True
>>> pitch_class_2 == pitch_class_3
False
>>> pitch_class_3 == pitch_class_1
False
>>> pitch_class_3 == pitch_class_2
False
>>> pitch_class_3 == pitch_class_3
True
"""
return super().__eq__(argument)
[docs] def __hash__(self):
"""
Hashes named pitch-class.
Returns integer.
"""
return super().__hash__()
[docs] def __lt__(self, argument) -> bool:
"""
Compares ``number``.
.. container:: example
>>> abjad.NamedPitchClass("cs") < abjad.NamedPitchClass("d")
True
>>> abjad.NamedPitchClass("d") < abjad.NamedPitchClass("cs")
False
"""
assert isinstance(argument, type(self))
return self.number < argument.number
[docs] def __radd__(self, interval):
"""
Right-addition not defined on named pitch-classes.
.. container:: example
>>> abjad.NamedPitchClass("cs").__radd__(1)
Traceback (most recent call last):
...
NotImplementedError: right-addition not defined on NamedPitchClass.
"""
message = f"right-addition not defined on {type(self).__name__}."
raise NotImplementedError(message)
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.name!r})"
[docs] def __sub__(self, argument) -> "NamedInversionEquivalentIntervalClass":
"""
Subtracts ``argument`` from named pitch-class.
.. container:: example
>>> abjad.NamedPitchClass("cs") - abjad.NamedPitchClass("g")
NamedInversionEquivalentIntervalClass('+A4')
>>> abjad.NamedPitchClass("c") - abjad.NamedPitchClass("cf")
NamedInversionEquivalentIntervalClass('+A1')
>>> abjad.NamedPitchClass("cf") - abjad.NamedPitchClass("c")
NamedInversionEquivalentIntervalClass('+A1')
"""
assert isinstance(argument, type(self))
pitch_1 = NamedPitch((self.name, 4))
pitch_2 = NamedPitch((argument.name, 4))
mdi = NamedInterval.from_pitch_carriers(pitch_1, pitch_2)
pair = (mdi.quality, mdi.number)
dic = NamedInversionEquivalentIntervalClass(pair)
return dic
def _apply_accidental(self, accidental=None):
accidental = Accidental(accidental)
new_accidental = self.accidental + accidental
new_name = self._get_diatonic_pc_name() + str(new_accidental)
return type(self)(new_name)
def _from_named_parts(self, dpc_number, alteration):
self._diatonic_pc_number = dpc_number
self._accidental = Accidental(alteration)
def _from_number(self, number):
numbered_pitch_class = NumberedPitchClass(number)
self._from_pitch_or_pitch_class(numbered_pitch_class)
def _from_pitch_or_pitch_class(self, pitch_or_pitch_class):
if isinstance(pitch_or_pitch_class, Pitch):
pitch_or_pitch_class = pitch_or_pitch_class.pitch_class
self._diatonic_pc_number = pitch_or_pitch_class._get_diatonic_pc_number()
self._accidental = Accidental(
pitch_or_pitch_class._get_alteration(),
arrow=pitch_or_pitch_class.arrow,
)
def _get_alteration(self):
return self._accidental.semitones
def _get_diatonic_pc_name(self):
return _diatonic_pc_number_to_diatonic_pc_name[self._diatonic_pc_number]
def _get_diatonic_pc_number(self):
return self._diatonic_pc_number
def _get_lilypond_format(self):
name = self._get_diatonic_pc_name()
accidental = Accidental(self._get_alteration())
return f"{name}{accidental!s}"
@property
def accidental(self):
"""
Gets accidental.
.. container:: example
>>> abjad.NamedPitchClass("cs").accidental
Accidental(name='sharp')
"""
return self._accidental
@property
def arrow(self):
"""
Gets arrow of named pitch-class.
Returns up, down or none.
"""
return self._accidental.arrow
@property
def name(self) -> str:
"""
Gets name of named pitch-class.
.. container:: example
>>> abjad.NamedPitchClass("cs").name
'cs'
"""
diatonic_pc_name = _diatonic_pc_number_to_diatonic_pc_name[
self._diatonic_pc_number
]
return f"{diatonic_pc_name}{self._accidental!s}"
@property
def number(self) -> int | float:
"""
Gets number.
.. container:: example
>>> abjad.NamedPitchClass("cs").number
1
"""
dictionary = _diatonic_pc_number_to_pitch_class_number
result = dictionary[self._diatonic_pc_number]
result += self._accidental.semitones
result %= 12
return result
@property
def pitch_class_label(self):
"""
Gets pitch-class label.
.. container:: example
>>> abjad.NamedPitchClass("cs").pitch_class_label
'C#'
"""
pc = self._get_diatonic_pc_name().upper()
return f"{pc}{self.accidental.symbol}"
[docs] def invert(self, axis=None) -> "NamedPitchClass":
"""
Inverts named pitch-class.
"""
axis = axis or NamedPitch("c")
axis = NamedPitch(axis)
this = NamedPitch(self)
interval = this - axis
result = axis.transpose(interval)
result = type(self)(result)
return result
[docs] def multiply(self, n=1) -> "NamedPitchClass":
"""
Multiplies named pitch-class by ``n``.
.. container:: example
>>> abjad.NamedPitchClass("cs").multiply(3)
NamedPitchClass('ef')
"""
return type(self)(n * self.number)
[docs] def transpose(self, n=0) -> "NamedPitchClass":
"""
Transposes named pitch-class by index named interval ``n``.
.. container:: example
>>> interval = abjad.NamedInterval("-M2")
>>> abjad.NamedPitchClass("cs").transpose(interval)
NamedPitchClass('b')
>>> interval = abjad.NamedInterval("P1")
>>> abjad.NamedPitchClass("cs").transpose(interval)
NamedPitchClass('cs')
>>> interval = abjad.NamedInterval("+M2")
>>> abjad.NamedPitchClass("cs").transpose(interval)
NamedPitchClass('ds')
"""
interval = NamedInterval(n)
pitch = NamedPitch((self.name, 4))
pitch = interval.transpose(pitch)
return type(self)(pitch)
[docs]class NumberedPitchClass(PitchClass):
"""
Numbered pitch-class.
.. container:: example
Initializes from number of semitones:
>>> abjad.NumberedPitchClass(13)
NumberedPitchClass(1)
Initializes from pitch name:
>>> abjad.NumberedPitchClass("d")
NumberedPitchClass(2)
Initializes from named pitch:
>>> abjad.NumberedPitchClass(abjad.NamedPitch("g,"))
NumberedPitchClass(7)
Initializes from numbered pitch:
>>> abjad.NumberedPitchClass(abjad.NumberedPitch(15))
NumberedPitchClass(3)
Initializes from named pitch-class:
>>> abjad.NumberedPitchClass(abjad.NamedPitchClass("e"))
NumberedPitchClass(4)
Initializes from pitch-class / octave string:
>>> abjad.NumberedPitchClass("C#5")
NumberedPitchClass(1)
Initializes from other numbered pitch-class:
>>> abjad.NumberedPitchClass(abjad.NumberedPitchClass(9))
NumberedPitchClass(9)
Initializes from note:
>>> abjad.NumberedPitchClass(abjad.Note("a'8."))
NumberedPitchClass(9)
"""
__slots__ = ("_arrow", "_number")
def __init__(self, number=0, *, arrow=None):
super().__init__(number or 0)
if arrow is not None:
arrow = _string.to_tridirectional_ordinal_constant(arrow)
if arrow is _enums.CENTER:
arrow = None
self._arrow = arrow
[docs] def __add__(self, argument) -> "NumberedPitchClass":
"""
Adds ``argument`` to numbered pitch-class.
.. container:: example
>>> pitch_class = abjad.NumberedPitchClass(9)
>>> pitch_class + abjad.NumberedInterval(0)
NumberedPitchClass(9)
>>> pitch_class + abjad.NumberedInterval(1)
NumberedPitchClass(10)
>>> pitch_class + abjad.NumberedInterval(2)
NumberedPitchClass(11)
>>> pitch_class + abjad.NumberedInterval(3)
NumberedPitchClass(0)
"""
interval = NumberedInterval(argument)
return type(self)(self.number + interval.number % 12)
[docs] def __copy__(self, *arguments) -> "NumberedPitchClass":
"""
Copies numbered pitch-class.
.. container:: example
>>> import copy
>>> pitch_class = abjad.NumberedPitchClass(9)
>>> copy.copy(pitch_class)
NumberedPitchClass(9)
"""
return type(self)(self)
[docs] def __eq__(self, argument):
"""
Compares ``number``.
.. container:: example
>>> pitch_class_1 = abjad.NumberedPitchClass(0)
>>> pitch_class_2 = abjad.NumberedPitchClass(0)
>>> pitch_class_3 = abjad.NumberedPitchClass(1)
>>> pitch_class_1 == pitch_class_1
True
>>> pitch_class_1 == pitch_class_2
True
>>> pitch_class_1 == pitch_class_3
False
>>> pitch_class_2 == pitch_class_1
True
>>> pitch_class_2 == pitch_class_2
True
>>> pitch_class_2 == pitch_class_3
False
>>> pitch_class_3 == pitch_class_1
False
>>> pitch_class_3 == pitch_class_2
False
>>> pitch_class_3 == pitch_class_3
True
"""
if isinstance(argument, type(self)):
return self.number == argument.number
[docs] def __hash__(self):
"""
Hashes numbered pitch-class.
Returns integer.
"""
return super().__hash__()
[docs] def __lt__(self, argument) -> bool:
"""
Compares ``number``.
.. container:: example
>>> abjad.NumberedPitchClass(1) < abjad.NumberedPitchClass(2)
True
>>> abjad.NumberedPitchClass(2) < abjad.NumberedPitchClass(1)
False
"""
if not isinstance(argument, type(self)):
raise TypeError(f"can not compare numbered pitch-class to {argument!r}.")
return self.number < argument.number
[docs] def __neg__(self) -> "NumberedPitchClass":
"""
Negates numbered pitch-class.
.. container:: example
>>> pitch_class = abjad.NumberedPitchClass(9)
>>> -pitch_class
NumberedPitchClass(3)
"""
return type(self)(-self.number)
[docs] def __radd__(self, argument):
"""
Right-addition not defined on numbered pitch-classes.
.. container:: example
>>> 1 + abjad.NumberedPitchClass(9)
Traceback (most recent call last):
...
NotImplementedError: right-addition not defined on NumberedPitchClass.
Raises not implemented error.
"""
message = f"right-addition not defined on {type(self).__name__}."
raise NotImplementedError(message)
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.number!r})"
[docs] def __sub__(
self, argument
) -> typing.Union["NumberedPitchClass", "NumberedInversionEquivalentIntervalClass"]:
"""
Subtracts ``argument`` from numbered pitch-class.
Subtraction is defined against both numbered intervals and against other
pitch-classes.
.. container:: example
>>> abjad.NumberedPitchClass(6) - abjad.NumberedPitchClass(6)
NumberedInversionEquivalentIntervalClass(0)
>>> abjad.NumberedPitchClass(6) - abjad.NumberedPitchClass(7)
NumberedInversionEquivalentIntervalClass(1)
>>> abjad.NumberedPitchClass(7) - abjad.NumberedPitchClass(6)
NumberedInversionEquivalentIntervalClass(1)
>>> abjad.NumberedPitchClass(6) - abjad.NumberedInterval(-1)
NumberedPitchClass(5)
>>> abjad.NumberedPitchClass(6) - abjad.NumberedInterval(0)
NumberedPitchClass(6)
>>> abjad.NumberedPitchClass(6) - abjad.NumberedInterval(1)
NumberedPitchClass(5)
"""
if isinstance(argument, type(self)):
interval_class_number = abs(self.number - argument.number)
if 6 < interval_class_number:
interval_class_number = 12 - interval_class_number
return NumberedInversionEquivalentIntervalClass(interval_class_number)
interval_class = NumberedInversionEquivalentIntervalClass(argument)
return type(self)(self.number - interval_class.number % 12)
def _apply_accidental(self, accidental=None):
accidental = Accidental(accidental)
semitones = self.number + accidental.semitones
return type(self)(semitones)
def _from_named_parts(self, dpc_number, alteration):
number = _diatonic_pc_number_to_pitch_class_number[dpc_number]
number += alteration
self._from_number(number)
def _from_number(self, number):
self._arrow = None
self._number = self._to_nearest_quarter_tone(number)
def _from_pitch_or_pitch_class(self, pitch_or_pitch_class):
self._arrow = pitch_or_pitch_class.arrow
self._number = self._to_nearest_quarter_tone(float(pitch_or_pitch_class))
def _get_alteration(self):
dpc_number = self._get_diatonic_pc_number()
pc_number = _diatonic_pc_number_to_pitch_class_number[dpc_number]
return float(self) - pc_number
def _get_diatonic_pc_name(self):
return self.name[0]
def _get_diatonic_pc_number(self):
return _diatonic_pc_name_to_diatonic_pc_number[self._get_diatonic_pc_name()]
def _get_lilypond_format(self):
return NamedPitchClass(self)._get_lilypond_format()
@property
def accidental(self):
"""
Gets accidental.
.. container:: example
>>> abjad.NumberedPitchClass(1).accidental
Accidental(name='sharp')
"""
return NamedPitch(self.number).accidental
@property
def arrow(self):
"""
Gets arrow of numbered pitch-class.
Returns up, down or none.
"""
return self._arrow
@property
def name(self) -> str:
"""
Gets name of numbered pitch-class.
.. container:: example
>>> abjad.NumberedPitchClass(13).name
'cs'
"""
return _pitch_class_number_to_pitch_class_name[self.number]
@property
def number(self) -> int | float:
"""
Gets number.
.. container:: example
>>> abjad.NumberedPitchClass(1).number
1
>>> abjad.NumberedPitchClass(13).number
1
"""
return self._number
@property
def pitch_class_label(self):
"""
Gets pitch-class / octave label.
.. container:: example
>>> abjad.NumberedPitchClass(13).pitch_class_label
'C#'
"""
name = self._get_diatonic_pc_name().upper()
return f"{name}{self.accidental.symbol}"
[docs] def invert(self, axis=None) -> "NumberedPitchClass":
"""
Inverts numbered pitch-class.
.. container:: example
>>> for n in range(12):
... pitch_class = abjad.NumberedPitchClass(n)
... print(repr(pitch_class), repr(pitch_class.invert()))
...
NumberedPitchClass(0) NumberedPitchClass(0)
NumberedPitchClass(1) NumberedPitchClass(11)
NumberedPitchClass(2) NumberedPitchClass(10)
NumberedPitchClass(3) NumberedPitchClass(9)
NumberedPitchClass(4) NumberedPitchClass(8)
NumberedPitchClass(5) NumberedPitchClass(7)
NumberedPitchClass(6) NumberedPitchClass(6)
NumberedPitchClass(7) NumberedPitchClass(5)
NumberedPitchClass(8) NumberedPitchClass(4)
NumberedPitchClass(9) NumberedPitchClass(3)
NumberedPitchClass(10) NumberedPitchClass(2)
NumberedPitchClass(11) NumberedPitchClass(1)
Interprets axis of inversion equal to pitch-class 0.
"""
axis = axis or NumberedPitch("c")
axis = NumberedPitch(axis)
this = NumberedPitch(self)
interval = this - axis
result = axis.transpose(interval)
result = type(self)(result)
return result
[docs] def multiply(self, n=1) -> "NumberedPitchClass":
"""
Multiplies pitch-class number by ``n``.
.. container:: example
>>> for n in range(12):
... pitch_class = abjad.NumberedPitchClass(n)
... print(repr(pitch_class), repr(pitch_class.multiply(5)))
...
NumberedPitchClass(0) NumberedPitchClass(0)
NumberedPitchClass(1) NumberedPitchClass(5)
NumberedPitchClass(2) NumberedPitchClass(10)
NumberedPitchClass(3) NumberedPitchClass(3)
NumberedPitchClass(4) NumberedPitchClass(8)
NumberedPitchClass(5) NumberedPitchClass(1)
NumberedPitchClass(6) NumberedPitchClass(6)
NumberedPitchClass(7) NumberedPitchClass(11)
NumberedPitchClass(8) NumberedPitchClass(4)
NumberedPitchClass(9) NumberedPitchClass(9)
NumberedPitchClass(10) NumberedPitchClass(2)
NumberedPitchClass(11) NumberedPitchClass(7)
"""
return type(self)(n * self.number)
[docs] def transpose(self, n=0) -> "NumberedPitchClass":
"""
Transposes numbered pitch-class by index ``n``.
.. container:: example
>>> for n in range(12):
... pitch_class = abjad.NumberedPitchClass(n)
... print(repr(pitch_class), repr(pitch_class.transpose(-13)))
...
NumberedPitchClass(0) NumberedPitchClass(11)
NumberedPitchClass(1) NumberedPitchClass(0)
NumberedPitchClass(2) NumberedPitchClass(1)
NumberedPitchClass(3) NumberedPitchClass(2)
NumberedPitchClass(4) NumberedPitchClass(3)
NumberedPitchClass(5) NumberedPitchClass(4)
NumberedPitchClass(6) NumberedPitchClass(5)
NumberedPitchClass(7) NumberedPitchClass(6)
NumberedPitchClass(8) NumberedPitchClass(7)
NumberedPitchClass(9) NumberedPitchClass(8)
NumberedPitchClass(10) NumberedPitchClass(9)
NumberedPitchClass(11) NumberedPitchClass(10)
"""
return type(self)(self.number + n)
[docs]@functools.total_ordering
class Pitch:
"""
Abstract pitch.
"""
__slots__ = ("_pitch_class", "_octave")
_is_abstract = True
def __init__(self, argument, accidental=None, arrow=None, octave=None):
if isinstance(argument, str):
match = _comprehensive_pitch_name_regex.match(argument)
if not match:
match = _comprehensive_pitch_class_name_regex.match(argument)
if not match:
class_name = type(self).__name__
message = f"can not instantiate {class_name} from {argument!r}."
raise ValueError(message)
group_dict = match.groupdict()
_dpc_name = group_dict["diatonic_pc_name"].lower()
_dpc_number = _diatonic_pc_name_to_diatonic_pc_number[_dpc_name]
_alteration = Accidental(group_dict["comprehensive_accidental"]).semitones
foo = group_dict.get("comprehensive_octave", "")
assert isinstance(foo, str), repr(foo)
if foo.isdigit():
number = int(foo)
_octave = Octave(number).number
else:
_octave = Octave.from_ticks(foo).number
self._from_named_parts(_dpc_number, _alteration, _octave)
elif isinstance(argument, numbers.Number):
self._from_number(argument)
elif isinstance(argument, Pitch | PitchClass):
self._from_pitch_or_pitch_class(argument)
elif isinstance(argument, tuple) and len(argument) == 2:
_pitch_class = NamedPitchClass(argument[0])
_octave = Octave(argument[1])
self._from_named_parts(
_pitch_class._get_diatonic_pc_number(),
_pitch_class._get_alteration(),
_octave.number,
)
elif hasattr(argument, "written_pitch"):
self._from_pitch_or_pitch_class(argument.written_pitch)
elif hasattr(argument, "note_heads") and len(argument.note_heads):
self._from_pitch_or_pitch_class(argument.note_heads[0])
else:
class_name = type(self).__name__
raise ValueError(f"can not instantiate {class_name} from {argument!r}.")
if accidental is not None:
accidental = Accidental(accidental)
self._pitch_class = type(self._pitch_class)(
self._pitch_class, accidental=accidental
)
if arrow is not None:
self._pitch_class = type(self._pitch_class)(self._pitch_class, arrow=arrow)
if octave is not None:
octave = Octave(octave)
self._octave = octave
# TODO: remove
[docs] def __float__(self):
"""
Coerce to float.
Returns float.
"""
return float(self.number)
[docs] def __hash__(self) -> int:
"""
Hashes pitch.
"""
return hash(self.__class__.__name__ + repr(self))
[docs] def __lt__(self, argument):
"""
Is true when pitch is less than ``argument``.
Returns true or false.
"""
raise NotImplementedError
def _get_lilypond_format(self):
raise NotImplementedError
@staticmethod
def _to_nearest_octave(pitch_number, pitch_class_number):
target_pc = pitch_number % 12
down = (target_pc - pitch_class_number) % 12
up = (pitch_class_number - target_pc) % 12
if up < down:
return pitch_number + up
else:
return pitch_number - down
@staticmethod
def _to_nearest_quarter_tone(number):
number = round(float(number) * 4) / 4
quotient, remainder = divmod(number, 1)
if remainder == 0.75:
quotient += 1
elif remainder == 0.5:
quotient += 0.5
return _math.integer_equivalent_number_to_integer(quotient)
@staticmethod
def _to_pitch_class_item_class(item_class):
item_class = item_class or NumberedPitch
if item_class in (NamedPitchClass, NumberedPitchClass):
return item_class
elif item_class is NamedPitch:
return NamedPitchClass
elif item_class is NumberedPitch:
return NumberedPitchClass
else:
raise TypeError(item_class)
@staticmethod
def _to_pitch_item_class(item_class):
item_class = item_class or NumberedPitch
if item_class in (NamedPitch, NumberedPitch):
return item_class
elif item_class is NamedPitchClass:
return NamedPitch
elif item_class is NumberedPitchClass:
return NumberedPitch
else:
raise TypeError(item_class)
@property
def arrow(self):
"""
Gets arrow of pitch.
"""
raise NotImplementedError
@property
def hertz(self) -> float:
"""
Gets frequency of pitch in Hertz.
"""
hertz = pow(2.0, (float(self.number) - 9.0) / 12.0) * 440.0
return hertz
@property
def name(self):
"""
Gets name of pitch.
Returns string.
"""
raise NotImplementedError
@property
def number(self):
"""
Gets number of pitch.
Returns number.
"""
raise NotImplementedError
@property
def octave(self):
"""
Gets octave of pitch.
Returns octave.
"""
raise NotImplementedError
@property
def pitch_class(self):
"""
Gets pitch-class of pitch.
Returns pitch-class.
"""
raise NotImplementedError
[docs] @classmethod
def from_hertz(class_, hertz):
"""
Creates pitch from ``hertz``.
Returns new pitch.
"""
midi = 9.0 + (12.0 * math.log(float(hertz) / 440.0, 2))
pitch = class_(midi)
return pitch
[docs] def get_name(self, locale=None):
"""
Gets name of pitch according to ``locale``.
Returns string.
"""
raise NotImplementedError
[docs] def invert(self, axis=None):
"""
Inverts pitch about ``axis``.
Interprets ``axis`` of none equal to middle C.
Returns new pitch.
"""
axis = axis or NamedPitch("c'")
axis = type(self)(axis)
interval = self - axis
result = axis.transpose(interval)
return result
[docs] def multiply(self, n=1):
"""
Multiplies pitch by ``n``.
Returns new pitch.
"""
return type(self)(n * self.number)
[docs] def transpose(self, n):
"""
Transposes pitch by index ``n``.
Returns new pitch.
"""
raise NotImplementedError
[docs]@functools.total_ordering
class NamedPitch(Pitch):
r"""
Named pitch.
.. container:: example
Initializes from pitch name:
>>> abjad.NamedPitch("cs''")
NamedPitch("cs''")
Initializes quartertone from pitch name:
>>> abjad.NamedPitch("aqs")
NamedPitch('aqs')
Initializes from pitch-class / octave string:
>>> abjad.NamedPitch("C#5")
NamedPitch("cs''")
Initializes quartertone from pitch-class / octave string:
>>> abjad.NamedPitch("A+3")
NamedPitch('aqs')
>>> abjad.NamedPitch("Aqs3")
NamedPitch('aqs')
Initializes arrowed pitch:
>>> abjad.NamedPitch("C#5", arrow=abjad.UP)
NamedPitch("cs''", arrow=Vertical.UP)
.. container:: example
REGRESSION. Small floats just less than a C initialize in the correct octave:
Initializes c / C3:
>>> abjad.NamedPitch(-12.1)
NamedPitch('c')
Initializes c' / C4:
>>> abjad.NamedPitch(-0.1)
NamedPitch("c'")
Initializes c'' / C5:
>>> abjad.NamedPitch(11.9)
NamedPitch("c''")
"""
__slots__ = ()
def __init__(self, name="c'", *, accidental=None, arrow=None, octave=None):
super().__init__(
name or "c'", accidental=accidental, arrow=arrow, octave=octave
)
[docs] def __add__(self, interval) -> "NamedPitch":
"""
Adds named pitch to ``interval``.
.. container:: example
>>> abjad.NamedPitch("cs''") + abjad.NamedInterval("-M2")
NamedPitch("b'")
>>> abjad.NamedPitch("cs''") + abjad.NamedInterval("P1")
NamedPitch("cs''")
>>> abjad.NamedPitch("cs''") + abjad.NamedInterval("+M2")
NamedPitch("ds''")
"""
interval = NamedInterval(interval)
return interval.transpose(self)
[docs] def __copy__(self, *arguments) -> "NamedPitch":
"""
Copies named pitch.
>>> import copy
.. container:: example
>>> copy.copy(abjad.NamedPitch("c''"))
NamedPitch("c''")
>>> copy.copy(abjad.NamedPitch("cs''"))
NamedPitch("cs''")
>>> copy.copy(abjad.NamedPitch("df''"))
NamedPitch("df''")
Copies arrowed pitch:
>>> pitch = abjad.NamedPitch("cs''", arrow=abjad.UP)
>>> copy.copy(pitch)
NamedPitch("cs''", arrow=Vertical.UP)
"""
return type(self)(self, arrow=self.arrow)
[docs] def __eq__(self, argument) -> bool:
"""
Is true when ``argument`` is a named pitch equal to this named pitch.
.. container:: example
>>> pitch_1 = abjad.NamedPitch("fs")
>>> pitch_2 = abjad.NamedPitch("fs")
>>> pitch_3 = abjad.NamedPitch("gf")
>>> pitch_1 == pitch_1
True
>>> pitch_1 == pitch_2
True
>>> pitch_1 == pitch_3
False
>>> pitch_2 == pitch_1
True
>>> pitch_2 == pitch_2
True
>>> pitch_2 == pitch_3
False
>>> pitch_3 == pitch_1
False
>>> pitch_3 == pitch_2
False
>>> pitch_3 == pitch_3
True
Returns true or false.
"""
if isinstance(argument, str):
argument = NamedPitch(argument)
if isinstance(argument, type(self)):
return (
self.number == argument.number
and self.accidental == argument.accidental
and self.arrow == argument.arrow
and self.octave == argument.octave
)
return False
[docs] def __hash__(self):
"""
Hashes numbered pitch.
"""
return super().__hash__()
# mypy currently does not support functools.total_ordering
# https://github.com/python/mypy/issues/4610
# remove __le__ (in favor of __lt__) when mypy supports functools.total_ordering
# or, refactor this class as a dataclass and then remove __le__
[docs] def __le__(self, argument) -> bool:
"""
Is true when named pitch is less than or equal to ``argument``.
.. container:: example
>>> pitch_1 = abjad.NamedPitch("fs")
>>> pitch_2 = abjad.NamedPitch("fs")
>>> pitch_3 = abjad.NamedPitch("gf")
>>> pitch_1 <= pitch_1
True
>>> pitch_1 <= pitch_2
True
>>> pitch_1 <= pitch_3
True
>>> pitch_2 <= pitch_1
True
>>> pitch_2 <= pitch_2
True
>>> pitch_2 <= pitch_3
True
>>> pitch_3 <= pitch_1
False
>>> pitch_3 <= pitch_2
False
>>> pitch_3 <= pitch_3
True
"""
try:
argument = type(self)(argument)
except (TypeError, ValueError):
return False
self_dpn = self._get_diatonic_pitch_number()
argument_dpn = argument._get_diatonic_pitch_number()
if self_dpn == argument_dpn:
return self.accidental <= argument.accidental
return self_dpn <= argument_dpn
[docs] def __lt__(self, argument) -> bool:
"""
Is true when named pitch is less than ``argument``.
.. container:: example
>>> pitch_1 = abjad.NamedPitch("fs")
>>> pitch_2 = abjad.NamedPitch("fs")
>>> pitch_3 = abjad.NamedPitch("gf")
>>> pitch_1 < pitch_1
False
>>> pitch_1 < pitch_2
False
>>> pitch_1 < pitch_3
True
>>> pitch_2 < pitch_1
False
>>> pitch_2 < pitch_2
False
>>> pitch_2 < pitch_3
True
>>> pitch_3 < pitch_1
False
>>> pitch_3 < pitch_2
False
>>> pitch_3 < pitch_3
False
Returns true or false.
"""
try:
argument = type(self)(argument)
except (TypeError, ValueError):
return False
self_dpn = self._get_diatonic_pitch_number()
argument_dpn = argument._get_diatonic_pitch_number()
if self_dpn == argument_dpn:
return self.accidental < argument.accidental
return self_dpn < argument_dpn
[docs] def __radd__(self, interval):
"""
Right-addition not defined on named pitches.
.. container:: example
>>> abjad.NamedPitch("cs'").__radd__(1)
Traceback (most recent call last):
...
NotImplementedError: right-addition not defined on NamedPitch.
"""
message = f"right-addition not defined on {type(self).__name__}."
raise NotImplementedError(message)
[docs] def __repr__(self):
"""
Gets repr.
"""
if self.arrow is not None:
return f"{type(self).__name__}({self.name!r}, arrow={self.arrow})"
else:
return f"{type(self).__name__}({self.name!r})"
[docs] def __sub__(self, argument) -> "NamedInterval":
"""
Subtracts ``argument`` from named pitch.
.. container:: example
>>> abjad.NamedPitch("cs''") - abjad.NamedPitch("b'")
NamedInterval('-M2')
>>> abjad.NamedPitch("cs''") - abjad.NamedPitch("fs''")
NamedInterval('+P4')
"""
if isinstance(argument, type(self)):
return NamedInterval.from_pitch_carriers(self, argument)
interval = NamedInterval(argument)
interval = -interval
return interval.transpose(self)
def _apply_accidental(self, accidental):
name = self._get_diatonic_pc_name()
name += str(self.accidental + Accidental(accidental))
name += self.octave.ticks
return type(self)(name)
def _from_named_parts(self, dpc_number, alteration, octave):
dpc_name = _diatonic_pc_number_to_diatonic_pc_name[dpc_number]
accidental = Accidental(alteration)
octave = Octave(octave)
self._octave = octave
self._pitch_class = NamedPitchClass(dpc_name + str(accidental))
def _from_number(self, number):
number = self._to_nearest_quarter_tone(number)
quotient, remainder = divmod(number, 12)
pitch_class = NumberedPitchClass(remainder)
self._from_named_parts(
dpc_number=pitch_class._get_diatonic_pc_number(),
alteration=pitch_class._get_alteration(),
octave=quotient + 4,
)
def _from_pitch_or_pitch_class(self, pitch_or_pitch_class):
name = pitch_or_pitch_class._get_lilypond_format()
if not isinstance(pitch_or_pitch_class, Pitch):
name += "'"
if isinstance(pitch_or_pitch_class, Pitch):
self._pitch_class = NamedPitchClass(pitch_or_pitch_class.pitch_class)
self._octave = pitch_or_pitch_class.octave
else:
self._pitch_class = NamedPitchClass(pitch_or_pitch_class)
self._octave = Octave()
def _get_alteration(self):
return self.accidental.semitones
def _get_diatonic_pc_name(self):
return _diatonic_pc_number_to_diatonic_pc_name[
self.pitch_class._diatonic_pc_number
]
def _get_diatonic_pc_number(self):
diatonic_pc_name = self._get_diatonic_pc_name()
diatonic_pc_number = _diatonic_pc_name_to_diatonic_pc_number[diatonic_pc_name]
return diatonic_pc_number
def _get_diatonic_pitch_number(self):
diatonic_pitch_number = 7 * (self.octave.number - 4)
diatonic_pitch_number += self._get_diatonic_pc_number()
return diatonic_pitch_number
def _get_lilypond_format(self):
return self.name
def _list_contributions(self):
contributions = []
if self.arrow is None:
return contributions
string = r"\once \override Accidental.stencil ="
string += " #ly:text-interface::print"
contributions.append(string)
glyph = f"accidentals.{self.accidental.name}"
glyph += f".arrow{str(self.arrow).lower()}"
string = r"\once \override Accidental.text ="
string += rf' \markup {{ \musicglyph #"{glyph}" }}'
contributions.append(string)
return contributions
@property
def accidental(self) -> "Accidental":
"""
Gets accidental of named pitch.
.. container:: example
>>> abjad.NamedPitch("c''").accidental
Accidental(name='natural')
>>> abjad.NamedPitch("cs''").accidental
Accidental(name='sharp')
>>> abjad.NamedPitch("df''").accidental
Accidental(name='flat')
"""
return self.pitch_class.accidental
@property
def arrow(self):
"""
Gets arrow of named pitch.
.. container:: example
>>> abjad.NamedPitch("cs''").arrow is None
True
>>> abjad.NamedPitch("cs''", arrow=abjad.UP).arrow
<Vertical.UP: 1>
>>> abjad.NamedPitch("cs''", arrow=abjad.DOWN).arrow
<Vertical.DOWN: -1>
Displays arrow in interpreter representation:
>>> abjad.NamedPitch("cs''", arrow=abjad.DOWN)
NamedPitch("cs''", arrow=Vertical.DOWN)
Returns up, down or none.
"""
return self._pitch_class.arrow
@property
def hertz(self) -> float:
"""
Gets frequency of named pitch in Hertz.
.. container:: example
>>> abjad.NamedPitch("c''").hertz
523.25...
>>> abjad.NamedPitch("cs''").hertz
554.36...
>>> abjad.NamedPitch("df''").hertz
554.36...
"""
return super().hertz
@property
def name(self) -> str:
"""
Gets name of named pitch.
.. container:: example
>>> abjad.NamedPitch("c''").name
"c''"
>>> abjad.NamedPitch("cs''").name
"cs''"
>>> abjad.NamedPitch("df''").name
"df''"
"""
return f"{self.pitch_class.name}{self.octave.ticks}"
@property
def number(self) -> int | float:
"""
Gets number of named pitch.
.. container:: example
>>> abjad.NamedPitch("c''").number
12
>>> abjad.NamedPitch("cs''").number
13
>>> abjad.NamedPitch("df''").number
13
>>> abjad.NamedPitch("cf'").number
-1
"""
diatonic_pc_number = self.pitch_class._get_diatonic_pc_number()
pc_number = _diatonic_pc_number_to_pitch_class_number[diatonic_pc_number]
alteration = self.pitch_class._get_alteration()
octave_base_pitch = (self.octave.number - 4) * 12
return _math.integer_equivalent_number_to_integer(
pc_number + alteration + octave_base_pitch
)
@property
def octave(self) -> Octave:
"""
Gets octave of named pitch.
.. container:: example
>>> abjad.NamedPitch("c''").octave
Octave(number=5)
>>> abjad.NamedPitch("cs''").octave
Octave(number=5)
>>> abjad.NamedPitch("df''").octave
Octave(number=5)
"""
return self._octave
@property
def pitch_class(self) -> NamedPitchClass:
"""
Gets pitch-class of named pitch.
.. container:: example
>>> abjad.NamedPitch("c''").pitch_class
NamedPitchClass('c')
>>> abjad.NamedPitch("cs''").pitch_class
NamedPitchClass('cs')
>>> abjad.NamedPitch("df''").pitch_class
NamedPitchClass('df')
"""
return self._pitch_class
[docs] @classmethod
def from_hertz(class_, hertz) -> "NamedPitch":
"""
Makes named pitch from ``hertz``.
.. container:: example
>>> abjad.NamedPitch.from_hertz(440)
NamedPitch("a'")
REGRESSION. Returns c'' (C5) and not c' (C4):
>>> abjad.NamedPitch.from_hertz(519)
NamedPitch("c''")
"""
return super().from_hertz(hertz)
[docs] def get_name(self, locale=None) -> str:
"""
Gets name of named pitch according to ``locale``.
.. container:: example
>>> abjad.NamedPitch("cs''").get_name()
"cs''"
>>> abjad.NamedPitch("cs''").get_name(locale="us")
'C#5'
Set ``locale`` to ``'us'`` or none.
"""
if locale is None:
return self.name
elif locale == "us":
name = self._get_diatonic_pc_name().upper()
return f"{name}{self.accidental.symbol}{self.octave.number}"
else:
raise ValueError(f"must be 'us' or none: {locale!r}.")
[docs] def invert(self, axis=None) -> "NamedPitch":
"""
Inverts named pitch around ``axis``.
Inverts pitch around middle C explicitly:
.. container:: example
>>> abjad.NamedPitch("d'").invert("c'")
NamedPitch('bf')
>>> abjad.NamedPitch('bf').invert("c'")
NamedPitch("d'")
Inverts pitch around middle C implicitly:
>>> abjad.NamedPitch("d'").invert()
NamedPitch('bf')
>>> abjad.NamedPitch("bf").invert()
NamedPitch("d'")
Inverts pitch around A3:
>>> abjad.NamedPitch("d'").invert("a")
NamedPitch('e')
Interprets none-valued ``axis`` equal to middle C.
"""
return super().invert(axis=axis)
[docs] def multiply(self, n=1) -> "NamedPitch":
"""
Multiplies named pitch.
.. container:: example
>>> abjad.NamedPitch("d'").multiply(1)
NamedPitch("d'")
>>> abjad.NamedPitch("d'").multiply(3)
NamedPitch("fs'")
>>> abjad.NamedPitch("d'").multiply(6)
NamedPitch("c''")
>>> abjad.NamedPitch("d'").multiply(6.5)
NamedPitch("cs''")
"""
return super().multiply(n=n)
[docs] def respell(self, accidental="sharps"):
"""
Respells named pitch with ``accidental``.
.. container:: example
>>> abjad.NamedPitch("cs").respell(accidental="flats")
NamedPitch('df')
>>> abjad.NamedPitch("df").respell(accidental="sharps")
NamedPitch('cs')
"""
if accidental == "sharps":
dictionary = _pitch_class_number_to_pitch_class_name_with_sharps
else:
assert accidental == "flats"
dictionary = _pitch_class_number_to_pitch_class_name_with_flats
name = dictionary[self.pitch_class.number]
candidate = type(self)((name, self.octave.number))
if candidate.number == self.number - 12:
candidate = type(self)(candidate, octave=candidate.octave.number + 1)
elif candidate.number == self.number + 12:
candidate = type(self)(candidate, octave=candidate.octave.number - 1)
assert candidate.number == self.number
return candidate
[docs] def simplify(self) -> "NamedPitch":
"""
Reduce alteration to between -2 and 2 while maintaining identical pitch number.
>>> abjad.NamedPitch("cssqs'").simplify()
NamedPitch("dqs'")
>>> abjad.NamedPitch("cfffqf'").simplify()
NamedPitch('aqf')
>>> float(abjad.NamedPitch("cfffqf'").simplify()) == float(abjad.NamedPitch("aqf"))
True
.. note:: LilyPond by default only supports accidentals from
double-flat to double-sharp.
Returns named pitch.
"""
alteration = self._get_alteration()
if abs(alteration) <= 2:
return self
diatonic_pc_number = self._get_diatonic_pc_number()
octave = int(self.octave)
while alteration > 2:
step_size = 2
if diatonic_pc_number == 2: # e to f
step_size = 1
elif diatonic_pc_number == 6: # b to c
step_size = 1
octave += 1
diatonic_pc_number = (diatonic_pc_number + 1) % 7
alteration -= step_size
while alteration < -2:
step_size = 2
if diatonic_pc_number == 3: # f to e
step_size = 1
elif diatonic_pc_number == 0: # c to b
step_size = 1
octave -= 1
diatonic_pc_number = (diatonic_pc_number - 1) % 7
alteration += step_size
diatonic_pc_name = _diatonic_pc_number_to_diatonic_pc_name[diatonic_pc_number]
accidental = Accidental(alteration)
pitch_name = f"{diatonic_pc_name}{accidental!s}{octave!s}"
return type(self)(pitch_name, arrow=self.arrow)
[docs] def transpose(self, n=0) -> "NamedPitch":
"""
Transposes named pitch by index ``n``.
Transposes C4 up a minor second:
.. container:: example
>>> abjad.NamedPitch("c'").transpose(n="m2")
NamedPitch("df'")
Transposes C4 down a major second:
>>> abjad.NamedPitch("c'").transpose(n="-M2")
NamedPitch('bf')
"""
interval = NamedInterval(n)
pitch_number = self.number + interval.semitones
diatonic_pc_number = self._get_diatonic_pc_number()
diatonic_pc_number += interval.staff_spaces
diatonic_pc_number %= 7
diatonic_pc_name = _diatonic_pc_number_to_diatonic_pc_name[diatonic_pc_number]
pc = _diatonic_pc_name_to_pitch_class_number[diatonic_pc_name]
nearest_neighbor = self._to_nearest_octave(pitch_number, pc)
semitones = pitch_number - nearest_neighbor
accidental = Accidental(semitones)
octave_number = int(math.floor((pitch_number - semitones) / 12)) + 4
octave = Octave(octave_number)
name = diatonic_pc_name + str(accidental) + octave.ticks
return type(self)(name)
# mypy currently does not support functools.total_ordering
# https://github.com/python/mypy/issues/4610
[docs]@functools.total_ordering
class NumberedPitch(Pitch):
r"""
Numbered pitch.
.. container:: example
Initializes from number:
>>> abjad.NumberedPitch(13)
NumberedPitch(13)
Initializes from other numbered pitch:
>>> abjad.NumberedPitch(abjad.NumberedPitch(13))
NumberedPitch(13)
Initializes from pitch-class / octave pair:
>>> abjad.NumberedPitch((1, 5))
NumberedPitch(13)
"""
__slots__ = ("_number",)
def __init__(self, number=0, *, arrow=None, octave=None):
super().__init__(number or 0, arrow=arrow, octave=octave)
[docs] def __add__(self, argument) -> "NumberedPitch":
"""
Adds ``argument`` to numbered pitch.
.. container:: example
>>> abjad.NumberedPitch(12) + abjad.NumberedPitch(13)
NumberedPitch(25)
>>> abjad.NumberedPitch(13) + abjad.NumberedPitch(12)
NumberedPitch(25)
"""
argument = type(self)(argument)
semitones = float(self) + float(argument)
return type(self)(semitones)
[docs] def __eq__(self, argument) -> bool:
"""
Is true when ``argument`` is a numbered pitch with ``number`` the same as this
numbered pitch.
"""
if isinstance(argument, int | float):
argument = type(self)(argument)
if isinstance(argument, type(self)):
return (
self.number == argument.number
and self.arrow == argument.arrow
and self.octave == argument.octave
)
return False
[docs] def __hash__(self):
"""
Hashes numbered pitch.
"""
return super().__hash__()
# mypy currently does not support functools.total_ordering
# https://github.com/python/mypy/issues/4610
# remove __le__ (in favor of __lt__) when mypy supports functools.total_ordering
# or, refactor this class as a dataclass and then remove __le__
[docs] def __le__(self, argument) -> bool:
r"""Is true when ``argument`` can be coerced to a numbered pitch and when this
numbered pitch is less or equal to ``argument``.
.. container:: example
>>> pitch_1 = abjad.NumberedPitch(12)
>>> pitch_2 = abjad.NumberedPitch(12)
>>> pitch_3 = abjad.NumberedPitch(13)
>>> pitch_1 <= pitch_1
True
>>> pitch_1 <= pitch_2
True
>>> pitch_1 <= pitch_3
True
>>> pitch_2 <= pitch_1
True
>>> pitch_2 <= pitch_2
True
>>> pitch_2 <= pitch_3
True
>>> pitch_3 <= pitch_1
False
>>> pitch_3 <= pitch_2
False
>>> pitch_3 <= pitch_3
True
"""
try:
argument = type(self)(argument)
except (ValueError, TypeError):
return False
return self.number <= argument.number
[docs] def __lt__(self, argument) -> bool:
r"""Is true when ``argument`` can be coerced to a numbered pitch and when this
numbered pitch is less than ``argument``.
.. container:: example
>>> pitch_1 = abjad.NumberedPitch(12)
>>> pitch_2 = abjad.NumberedPitch(12)
>>> pitch_3 = abjad.NumberedPitch(13)
>>> pitch_1 < pitch_1
False
>>> pitch_1 < pitch_2
False
>>> pitch_1 < pitch_3
True
>>> pitch_2 < pitch_1
False
>>> pitch_2 < pitch_2
False
>>> pitch_2 < pitch_3
True
>>> pitch_3 < pitch_1
False
>>> pitch_3 < pitch_2
False
>>> pitch_3 < pitch_3
False
"""
try:
argument = type(self)(argument)
except (ValueError, TypeError):
return False
return self.number < argument.number
[docs] def __neg__(self) -> "NumberedPitch":
"""
Negates numbered pitch.
.. container:: example
>>> -abjad.NumberedPitch(13.5)
NumberedPitch(-13.5)
>>> -abjad.NumberedPitch(-13.5)
NumberedPitch(13.5)
"""
return type(self)(-self.number)
[docs] def __radd__(self, argument) -> "NumberedPitch":
"""
Adds numbered pitch to ``argument``.
.. container:: example
>>> pitch = abjad.NumberedPitch(13)
>>> abjad.NumberedPitch(12).__radd__(pitch)
NumberedPitch(25)
>>> pitch = abjad.NumberedPitch(12)
>>> abjad.NumberedPitch(13).__radd__(pitch)
NumberedPitch(25)
"""
argument = type(self)(argument)
return argument.__add__(self)
[docs] def __repr__(self):
"""
Gets repr.
"""
return f"{type(self).__name__}({self.number!r})"
[docs] def __sub__(self, argument) -> "NumberedInterval":
"""
Subtracts ``argument`` from numbered pitch.
.. container:: example
>>> abjad.NumberedPitch(12) - abjad.NumberedPitch(12)
NumberedInterval(0)
>>> abjad.NumberedPitch(12) - abjad.NumberedPitch(13)
NumberedInterval(1)
>>> abjad.NumberedPitch(13) - abjad.NumberedPitch(12)
NumberedInterval(-1)
"""
if isinstance(argument, type(self)):
return NumberedInterval.from_pitch_carriers(self, argument)
interval = NumberedInterval(argument)
interval = -interval
return interval.transpose(self)
def _apply_accidental(self, accidental=None):
accidental = Accidental(accidental)
semitones = self.number + accidental.semitones
return type(self)(semitones)
def _from_named_parts(self, dpc_number, alteration, octave):
pc_number = _diatonic_pc_number_to_pitch_class_number[dpc_number]
pc_number += alteration
pc_number += (octave - 4) * 12
self._number = _math.integer_equivalent_number_to_integer(pc_number)
octave_number, pc_number = divmod(self._number, 12)
self._pitch_class = NumberedPitchClass(pc_number)
self._octave = Octave(octave_number + 4)
def _from_number(self, number):
self._number = self._to_nearest_quarter_tone(number)
octave_number, pc_number = divmod(self._number, 12)
self._octave = Octave(octave_number + 4)
self._pitch_class = NumberedPitchClass(pc_number)
def _from_pitch_or_pitch_class(self, pitch_or_pitch_class):
self._number = self._to_nearest_quarter_tone(float(pitch_or_pitch_class))
octave_number, pc_number = divmod(self._number, 12)
self._octave = Octave(octave_number + 4)
self._pitch_class = NumberedPitchClass(
pc_number, arrow=pitch_or_pitch_class.arrow
)
def _get_diatonic_pc_name(self):
return self.pitch_class._get_diatonic_pc_name()
def _get_diatonic_pc_number(self):
return self.numbered_pitch_class._get_diatonic_pc_number()
def _get_diatonic_pitch_number(self):
result = 7 * (self.octave.number - 4)
result += self._get_diatonic_pc_number()
return result
def _get_lilypond_format(self):
return self.name
@property
def accidental(self) -> Accidental:
"""
Gets accidental of numbered pitch.
.. container:: example
>>> abjad.NumberedPitchClass(13).accidental
Accidental(name='sharp')
"""
return self.pitch_class.accidental
@property
def arrow(self):
"""
Gets arrow of numbered pitch.
.. container:: example
>>> abjad.NumberedPitch(13).arrow is None
True
>>> abjad.NumberedPitch(13, arrow=abjad.UP).arrow
<Vertical.UP: 1>
>>> abjad.NumberedPitch(13, arrow=abjad.DOWN).arrow
<Vertical.DOWN: -1>
Returns up, down or none.
"""
return self._pitch_class.arrow
@property
def hertz(self) -> float:
"""
Gets frequency of numbered pitch in Hertz.
.. container:: example
>>> abjad.NumberedPitch(9).hertz
440.0
>>> abjad.NumberedPitch(0).hertz
261.62...
>>> abjad.NumberedPitch(12).hertz
523.25...
"""
return super().hertz
@property
def name(self) -> str:
"""
Gets name of numbered pitch.
.. container:: example
>>> abjad.NumberedPitch(13).name
"cs''"
"""
return f"{self.pitch_class.name}{self.octave.ticks}"
@property
def number(self) -> int | float:
"""
Gets number of numbered pitch.
.. container:: example
>>> abjad.NumberedPitch(13).number
13
"""
pc_number = float(self.pitch_class)
octave_base_pitch = (self.octave.number - 4) * 12
return _math.integer_equivalent_number_to_integer(pc_number + octave_base_pitch)
@property
def octave(self) -> Octave:
"""
Gets octave of numbered pitch.
.. container:: example
>>> abjad.NumberedPitch(13).octave
Octave(number=5)
"""
return self._octave
@property
def pitch_class(self) -> NumberedPitchClass:
"""
Gets pitch-class of numbered pitch.
.. container:: example
>>> abjad.NumberedPitch(13).pitch_class
NumberedPitchClass(1)
"""
return self._pitch_class
[docs] @classmethod
def from_hertz(class_, hertz) -> "NumberedPitch":
"""
Makes numbered pitch from ``hertz``.
.. container:: example
>>> abjad.NumberedPitch.from_hertz(440)
NumberedPitch(9)
REGRESSION. Returns 12 (not 0):
>>> abjad.NumberedPitch.from_hertz(519)
NumberedPitch(12)
"""
return super().from_hertz(hertz)
[docs] def get_name(self, locale=None) -> str:
"""
Gets name of numbered pitch name according to ``locale``.
.. container:: example
>>> abjad.NumberedPitch(13).get_name()
"cs''"
>>> abjad.NumberedPitch(13).get_name(locale="us")
'C#5'
Set ``locale`` to ``"us"`` or none.
"""
return NamedPitch(self).get_name(locale=locale)
[docs] def interpolate(self, stop_pitch, fraction) -> "NumberedPitch":
"""
Interpolates between numbered pitch and ``stop_pitch`` by ``fraction``.
Interpolates from C4 to C5:
.. container:: example
>>> start_pitch = abjad.NumberedPitch(0)
>>> stop_pitch = abjad.NumberedPitch(12)
>>> start_pitch.interpolate(stop_pitch, 0)
NumberedPitch(0)
>>> start_pitch.interpolate(stop_pitch, (1, 4))
NumberedPitch(3)
>>> start_pitch.interpolate(stop_pitch, (1, 2))
NumberedPitch(6)
>>> start_pitch.interpolate(stop_pitch, (3, 4))
NumberedPitch(9)
>>> start_pitch.interpolate(stop_pitch, 1)
NumberedPitch(12)
Interpolates from C5 to C4:
>>> start_pitch = abjad.NumberedPitch(12)
>>> stop_pitch = abjad.NumberedPitch(0)
>>> start_pitch.interpolate(stop_pitch, 0)
NumberedPitch(12)
>>> start_pitch.interpolate(stop_pitch, (1, 4))
NumberedPitch(9)
>>> start_pitch.interpolate(stop_pitch, (1, 2))
NumberedPitch(6)
>>> start_pitch.interpolate(stop_pitch, (3, 4))
NumberedPitch(3)
>>> start_pitch.interpolate(stop_pitch, 1)
NumberedPitch(0)
"""
try:
fraction = fractions.Fraction(*fraction)
except TypeError:
fraction = fractions.Fraction(fraction)
assert 0 <= fraction <= 1, repr(fraction)
stop_pitch = type(self)(stop_pitch)
distance = stop_pitch - self
distance = abs(distance.semitones)
distance = fraction * distance
distance = int(distance)
if stop_pitch < self:
distance *= -1
pitch_number = self.number
pitch_number = pitch_number + distance
pitch = NumberedPitch(pitch_number)
if self <= stop_pitch:
triple = (self, pitch, stop_pitch)
assert self <= pitch <= stop_pitch, triple
else:
triple = (self, pitch, stop_pitch)
assert self >= pitch >= stop_pitch, triple
return pitch
[docs] def invert(self, axis=None) -> "NumberedPitch":
"""
Inverts numbered pitch around ``axis``.
Inverts pitch-class about pitch-class 0 explicitly:
.. container:: example
>>> abjad.NumberedPitch(2).invert(0)
NumberedPitch(-2)
>>> abjad.NumberedPitch(-2).invert(0)
NumberedPitch(2)
Inverts pitch-class about pitch-class 0 implicitly:
>>> abjad.NumberedPitch(2).invert()
NumberedPitch(-2)
>>> abjad.NumberedPitch(-2).invert()
NumberedPitch(2)
Inverts pitch-class about pitch-class -3:
>>> abjad.NumberedPitch(2).invert(-3)
NumberedPitch(-8)
"""
return Pitch.invert(self, axis=axis)
[docs] def multiply(self, n=1) -> "NumberedPitch":
"""
Multiplies numbered pitch by index ``n``.
.. container:: example
>>> abjad.NumberedPitch(14).multiply(3)
NumberedPitch(42)
"""
return super().multiply(n=n)
[docs] def transpose(self, n=0) -> "NumberedPitch":
"""
Tranposes numbered pitch by ``n`` semitones.
.. container:: example
>>> abjad.NumberedPitch(13).transpose(1)
NumberedPitch(14)
"""
interval = NumberedInterval(n)
return type(self)(float(self) + float(interval))
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StaffPosition:
"""
Staff position.
.. container:: example
Middle line of staff:
>>> abjad.StaffPosition(0)
StaffPosition(number=0)
One space below middle line of staff:
>>> abjad.StaffPosition(-1)
StaffPosition(number=-1)
One line below middle line of staff:
>>> abjad.StaffPosition(-2)
StaffPosition(number=-2)
"""
number: int = 0
[docs] def __post_init__(self):
assert isinstance(self.number, int), repr(self.number)