Skip to content

Pitch

Pitch

Pitch(
    pitch: Union[Pitch, int, float, str, None] = 60,
    alt: Union[int, float, None] = None,
    octave: Union[int, None] = None,
    accidental_chars: Optional[str] = None,
)

Represents a symbolic musical pitch.

A pitch is represented by a key_num and an alt. The key_num is a number that corresponds to the MIDI convention where C4 is 60, C# is 61, etc., but generalized to floats (60.5 would be C4-quarter-tone-sharp). The alt is an alteration, where +1 represents a sharp and -1 represents a flat. Alterations can also be, for example, 2 (double-sharp) or -0.5 (quarter-tone flat). The symbolic note name is derived by subtracting alt from key_num.

E.g., C#4 has key_num=61, alt=1, so 61-1 gives us 60, corresponding to note name C. A Db has the same key_num=61, but alt=-1, and 61-(-1) gives us 62, corresponding to note name D. There is no representation for the “natural sign” (other than alt=0, which could imply no accidental) or “courtesy accidentals.” Because accidentals normally “stick” within a measure or are implied by key signatures, accidentals are often omitted in the score presentation. Nonetheless, these implied accidentals are encoded in the alt attribute and key_num is the intended pitch with the accidental applied.

Author: Roger B. Dannenberg

Parameters:

  • pitch (Union[int, float, str, None], default: 60 ) –

    Optional MIDI key_num or string Pitch name. Syntax is A-G followed by accidentals (see accidental_chars below) followed by octave number. (Defaults to 60)

  • alt (Union[int, float, None], default: None ) –

    If pitch is a number, alt is an optional alteration (Defaults to 0). If pitch - alt does not result in a diatonic pitch number, alt is adjusted, normally choosing spellings C#, Eb, F#, Ab, and Bb. If pitch is a string, alt must be None.

  • octave (Optional[int], default: None ) –

    If pitch is a string without an octave specification and octave is an int, then octave is used to specify the octave, where 4 denotes the key_num range 60 through 71. octave defaults defaults to -1, which yields pitch class key_nums 0-11).

  • accidental_chars (Optional[str], default: None ) –

    Allows parsing of pitch names with customized accidental characters. The value is a tuple or list consisting of a string of flat characters and a string of sharp characters, e.g. `["fb", "s#"]. (Defaults to None, which admits '♭', 'b' or '-' for flat, and '♯', '#', and '+' for sharp, but does not accept 'f' and 's'.)

Attributes:

  • key_num (float) –

    MIDI key number, e.g., C4 = 60, generalized to float.

  • alt (float) –

    Alteration, e.g., flat = -1.

Examples:

>>> p = Pitch(64)
>>> p
Pitch(name='E4', key_num=64)
>>> p.octave
4
>>> p = Pitch("E4")
>>> p.octave
4
>>> p = Pitch("F#################2")
>>> p.alt
17
>>> p.octave
2
>>> p = Pitch("E--------------------4")
>>> p.alt
-20
>>> p.octave
4
>>> p.register
2
>>> Pitch(61.5, alt=1.5)
Pitch(name='C?4', key_num=61.5)
>>> # key_num - alt must be a diatonic pitch number. If not, key_num
>>> # gets priority and alt is adjusted to the smallest valid value.
>>> # Here, alt is adjusted to 0, which preserves key_num 60:
>>> Pitch(60, alt=1.4)
Pitch(name='C4', key_num=60)
Source code in amads/core/pitch.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def __init__(self,
             pitch: Union["Pitch", int, float, str, None] = 60,
             alt: Union[int, float, None] = None,
             octave: Union[int, None] = None,
             accidental_chars: Optional[str] = None):
    if isinstance(pitch, str):
        if alt is not None:
            raise ValueError("If pitch is a string, alt must be None")
        self.key_num, self.alt = Pitch.from_name(pitch, octave, 
                                                 accidental_chars)
    elif isinstance(pitch, Pitch):
        self.key_num = pitch.key_num
        self.alt = pitch.alt
    elif pitch is None:
        self.key_num = None  # type: ignore (None is allowed, but if we put
        # that in the type annotation, we have to annotate every use of
        # arithmetic on key_num.)
        self.alt = (0 if alt is None else alt)
    else:  # pitch is a number (int or float)
        # this will raise a ValueError if pitch is not some kind of number:
        pitch = float(pitch)  # converts numpy.int64, nympy.floating, etc.
        if pitch.is_integer():  # for nicer printing
            pitch = int(pitch)  # pitch numbers as integers.
        self.key_num = pitch
        self.alt = (0 if alt is None else alt)
        self._fix_alteration()

Attributes

step property

step: str

The diatonic name of the pitch: A, B, C, D, E, F, or G.

The diatonic name corresponds to letter name without accidentals.

Returns:

  • str

    The name of the pitch, a letter in "A" through "G".

name_with_octave property

name_with_octave: str

The string name with octave, e.g., "C4", "B#3", etc.

The octave number is calculated by

(key_num - alteration) // 12 + 1  # (integer division)

and refers to the pitch before alteration, e.g., C4 is enharmonic to B#3 and represents the same (more or less) pitch even though the written octave numbers differ.

See also get_name_with_octave, which accepts a parameter to specify custom characters to represent accidentals.

octave property

octave: int

The octave number of the note name.

The note name is based on key_num - alt, e.g., C4 has octave 4 while B#3 has octave 3.

pitch_class property

pitch_class: int

The pitch class of the note, e.g., 0, 1, 2, ..., 11.

The pitch class is the key_num modulo 12, which gives the class of this pitch in the range 0-11. If the key_num is non-integer, it is rounded.

Returns:

  • int

    The pitch class of the note.

register property

register: int

Returns the absolute octave number based on floor(key_num).

Both C4 and B#3 have register 4.

Functions

_fix_alteration

_fix_alteration() -> None

Fix the alteration to ensure it is a valid value.

I.e., that (key_num - alt) % 12 denotes one of {C D E F G A B}. If the invariant is false, we give priority to key_num and find the smallest value of alt that makes it true. For integer key_num, we break ties (enharmonics) by favoring C#, Eb, F#, Ab, and Bb.

Source code in amads/core/pitch.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def _fix_alteration(self) -> None:
    """Fix the alteration to ensure it is a valid value.

    I.e., that `(key_num - alt) % 12` denotes one of {C D E F G A B}.
    If the invariant is false, we give priority to key_num and find
    the smallest value of alt that makes it true. For integer key_num,
    we break ties (enharmonics) by favoring C#, Eb, F#, Ab, and Bb.
    """
    unaltered = self.key_num - self.alt
    # if alt is off by a tiny amount, we just correct it
    if abs(unaltered - round(unaltered)) < 1e-6:
        unaltered = round(unaltered)
        self.alt = self.key_num - unaltered
    # if unaltered was even close to an integer, it will become an
    # integer so the following test is correct:
    if isinstance(unaltered, int) and (unaltered % 12) in DIATONIC:
        return  # valid key_num and alt

    # If alt is not an integer, we adjust it to be as small as
    # possible (< 1 when unaltered is in C-to-E or G-to-B, and < 0.5
    # when pc is in E-to-F or B to C).  First, we need to force
    # key_num to be an integer. For 3.10 compatibility, can only
    # send .is_integer() to a float:
    if not float(self.key_num).is_integer():
        # if alt could be less than 0.5, make it so:
        closest_pc = round(self.key_num) % 12
        self.alt = self.key_num - round(self.key_num)
        # now alt < 0.5
        if not closest_pc in DIATONIC:
            sign = 1 if self.alt > 0 else -1
            self.alt -= sign
        assert abs(self.alt) < 1, "alt must be < 1 in magnitude"
        # now we have a small value for a non-integer alt
    else:
        # again, we have to change alt (an integer this time). Make it 
        # -1, 0, or 1, giving priority to default spellings C#, Eb, F#,
        # Ab, and Bb.
        pc = self.key_num % 12
        if pc in [1, 6]:  # C#->C, F#->F
            self.alt = 1
        elif pc in [3, 8, 10]:  # Eb->E, Ab->A, Bb->B
            self.alt = -1
        else:
            self.alt = 0
    unaltered = self.key_num - self.alt
    assert (unaltered - round(unaltered)) < 1e-6
    assert round(unaltered) % 12 in DIATONIC, "pc must be in valid"

as_tuple

as_tuple()

Return a tuple representation of the Pitch instance.

Returns:

  • tuple

    A tuple containing the key_num and alt values.

Source code in amads/core/pitch.py
200
201
202
203
204
205
206
207
208
def as_tuple(self):
    """Return a tuple representation of the `Pitch` instance.

    Returns
    -------
    tuple
        A tuple containing the `key_num` and `alt` values.
    """
    return (self.key_num, self.alt)

__lt__

__lt__(other) -> bool

Check if this Pitch instance is less than another Pitch instance. Pitches are compared first by key_num and then by alt. Pitches with sharps (i.e. positive alt) are considered lower because their letter names are lower in the musical alphabet.

Parameters:

  • other (Pitch) –

    The other Pitch instance to compare with.

Returns:

  • bool

    True if this Pitch instance is less than the other, False otherwise.

Source code in amads/core/pitch.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def __lt__(self, other) -> bool:
    """Check if this Pitch instance is less than another Pitch instance.
    Pitches are compared first by `key_num` and then by `alt`. Pitches
    with sharps (i.e. positive alt) are considered lower because
    their letter names are lower in the musical alphabet.

    Parameters
    ----------
    other : Pitch
        The other Pitch instance to compare with.

    Returns
    -------
    bool
        True if this Pitch instance is less than the other, False otherwise.
    """
    return (self.key_num, -self.alt) < (other.key_num, -other.alt)

get_name_with_octave

get_name_with_octave(accidental_chars: str = 'b#') -> str

Return string name with octave, e.g., C4, B#3, etc.

See the name_with_octave property for details.

Parameters:

  • accidental_chars (str, default: 'b#' ) –

    The characters to use for flat and sharp accidentals. (Defaults to "b#")

Returns:

  • str

    The string representation of the pitch name with octave.

Source code in amads/core/pitch.py
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def get_name_with_octave(self, accidental_chars: str = "b#") -> str:
    """Return string name with octave, e.g., C4, B#3, etc.

    See the [name_with_octave][amads.core.pitch.Pitch.name_with_octave]
    property for details.

    Parameters
    ----------
    accidental_chars : str, optional
        The characters to use for flat and sharp accidentals.
        (Defaults to "b#")

    Returns
    -------
    str
        The string representation of the pitch name with octave.
    """
    return ("unpitched" if self.key_num is None
                        else self.name + str(self.octave))

enharmonic

enharmonic() -> Pitch

Construct an enharmonic equivalent.

If alt is non-zero, return a Pitch where alt is zero or has the opposite sign and where alt is minimized. E.g. enharmonic(Cbb) is A# (not Bb). If alt is zero, return a Pitch with alt of +1 or -1 if possible. Otherwise, return a Pitch with alt of -2 (Ebb, Abb or Bbb). Note the difference between this and simplest_enharmonic.

Returns:

  • Pitch

    A new Pitch object representing the enharmonic equivalent.

Examples:

>>> Pitch("C4").enharmonic()
Pitch(name='B#3', key_num=60)
>>> Pitch("B3").enharmonic()
Pitch(name='Cb4', key_num=59)
>>> Pitch("B#3").enharmonic()
Pitch(name='C4', key_num=60)
>>> bds = Pitch("B##3")
>>> bds.enharmonic() # change of direction
Pitch(name='Db4', key_num=61)
>>> bds.upper_enharmonic()  # note the difference
Pitch(name='C#4', key_num=61)
>>> Pitch("Dbb4").enharmonic()
Pitch(name='C4', key_num=60)
Source code in amads/core/pitch.py
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def enharmonic(self) -> "Pitch":
    """Construct an enharmonic equivalent.

    If `alt` is non-zero, return a Pitch where `alt` is zero
    or has the opposite sign and where `alt` is minimized. E.g.
    enharmonic(Cbb) is A# (not Bb). If alt is zero, return a
    Pitch with alt of +1 or -1 if possible. Otherwise, return
    a Pitch with alt of -2 (Ebb, Abb or Bbb).
    Note the difference between this and `simplest_enharmonic`.

    Returns
    -------
    Pitch
        A new Pitch object representing the enharmonic equivalent.

    Examples
    --------
    >>> Pitch("C4").enharmonic()
    Pitch(name='B#3', key_num=60)

    >>> Pitch("B3").enharmonic()
    Pitch(name='Cb4', key_num=59)

    >>> Pitch("B#3").enharmonic()
    Pitch(name='C4', key_num=60)

    >>> bds = Pitch("B##3")
    >>> bds.enharmonic() # change of direction
    Pitch(name='Db4', key_num=61)

    >>> bds.upper_enharmonic()  # note the difference
    Pitch(name='C#4', key_num=61)

    >>> Pitch("Dbb4").enharmonic()
    Pitch(name='C4', key_num=60)
    """
    alt = self.alt
    unaltered = round(self.key_num - alt)
    if alt < 0:
        while alt < 0 or (unaltered % 12) not in [0, 2, 4, 5, 7, 9, 11]:
            unaltered -= 1
            alt += 1
    elif alt > 0:
        while alt > 0 or (unaltered % 12) not in [0, 2, 4, 5, 7, 9, 11]:
            unaltered += 1
            alt -= 1
    else:  # alt == 0
        unaltered = unaltered % 12
        if unaltered in [0, 5]:  # C->B#, F->E#
            alt = 1
        elif unaltered in [11, 4]:  # B->Cb, E->Fb
            alt = -1
        else:  # A->Bbb, D->Ebb, G->Abb
            alt = -2
    return Pitch(self.key_num, alt)

simplest_enharmonic

simplest_enharmonic(sharp_or_flat: Optional[str] = 'default') -> Pitch

Create Pitch object with the simplest enharmonic representation.

I.e., if there exists an enharmonic-equivalent pitch with no alterations, then use that. If the Pitch is already in simplest form (e.g., C4), it is simply returned. If an alteration is needed, then use sharps or flats depending on sharp_or_flat. If sharp_or_flat is omitted, the same enharmonic choice as the Pitch constructor is used (C#, Eb, F#, Ab, and Bb).

Parameters:

  • sharp_or_flat (Optional[str], default: 'default' ) –

    This is only relevant if the pitch needs an alteration, otherwise it is unused. The value can be "sharp" (use sharps), "flat" (use flats), and otherwise use the same enharmonic choice as the Pitch constructor.

Examples:

>>> bds = Pitch("B##3")
>>> bds.simplest_enharmonic()
Pitch(name='C#4', key_num=61)
>>> bds.simplest_enharmonic(sharp_or_flat="flat")
Pitch(name='Db4', key_num=61)
>>> Pitch("C4").simplest_enharmonic()
Pitch(name='C4', key_num=60)

Returns:

  • Pitch

    A Pitch object representing the enharmonic equivalent.

Source code in amads/core/pitch.py
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
def simplest_enharmonic(self,
        sharp_or_flat: Optional[str] = 'default') -> "Pitch":
    """
    Create Pitch object with the simplest enharmonic representation.

    I.e., if there exists an enharmonic-equivalent pitch with no
    alterations, then use that. If the Pitch is already in simplest
    form (e.g., C4), it is simply returned. If an alteration is
    needed, then use sharps or flats depending on `sharp_or_flat`.
    If `sharp_or_flat` is omitted, the same enharmonic choice
    as the Pitch constructor is used (C#, Eb, F#, Ab, and Bb).

    Parameters
    ----------
    sharp_or_flat: str
        This is only relevant if the pitch needs an alteration, otherwise
        it is unused. The value can be "sharp" (use sharps), "flat" (use
        flats), and otherwise use the same enharmonic choice as the Pitch
        constructor.

    Examples
    --------

    >>> bds = Pitch("B##3")
    >>> bds.simplest_enharmonic()
    Pitch(name='C#4', key_num=61)

    >>> bds.simplest_enharmonic(sharp_or_flat="flat")
    Pitch(name='Db4', key_num=61)

    >>> Pitch("C4").simplest_enharmonic()
    Pitch(name='C4', key_num=60)

    Returns
    -------
    Pitch
        A Pitch object representing the enharmonic equivalent.
    """
    if self.alt in [None, 0]:
        return self

    if self.pitch_class in [0, 2, 4, 5, 7, 9, 11]:  # C, D, E, F, G, A, B
        return Pitch(self.key_num)
    elif sharp_or_flat == "sharp":  # unaltered in 1, 3, 6, 8, 10
        return Pitch(self.key_num, 1)
    elif sharp_or_flat == "flat":
        return Pitch(self.key_num, -1)
    else:  # let Pitch figure out which enharmonic spelling (alt) to use:
        return Pitch(self.key_num)

upper_enharmonic

upper_enharmonic() -> Pitch

Return the enharmonic based on the note name above.

The result will have the next higher diatonic name with alt accordingly decreased by 1 or 2, e.g., C#->Db, C##->D, Cb->Dbbb.

Returns:

  • Pitch

    A Pitch object representing the upper enharmonic equivalent.

Examples:

>>> bds = Pitch("B##3")
>>> bds
Pitch(name='B##3', key_num=61)
>>> cis = bds.upper_enharmonic()
>>> cis
Pitch(name='C#4', key_num=61)
>>> des = cis.upper_enharmonic()
>>> des
Pitch(name='Db4', key_num=61)
>>> des.upper_enharmonic()
Pitch(name='Ebbb4', key_num=61)
>>> Pitch("D4").upper_enharmonic()
Pitch(name='Ebb4', key_num=62)
Source code in amads/core/pitch.py
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
def upper_enharmonic(self) -> "Pitch":
    """
    Return the enharmonic based on the note name above.

    The result will have the next higher diatonic name
    with `alt` accordingly decreased by 1 or 2, e.g.,
    C#->Db, C##->D, Cb->Dbbb.

    Returns
    -------
    Pitch
        A Pitch object representing the upper enharmonic equivalent.


    Examples
    --------
    >>> bds = Pitch("B##3")
    >>> bds
    Pitch(name='B##3', key_num=61)

    >>> cis = bds.upper_enharmonic()
    >>> cis
    Pitch(name='C#4', key_num=61)

    >>> des = cis.upper_enharmonic()
    >>> des
    Pitch(name='Db4', key_num=61)

    >>> des.upper_enharmonic()
    Pitch(name='Ebbb4', key_num=61)

    >>> Pitch("D4").upper_enharmonic()
    Pitch(name='Ebb4', key_num=62)

    """
    alt = self.alt
    unaltered = round(self.key_num - alt) % 12
    if unaltered in [0, 2, 5, 7, 9]:  # C->D, D->E, F->G, G->A, A->B
        alt -= 2
    else:  # E->F, B->C
        alt -= 1
    return Pitch(self.key_num, alt)

lower_enharmonic

lower_enharmonic() -> Pitch

Return the enharmonic based on the note name below.

The result will have the next lower diatonic name with alt accordingly increased by 1 or 2, e.g., Db->C#, D->C##, D#->C###.

Returns:

  • Pitch

    A Pitch object representing the lower enharmonic equivalent.

Examples:

>>> Pitch("Db4").lower_enharmonic()
Pitch(name='C#4', key_num=61)
>>> Pitch("D4").lower_enharmonic()
Pitch(name='C##4', key_num=62)
>>> Pitch("C#4").lower_enharmonic()
Pitch(name='B##3', key_num=61)
Source code in amads/core/pitch.py
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
def lower_enharmonic(self) -> "Pitch":
    """Return the enharmonic based on the note name below.

    The result will have the next lower diatonic name
    with `alt` accordingly increased by 1 or 2, e.g.,
    Db->C#, D->C##, D#->C###.

    Returns
    -------
    Pitch
        A Pitch object representing the lower enharmonic equivalent.

    Examples
    --------
    >>> Pitch("Db4").lower_enharmonic()
    Pitch(name='C#4', key_num=61)

    >>> Pitch("D4").lower_enharmonic()
    Pitch(name='C##4', key_num=62)

    >>> Pitch("C#4").lower_enharmonic()
    Pitch(name='B##3', key_num=61)

    """
    alt = self.alt
    unaltered = round(self.key_num - alt) % 12
    if unaltered in [2, 4, 7, 9, 11]:  # D->C, E->D, G->F, A->G, B->A
        alt += 2
    else:  # F->E, C->B
        alt += 1
    return Pitch(self.key_num, alt)

PitchCollection dataclass

PitchCollection(pitches: list[Pitch])

Combined representations of more than one pitch. Differs from Chord which has onset, duration, and contains Notes, not Pitches.

Parameters:

  • pitches (list[Pitch]) –

    A list of Pitch instances.

Attributes:

  • pitches (list[Pitch]) –

    A list of Pitch instances.

Examples:

>>> test_case = ['G#4', 'G#4', 'B4', 'D4', 'F4', 'Ab4']  
>>> pitches = [Pitch(p) for p in test_case]  
>>> pitches_gathered = PitchCollection(pitches)
>>> pitches_gathered.pitch_name_multiset  
['G#4', 'G#4', 'B4', 'D4', 'F4', 'Ab4']
>>> pitches_gathered.pitch_num_multiset  
[68, 68, 71, 62, 65, 68]
>>> pitches_gathered.pitch_class_multiset  
[2, 5, 8, 8, 8, 11]
>>> pitches_gathered.pitch_class_set  
[2, 5, 8, 11]
>>> pitches_gathered.pitch_class_vector  
(0, 0, 1, 0, 0, 1, 0, 0, 3, 0, 0, 1)
>>> pitches_gathered.pitch_class_indicator_vector
(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)

Attributes

pitch_num_multiset property

pitch_num_multiset

Return a list of pitch numbers from the pitches in the collection.

pitch_name_multiset property

pitch_name_multiset

Return a list of pitch names with octaves from the pitches in the collection.

pitch_class_multiset property

pitch_class_multiset

Return a sorted list of pitch classes from the pitches in the collection, including duplicates.

pitch_class_set property

pitch_class_set

Return a sorted list of pitch classes from the pitches in the collection without duplicates.

pitch_class_vector property

pitch_class_vector

Return a pitch class vector (12-dimensional) representing the count of each pitch class in the collection.

pitch_class_indicator_vector property

pitch_class_indicator_vector

Return a pitch class indicator vector (12-dimensional) representing the presence (1) or absence (0) of each pitch class in the collection.