Source code for music21.pitch

# ------------------------------------------------------------------------------
# Name:         pitch.py
# Purpose:      music21 classes for representing pitches
#
# Authors:      Michael Scott Asato Cuthbert
#               Christopher Ariza
#
# Copyright:    Copyright © 2008-2019 Michael Scott Asato Cuthbert
# License:      BSD, see license.txt
# ------------------------------------------------------------------------------
'''
Classes for representing and manipulating pitches, pitch-space, and accidentals.

Each :class:`~music21.note.Note` object has a `Pitch` object embedded in it.
Some methods below, such as `Pitch.name`, `Pitch.step`, etc. are
made available directly in the `Note` object, so they will seem familiar.
'''
from __future__ import annotations

from collections import OrderedDict
import copy
import itertools
import math
import typing as t
from typing import overload  # Pycharm can't use an alias
import unittest

from music21 import base
from music21 import common
from music21.common.objects import SlottedObjectMixin
from music21.common.types import StepName
from music21 import defaults
from music21 import environment
from music21 import exceptions21
from music21 import interval
from music21 import prebase
from music21 import style

if t.TYPE_CHECKING:
    from music21 import chord
    from music21 import key
    from music21 import note

environLocal = environment.Environment('pitch')

type PitchClassString = t.Literal['a', 'A', 't', 'T', 'b', 'B', 'e', 'E']

STEPREF: dict[StepName, int] = {
    'C': 0,
    'D': 2,
    'E': 4,
    'F': 5,
    'G': 7,
    'A': 9,
    'B': 11,
}
NATURAL_PCS = (0, 2, 4, 5, 7, 9, 11)
STEPREF_REVERSED: dict[int, StepName] = {
    0: 'C',
    2: 'D',
    4: 'E',
    5: 'F',
    7: 'G',
    9: 'A',
    11: 'B',
}
STEPNAMES: set[StepName] = {'C', 'D', 'E', 'F', 'G', 'A', 'B'}  # set
STEP_TO_DNN_OFFSET: dict[StepName, int] = {
    'C': 0,
    'D': 1,
    'E': 2,
    'F': 3,
    'G': 4,
    'A': 5,
    'B': 6,
}


TWELFTH_ROOT_OF_TWO = 2.0 ** (1 / 12)

# how many significant digits to keep in pitch space resolution
# where 1 is a half step. this means that 4 significant digits of cents will be kept
PITCH_SPACE_SIG_DIGITS = 6

# basic accidental string and symbol definitions;
# additional symbolic and text-based alternatives
# are given in the set Accidental.set() method
MICROTONE_OPEN = '('
MICROTONE_CLOSE = ')'

accidentalNameToModifier = {
    'natural': '',
    'sharp': '#',
    'double-sharp': '##',
    'triple-sharp': '###',
    'quadruple-sharp': '####',
    'flat': '-',
    'double-flat': '--',
    'triple-flat': '---',
    'quadruple-flat': '----',
    'half-sharp': '~',
    'one-and-a-half-sharp': '#~',
    'half-flat': '`',
    'one-and-a-half-flat': '-`',
}

modifierToAccidentalName = {v: k for k, v in accidentalNameToModifier.items()}

unicodeFromModifier = OrderedDict([
    ('####', chr(0x1d12a) + chr(0x1d12a)),
    ('###', '\u266f' + chr(0x1d12a)),
    ('##', chr(0x1d12a)),  # 1D12A  # note that this must be expressed as a surrogate pair
    ('#~', '\u266f' + chr(0x1d132)),  # 1D132
    ('#', '\u266f'),
    ('~', chr(0x1d132)),  # 1D132
    ('----', chr(0x1d12b) + chr(0x1d12b)),
    ('---', '\u266D'),
    ('--', chr(0x1d12b)),
    ('-`', '\u266D' + chr(0x1d132)),
    ('-', '\u266D'),
    ('`', chr(0x1d132)),  # 1D132; raised flat: 1D12C
    ('', '\u266e'),  # natural
])

alternateNameToAccidentalName = {
    'n': 'natural',
    'is': 'sharp',
    'isis': 'double-sharp',
    'isisis': 'triple-sharp',
    'isisisis': 'quadruple-sharp',
    'es': 'flat',
    'b': 'flat',
    'eses': 'double-flat',
    'eseses': 'triple-flat',
    'eseseses': 'quadruple-flat',
    'quarter-sharp': 'half-sharp',
    'ih': 'half-sharp',
    'semisharp': 'half-sharp',
    'three-quarter-sharp': 'one-and-a-half-sharp',
    'three-quarters-sharp': 'one-and-a-half-sharp',
    'isih': 'one-and-a-half-sharp',
    'sesquisharp': 'one-and-a-half-sharp',
    'quarter-flat': 'half-flat',
    'eh': 'half-flat',
    'semiflat': 'half-flat',
    'three-quarter-flat': 'one-and-a-half-flat',
    'three-quarters-flat': 'one-and-a-half-flat',
    'eseh': 'one-and-a-half-flat',
    'sesquiflat': 'one-and-a-half-flat',
}


[docs] def isValidAccidentalName(name: str) -> bool: ''' Check if name is a valid accidental name string that can be used to initialize an Accidental. Standard accidental names are valid: >>> pitch.isValidAccidentalName('double-flat') True Accidental modifiers are valid: >>> pitch.isValidAccidentalName('--') True Alternate accidental names are valid: >>> pitch.isValidAccidentalName('eses') True Anything else is not valid: >>> pitch.isValidAccidentalName('two flats') False ''' # check against official names if name in accidentalNameToModifier: return True # check against official modifiers if name in accidentalNameToModifier.values(): return True # check against alternate supported names if name in alternateNameToAccidentalName: return True return False
[docs] def standardizeAccidentalName(name: str) -> str: ''' Convert a valid accidental name to the standard accidental name. Raises AccidentalException if name is not a valid accidental name. >>> pitch.standardizeAccidentalName('double-flat') 'double-flat' >>> pitch.standardizeAccidentalName('--') 'double-flat' >>> pitch.standardizeAccidentalName('eses') 'double-flat' >>> pitch.standardizeAccidentalName('two flats') Traceback (most recent call last): music21.pitch.AccidentalException: 'two flats' is not a supported accidental type ''' if name in accidentalNameToModifier: # it is already standardized, just return it return name if name in modifierToAccidentalName: # it is a modifier, look up standardized name return modifierToAccidentalName[name] if name in alternateNameToAccidentalName: # it is an alternate name, look up standardized name return alternateNameToAccidentalName[name] raise AccidentalException(f"'{name}' is not a supported accidental type")
# ------------------------------------------------------------------------------ # utility functions def _convertPitchClassToNumber( ps: int|float|PitchClassString ) -> int|float: ''' Given a pitch class string return the pitch class representation. Ints and floats are returned unchanged; >>> pitch._convertPitchClassToNumber(3) 3 >>> pitch._convertPitchClassToNumber('a') 10 >>> pitch._convertPitchClassToNumber('B') 11 >>> pitch._convertPitchClassToNumber('3') 3 >>> pitch._convertPitchClassToNumber(3.5) 3.5 ''' if isinstance(ps, (int, float)): return ps else: # assume it is a string if ps in ('a', 'A', 't', 'T'): return 10 if ps in ('b', 'B', 'e', 'E'): return 11 # maybe it is a string of an integer? return int(ps)
[docs] def convertPitchClassToStr(pc: int) -> str: ''' Given a pitch class number, return a string. >>> pitch.convertPitchClassToStr(3) '3' >>> pitch.convertPitchClassToStr(10) 'A' ''' pc = pc % 12 # do just in case # replace 10 with A and 11 with B return f'{pc:X}' # using hex conversion, good up to 15
def _convertPsToOct(ps: int|float) -> int: ''' Utility conversion; does not process internals. Converts a midiNote number to an octave number. Assume C4 middle C, so 60 returns 4 >>> [pitch._convertPsToOct(59), pitch._convertPsToOct(60), pitch._convertPsToOct(61)] [3, 4, 4] >>> [pitch._convertPsToOct(12), pitch._convertPsToOct(0), pitch._convertPsToOct(-12)] [0, -1, -2] >>> pitch._convertPsToOct(135) 10 Note that while this is basically a floor operation, we only treat 6 digits as significant (PITCH_SPACE_SIGNIFICANT_DIGITS) >>> pitch._convertPsToOct(71.999) 4 >>> pitch._convertPsToOct(71.99999999) 5 >>> pitch._convertPsToOct(72) 5 ''' # environLocal.printDebug(['_convertPsToOct: input', ps]) ps = round(ps, PITCH_SPACE_SIG_DIGITS) return int(math.floor(ps / 12)) - 1 def _convertPsToStep( ps: int|float ) -> tuple[StepName, Accidental, Microtone, int]: ''' Utility conversion; does not process internal representations. Takes in a pitch space floating-point value or a MIDI note number (Assume C4 middle C, so 60 returns 4). Returns a tuple of Step, an Accidental object, a Microtone object or None, and an int representing octave shift (which is nearly always zero, but can be 1 for a B with very high microtones). >>> pitch._convertPsToStep(60) ('C', <music21.pitch.Accidental natural>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(66) ('F', <music21.pitch.Accidental sharp>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(67) ('G', <music21.pitch.Accidental natural>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(68) ('G', <music21.pitch.Accidental sharp>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(-2) ('B', <music21.pitch.Accidental flat>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(60.5) ('C', <music21.pitch.Accidental half-sharp>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(62) ('D', <music21.pitch.Accidental natural>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(62.5) ('D', <music21.pitch.Accidental half-sharp>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(135) ('E', <music21.pitch.Accidental flat>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(70) ('B', <music21.pitch.Accidental flat>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(70.2) ('B', <music21.pitch.Accidental flat>, <music21.pitch.Microtone (+20c)>, 0) >>> pitch._convertPsToStep(70.5) ('B', <music21.pitch.Accidental half-flat>, <music21.pitch.Microtone (+0c)>, 0) Here is a case where perhaps D half-flat would be better: >>> pitch._convertPsToStep(61.5) ('C', <music21.pitch.Accidental one-and-a-half-sharp>, <music21.pitch.Microtone (+0c)>, 0) Note that Microtones can have negative and positive zeros. >>> pitch._convertPsToStep(43.00001) ('G', <music21.pitch.Accidental natural>, <music21.pitch.Microtone (+0c)>, 0) >>> pitch._convertPsToStep(42.999739) ('G', <music21.pitch.Accidental natural>, <music21.pitch.Microtone (-0c)>, 0) A case where octave shift is not zero. Here the ps of 59.8 would normally imply octave 3, but because it is being represented by a C, it must appear in the octave above what is implied by the ps. >>> pitch._convertPsToStep(59.8) ('C', <music21.pitch.Accidental natural>, <music21.pitch.Microtone (-20c)>, 1) A number very close to 60, however, is rounded, and gets no octave shift. >>> pitch._convertPsToStep(59.9999999) ('C', <music21.pitch.Accidental natural>, <music21.pitch.Microtone (+0c)>, 0) ''' if isinstance(ps, int): pc = ps % 12 alter = 0.0 micro = 0.0 elif ps == int(ps): pc = int(ps) % 12 alter = 0.0 micro = 0.0 else: # rounding here is essential ps = round(ps, PITCH_SPACE_SIG_DIGITS) # micro here will be between 0 and 1 pcReal = ps % 12 pc_float, micro = divmod(pcReal, 1) pc = int(pc_float) # if close enough to a quarter tone if round(micro, 1) == 0.5: # if we can round to 0.5, then this is a quarter-tone accidental alter = 0.5 # need to find microtonal alteration around this value # if alter is 0.5 and micro is 0.7 then micro should be 0.2 # if alter is 0.5 and micro is 0.4 then micro should be -0.1 micro = micro - alter # if greater than 0.5 elif 0.25 < micro < 0.75: alter = 0.5 micro = micro - alter # if closer to 1, then go to the higher alter and get negative micro elif 0.75 <= micro < 1: alter = 1.0 micro = micro - alter # not greater than 0.25 elif micro > 0: alter = 0.0 # micro = micro # no change necessary else: alter = 0.0 micro = 0.0 # environLocal.printDebug(['_convertPsToStep(): post', 'alter', alter, # 'micro', micro, 'pc', pc]) octShift = 0 # check for unnecessary enharmonics if alter == 1 and pc in (4, 11): acc = Accidental(0) pcName = (pc + 1) % 12 # if a B, we are shifting out of this octave, and need to get # the above octave, which may not be represented in ps value if pc == 11: octShift = 1 # it is a natural; nothing to do elif pc in NATURAL_PCS: # 0, 2, 4, 5, 7, 9, 11 acc = Accidental(0 + alter) # alter is usually 0 unless half-sharp. pcName = pc elif (pc - 1) in (0, 5, 7) and alter >= 1: # is this going to be a C##, F##, G##? acc = Accidental(alter - 1) pcName = pc + 1 # if we take the pc down a half-step, do we get a stepref (natural) value elif (pc - 1) in (0, 5, 7): # c, f, g: can be sharped # then we need an accidental to accommodate; here, a sharp acc = Accidental(1 + alter) pcName = pc - 1 elif (pc + 1) in (11, 4) and alter <= -1: # is this going to be an E-- or B--? acc = Accidental(1 + alter) pcName = pc - 1 # if we take the pc up a half-step, do we get a stepref (natural) value elif (pc + 1) in (11, 4): # b, e: can be flattened # then we need an accidental to accommodate; here, a flat acc = Accidental(-1 + alter) pcName = pc + 1 else: # pragma: no cover raise PitchException(f'cannot match condition for pc: {pc}') name = STEPREF_REVERSED[pcName] # create a micro object always if micro != 0: # provide cents value; these are alter values microObj = Microtone(micro * 100) else: microObj = Microtone(0) return name, acc, microObj, octShift def _convertCentsToAlterAndCents(shift: int|float) -> tuple[float, float]: ''' Given any floating point value, split into accidental and microtone components. >>> pitch._convertCentsToAlterAndCents(125) (1.0, 25.0) >>> pitch._convertCentsToAlterAndCents(-75) (-0.5, -25.0) >>> pitch._convertCentsToAlterAndCents(-125) (-1.0, -25.0) >>> pitch._convertCentsToAlterAndCents(-200) (-2.0, 0.0) >>> pitch._convertCentsToAlterAndCents(235) (2.5, -15.0) ''' value = float(shift) alterAdd = 0.0 if value > 150: increment, value = divmod(value, 100) alterAdd += increment elif value < -150: while value < 100: value += 100 alterAdd -= 1.0 if value < -75: alterShift = -1.0 cents = value + 100.0 elif -75 <= value < -25: alterShift = -0.5 cents = value + 50.0 elif -25 <= value <= 25: alterShift = 0.0 cents = value elif 25 < value <= 75: alterShift = 0.5 cents = value - 50 elif value > 75: alterShift = 1.0 cents = value - 100 else: # pragma: no cover raise ValueError(f'value exceeded range: {value}') return alterShift + alterAdd, float(cents) def _convertHarmonicToCents(value: int|float) -> int: r''' Given a harmonic number, return the total number shift in cents assuming 12 tone equal temperament. >>> pitch._convertHarmonicToCents(8) 3600 >>> [pitch._convertHarmonicToCents(x) for x in [5, 6, 7, 8]] [2786, 3102, 3369, 3600] >>> [pitch._convertHarmonicToCents(x) for x in [16, 17, 18, 19]] [4800, 4905, 5004, 5098] >>> [pitch._convertHarmonicToCents(x) for x in range(1, 33)] [0, 1200, 1902, 2400, 2786, 3102, 3369, 3600, 3804, 3986, 4151, 4302, 4441, 4569, 4688, 4800, 4905, 5004, 5098, 5186, 5271, 5351, 5428, 5502, 5573, 5641, 5706, 5769, 5830, 5888, 5945, 6000] Works with subHarmonics as well by specifying them as either 1/x or negative numbers. note that -2 is the first subharmonic, since -1 == 1: >>> [pitch._convertHarmonicToCents(1 / x) for x in [1, 2, 3, 4]] [0, -1200, -1902, -2400] >>> [pitch._convertHarmonicToCents(x) for x in [-1, -2, -3, -4]] [0, -1200, -1902, -2400] So the fifth subharmonic of the 7th harmonic, which is C2->C3->G3->C4->E4->G4->B\`4 --> B\`3->E\`3->B\`2->G-\`2 >>> pitch._convertHarmonicToCents(7 / 5) 583 >>> pitch._convertHarmonicToCents(7.0) - pitch._convertHarmonicToCents(5.0) 583 ''' if value < 0: # subharmonics value = 1 / (abs(value)) return round(1200 * math.log2(value)) # ----------------------------------------------------------------------------- def _dissonanceScore(pitches: list[Pitch], smallPythagoreanRatio: bool = True, accidentalPenalty: bool = True, triadAward: bool = True) -> float: r''' Calculate the 'dissonance' of a list of pitches based on three criteria. A chord is considered more consonant if (1) the numerators and denominators of the Pythagorean ratios of its constituent intervals are small (`smallPythagoreanRatio`), (2) it shows few double- or triple-accidentals (`accidentalPenalty`), and (3) it shows thirds that can form some triad (`triadAward`). ''' score_accidentals = 0.0 score_ratio = 0.0 score_triad = 0.0 intervals: list[interval.Interval] = [] if not pitches: return 0.0 if accidentalPenalty: # score_accidentals = accidentals per pitch accidentals = [abs(p.alter) for p in pitches] score_accidentals = sum(a if a > 1 else 0 for a in accidentals) / len(pitches) if smallPythagoreanRatio or triadAward: try: intervals = [] for p1, p2 in itertools.combinations(pitches, 2): p2 = copy.deepcopy(p2) p2.octave = None this_interval = interval.Interval(noteStart=p1, noteEnd=p2) intervals.append(this_interval) except interval.IntervalException: return math.inf if smallPythagoreanRatio: # score_ratio = Pythagorean ratio complexity per pitch for this_interval in intervals: # does not accept weird intervals, e.g. with semitones ratio = interval.intervalToPythagoreanRatio(this_interval) penalty = math.log(ratio.denominator) * 0.07585326888 score_ratio += penalty score_ratio /= len(pitches) if triadAward: # score_triad = number of thirds per pitch (avoid double-base-thirds) for this_interval in intervals: simple_directed = this_interval.generic.simpleDirected interval_semitones = this_interval.chromatic.semitones % 12 if simple_directed == 3 and interval_semitones in (3, 4): score_triad -= 1.0 elif simple_directed == 6 and interval_semitones in (8, 9): score_triad -= 1.0 score_triad /= len(pitches) return (score_accidentals + score_ratio + score_triad) / int(smallPythagoreanRatio + accidentalPenalty + triadAward) def _bruteForceEnharmonicsSearch( oldPitches: list[Pitch], scoreFunc: t.Callable[[list[Pitch]], float] = _dissonanceScore ) -> list[Pitch]: ''' Simplify a list of pitches by brute force -- useful if there are fewer than five pitches. ''' all_possible_pitches = [[p] + p.getAllCommonEnharmonics() for p in oldPitches[1:]] all_pitch_combinations = itertools.product(*all_possible_pitches) newPitches = min(all_pitch_combinations, key=lambda x: scoreFunc(oldPitches[:1] + list(x))) return oldPitches[:1] + list(newPitches) def _greedyEnharmonicsSearch( oldPitches: list[Pitch], scoreFunc: t.Callable[[list[Pitch]], float] = _dissonanceScore ) -> list[Pitch]: newPitches = oldPitches[:1] for oldPitch in oldPitches[1:]: candidates = [oldPitch] + oldPitch.getAllCommonEnharmonics() newPitch = min(candidates, key=lambda x: scoreFunc(newPitches + [x])) newPitches.append(newPitch) return newPitches
[docs] def simplifyMultipleEnharmonics( pitches: t.Iterable[Pitch|str|int|float], criterion: t.Callable[[list[Pitch]], float] = _dissonanceScore, keyContext: key.KeySignature|None = None ) -> list[Pitch]: r''' Try to simplify the enharmonic spelling of a list of pitch strings, `Pitch` objects, or pitch/pitch-class numbers according to a given criterion. A function can be passed as the `criterion` argument; it is minimized in a greedy, left-to-right fashion. >>> pitch.simplifyMultipleEnharmonics([11, 3, 6]) [<music21.pitch.Pitch B>, <music21.pitch.Pitch D#>, <music21.pitch.Pitch F#>] >>> pitch.simplifyMultipleEnharmonics([3, 8, 0]) [<music21.pitch.Pitch E->, <music21.pitch.Pitch A->, <music21.pitch.Pitch C>] >>> pitch.simplifyMultipleEnharmonics([pitch.Pitch('G3'), ... pitch.Pitch('C-4'), ... pitch.Pitch('D4')]) [<music21.pitch.Pitch G3>, <music21.pitch.Pitch B3>, <music21.pitch.Pitch D4>] >>> pitch.simplifyMultipleEnharmonics([pitch.Pitch('A3'), ... pitch.Pitch('B#3'), ... pitch.Pitch('E4')]) [<music21.pitch.Pitch A3>, <music21.pitch.Pitch C4>, <music21.pitch.Pitch E4>] The attribute `keyContext` is for supplying a KeySignature or a Key which is used in the simplification: >>> pitch.simplifyMultipleEnharmonics([6, 10, 1], keyContext=key.Key('B')) [<music21.pitch.Pitch F#>, <music21.pitch.Pitch A#>, <music21.pitch.Pitch C#>] >>> pitch.simplifyMultipleEnharmonics([6, 10, 1], keyContext=key.Key('C-')) [<music21.pitch.Pitch G->, <music21.pitch.Pitch B->, <music21.pitch.Pitch D->] Note that if there's no key context, then we won't simplify everything (at least for now; this behavior may change). >>> pitch.simplifyMultipleEnharmonics([pitch.Pitch('D--3'), ... pitch.Pitch('F-3'), ... pitch.Pitch('A--3')]) [<music21.pitch.Pitch D--3>, <music21.pitch.Pitch F-3>, <music21.pitch.Pitch A--3>] >>> pitch.simplifyMultipleEnharmonics([pitch.Pitch('D--3'), ... pitch.Pitch('F-3'), ... pitch.Pitch('A--3')], ... keyContext=key.Key('C')) [<music21.pitch.Pitch C3>, <music21.pitch.Pitch E3>, <music21.pitch.Pitch G3>] * Changed in v7.3: fixed a bug with compound intervals (such as formed against the tonic of a KeySignature defaulting to octave 4): >>> pitch.simplifyMultipleEnharmonics([pitch.Pitch('B5')], keyContext=key.Key('D')) [<music21.pitch.Pitch B5>] ''' oldPitches = [p if isinstance(p, Pitch) else Pitch(p) for p in pitches] if keyContext: oldPitches = [keyContext.asKey('major').tonic] + oldPitches remove_first = True else: remove_first = False if len(oldPitches) < 5: simplifiedPitches = _bruteForceEnharmonicsSearch(oldPitches, criterion) else: simplifiedPitches = _greedyEnharmonicsSearch(oldPitches, criterion) # Preserve value of spellingIsInferred for oldP, newP in zip(oldPitches, simplifiedPitches): newP.spellingIsInferred = oldP.spellingIsInferred if remove_first: simplifiedPitches = simplifiedPitches[1:] return simplifiedPitches
# ----------------------------------------------------------------------------- class AccidentalException(exceptions21.Music21Exception): pass class PitchException(exceptions21.Music21Exception): pass class MicrotoneException(exceptions21.Music21Exception): pass # -----------------------------------------------------------------------------
[docs] class Microtone(prebase.ProtoM21Object, SlottedObjectMixin): ''' The Microtone object defines a pitch transformation above or below a standard Pitch and its Accidental. >>> m = pitch.Microtone(20) >>> m.cents 20 >>> m.alter 0.2... >>> print(m) (+20c) >>> m <music21.pitch.Microtone (+20c)> Microtones can be shifted according to the harmonic. Here we take the 3rd harmonic of the previous microtone >>> m.harmonicShift = 3 >>> m.harmonicShift 3 >>> m <music21.pitch.Microtone (+20c+3rdH)> >>> m.cents 1922 >>> m.alter 19.2... Microtonal changes can be positive or negative. Both Positive and negative numbers can optionally be placed in parentheses Negative numbers in parentheses still need the minus sign. >>> m = pitch.Microtone('(-33.333333)') >>> m <music21.pitch.Microtone (-33c)> >>> m = pitch.Microtone('33.333333') >>> m <music21.pitch.Microtone (+33c)> Note that we round the display of microtones to the nearest cent, but we keep the exact alteration in both .cents and .alter: >>> m.cents 33.333... >>> m.alter 0.3333... ''' # CLASS VARIABLES # __slots__ = ( '_centShift', '_harmonicShift', ) # INITIALIZER # def __init__(self, centsOrString: str|int|float = 0, harmonicShift: int = 1) -> None: self._centShift: int|float = 0 self._harmonicShift: int = harmonicShift # the first harmonic is the start if isinstance(centsOrString, (int, float)): self._centShift = centsOrString # specify harmonic in cents else: self._parseString(centsOrString) # need to additional store a reference to a position in # another pitch's overtone series? # such as: A4(+69c [7thH/C3])? # SPECIAL METHODS # def __deepcopy__(self, memo: dict[int, t.Any]) -> Microtone: if type(self) is Microtone: # pylint: disable=unidiomatic-typecheck return Microtone(self._centShift, self._harmonicShift) else: # pragma: no cover return common.defaultDeepcopy(self, memo)
[docs] def __eq__(self, other: object) -> bool: ''' Compare cents (including harmonicShift). >>> m1 = pitch.Microtone(20) >>> m2 = pitch.Microtone(20) >>> m3 = pitch.Microtone(21) >>> m1 == m2 True >>> m1 == m3 False >>> m2.harmonicShift = 3 >>> m1 == m2 False Cannot compare True to a non-Microtone: >>> m1 == pitch.Accidental(1) False ''' otherCents = getattr(other, 'cents', None) if otherCents is None: return False return otherCents == self.cents
def __hash__(self) -> int: hashValues = ( self._centShift, self._harmonicShift, type(self), ) return hash(hashValues) def _reprInternal(self) -> str: return str(self) def __str__(self) -> str: ''' Return a string representation. >>> m1 = pitch.Microtone(20) >>> print(m1) (+20c) Differs from the representation: >>> m1 <music21.pitch.Microtone (+20c)> ''' # cent values may be of any resolution, but round to the nearest int sub = '' roundShift = round(self._centShift) if self._centShift >= 0: sub = f'+{roundShift}c' elif self._centShift < 0: sub = f'{roundShift}c' # minus sign is included already if sub == '0c': sub = '-0c' # only show a harmonic if present if self._harmonicShift != 1: ordAbbreviation = common.ordinalAbbreviation(self._harmonicShift) sub += f'+{self._harmonicShift}{ordAbbreviation}H' return f'{MICROTONE_OPEN}{sub}{MICROTONE_CLOSE}' # PRIVATE METHODS # def _parseString(self, value: str) -> None: ''' Parse a string representation. ''' # strip any delimiters value = value.replace(MICROTONE_OPEN, '') value = value.replace(MICROTONE_CLOSE, '') centValue: float = 0 # need to look for and split off harmonic definitions if value[0] == '+' or value[0].isdigit(): # positive cent representation num, unused_nonNum = common.getNumFromStr(value, numbers='0123456789.') if num == '': raise MicrotoneException(f'no numbers found in string value: {value}') centValue = float(num) elif value[0] == '-': num, unused_nonNum = common.getNumFromStr(value[1:], numbers='0123456789.') centValue = float(num) * -1 self._centShift = centValue # PUBLIC PROPERTIES # @property def alter(self) -> float: ''' Return the microtone value in accidental alter values. >>> pitch.Microtone(20).alter 0.2 ''' return self.cents * 0.01 @property def cents(self) -> int|float: ''' Return the microtone value in cents. This is not a settable property. To set the value in cents, simply use that value as a creation argument. >>> pitch.Microtone(20).cents 20 ''' return _convertHarmonicToCents(self._harmonicShift) + self._centShift @property def harmonicShift(self) -> int: ''' Set or get the harmonic shift, altering the microtone. ''' return self._harmonicShift @harmonicShift.setter def harmonicShift(self, value: int) -> None: self._harmonicShift = value
[docs] class Accidental(prebase.ProtoM21Object, style.StyleMixin): ''' Accidental class, representing the symbolic and numerical representation of pitch deviation from a pitch name (e.g., G, B). Two accidentals are considered equal if their names are equal. Accidentals have three defining attributes: a name, a modifier, and an alter. For microtonal specifications, the name and modifier are the same except in the case of half-sharp, half-flat, one-and-a-half-flat, and one-and-a-half-sharp. Accidentals up to quadruple-sharp and quadruple-flat are allowed. Natural-sharp etc. (for canceling a previous flat) are not yet supported officially. >>> a = pitch.Accidental('sharp') >>> a.name, a.alter, a.modifier ('sharp', 1.0, '#') >>> a.style.color = 'red' >>> import copy >>> b = copy.deepcopy(a) >>> b.style.color 'red' ''' _styleClass = style.TextStyle # CLASS VARIABLES # __slots__ = ( '_alter', '_displayStatus', '_displayType', '_modifier', '_name', '_client', 'displayLocation', 'displaySize', 'displayStyle', ) _DOC_ORDER = ['name', 'modifier', 'alter', 'set'] # documentation for all attributes (not properties or methods) _DOC_ATTR: dict[str, str] = { 'displaySize': 'Size in display: "cue", "large", or a percentage.', 'displayStyle': 'Style of display: "parentheses", "bracket", "both".', 'displayLocation': 'Location of accidental: "normal", "above", "below".', } # INITIALIZER # def __init__(self, specifier: int|str|float = 'natural') -> None: super().__init__() # managed by properties self._displayType = 'normal' # normal, always, never, if-absolutely-necessary, # unless-repeated, even-tied self._displayStatus: bool|None = None # None, True, False # not yet managed by properties: TODO self.displayStyle = 'normal' # 'parentheses', 'bracket', 'both' self.displaySize = 'full' # 'cue', 'large', or a percentage # 'normal', 'above' = ficta, 'below' # above and below could also be useful for gruppetti, etc. self.displayLocation = 'normal' # store a reference to the Pitch that has this Accidental object as a property self._client: Pitch|None = None self._name = '' self._modifier = '' self._alter = 0.0 # semitones to alter step # potentially can be a fraction, but not exponent self.set(specifier) # SPECIAL METHODS # def _hashValues(self) -> tuple[t.Any, ...]: return ( self._alter, self._displayStatus, self._displayType, self._modifier, self._name, self.displayLocation, self.displaySize, self.displayStyle, ) def __hash__(self) -> int: return hash(self._hashValues()) def __deepcopy__(self, memo: dict[int, t.Any]) -> Accidental: if type(self) is Accidental: # pylint: disable=unidiomatic-typecheck new = Accidental.__new__(Accidental) for s in self._getSlotsRecursive(): setattr(new, s, getattr(self, s)) return new else: # pragma: no cover return common.defaultDeepcopy(self, memo)
[docs] def __eq__(self, other: object) -> bool: ''' Equality. Needed for pitch comparisons. >>> a = pitch.Accidental('double-flat') >>> b = pitch.Accidental('double-flat') >>> c = pitch.Accidental('double-sharp') >>> a == b True >>> a == c False ''' if not isinstance(other, Accidental): return False if self.name == other.name: return True else: return False
def __ge__(self, other: object) -> bool: ''' Greater than or equal. Based on the accidentals' alter function. >>> a = pitch.Accidental('sharp') >>> b = pitch.Accidental('flat') >>> a >= b True ''' return self.__gt__(other) or self.__eq__(other) def __gt__(self, other: object) -> bool: ''' Greater than. Based on the accidentals' alter function. >>> a = pitch.Accidental('sharp') >>> b = pitch.Accidental('flat') >>> a < b False >>> a > b True >>> a > 5 False ''' if other is None or not isinstance(other, Accidental): return False if self.alter > other.alter: return True else: return False def __le__(self, other: object) -> bool: ''' Less than or equal. Based on the accidentals' alter function. >>> a = pitch.Accidental('sharp') >>> b = pitch.Accidental('flat') >>> c = pitch.Accidental('sharp') >>> a <= b False >>> a <= c True ''' return self.__lt__(other) or self.__eq__(other) def __lt__(self, other: object) -> bool: ''' Less than. Based on alter. >>> a = pitch.Accidental('natural') >>> b = pitch.Accidental('flat') >>> a > b True >>> a < b False >>> a < 5 False ''' if other is None or not isinstance(other, Accidental): return False if self.alter < other.alter: return True else: return False def _reprInternal(self) -> str: return self.name def __str__(self) -> str: return self.name
[docs] @classmethod def listNames(cls) -> list[str]: ''' Returns a list of accidental names that have any sort of semantic importance in music21. You may choose a name not from this list (1/7th-sharp) but if it's not on this list don't expect it to do anything for you. This is a class method, so you may call it directly on the class: Listed in alphabetical order. (TODO: maybe from lowest to highest or something implying importance?) >>> pitch.Accidental.listNames() ['double-flat', 'double-sharp', 'flat', 'half-flat', 'half-sharp', 'natural', 'one-and-a-half-flat', 'one-and-a-half-sharp', 'quadruple-flat', 'quadruple-sharp', 'sharp', 'triple-flat', 'triple-sharp'] Or call on an instance of an accidental: >>> f = pitch.Accidental('flat') >>> f.listNames() ['double-flat', 'double-sharp', 'flat', 'half-flat', 'half-sharp', 'natural', 'one-and-a-half-flat', 'one-and-a-half-sharp', 'quadruple-flat', 'quadruple-sharp', 'sharp', 'triple-flat', 'triple-sharp'] ''' return sorted(a.lower() for a in accidentalNameToModifier)
# PUBLIC METHODS #
[docs] def set(self, name: str|int|float, *, allowNonStandardValue: bool = False) -> None: ''' Change the type of the Accidental. Strings, numbers, and Lilypond (German-like) abbreviations are all accepted. All other values will change after setting. >>> a = pitch.Accidental() >>> a.set('sharp') >>> a.alter 1.0 >>> a = pitch.Accidental() >>> a.set(2) >>> a.modifier == '##' True >>> a = pitch.Accidental() >>> a.set(2.0) >>> a.modifier == '##' True >>> a = pitch.Accidental('--') >>> a.alter -2.0 >>> a.set('half-sharp') >>> a.alter 0.5 >>> a.name 'half-sharp' Setting an illegal name is generally an error: >>> a.set('flat-flat-up') Traceback (most recent call last): music21.pitch.AccidentalException: flat-flat-up is not a supported accidental type But if 'allowNonStandardValue' is True then other names (if strings) or alters (if numbers) are allowed: >>> a.set('quintuple-sharp', allowNonStandardValue=True) >>> a.set(5.0, allowNonStandardValue=True) >>> a.name 'quintuple-sharp' >>> a.alter 5.0 This is the argument that .name and .alter use to allow non-standard names * Changed in v5: added allowNonStandardValue. ''' if isinstance(name, str): name = name.lower() # sometimes args get capitalized if name in ('natural', 'n', 0): self._name = 'natural' self._alter = 0.0 elif name in ('sharp', accidentalNameToModifier['sharp'], 'is', 1): self._name = 'sharp' self._alter = 1.0 elif name in ('double-sharp', accidentalNameToModifier['double-sharp'], 'isis', 2): self._name = 'double-sharp' self._alter = 2.0 elif name in ('flat', accidentalNameToModifier['flat'], 'es', 'b', -1): self._name = 'flat' self._alter = -1.0 elif name in ('double-flat', accidentalNameToModifier['double-flat'], 'eses', -2): self._name = 'double-flat' self._alter = -2.0 elif name in ('half-sharp', accidentalNameToModifier['half-sharp'], 'quarter-sharp', 'ih', 'semisharp', 0.5): self._name = 'half-sharp' self._alter = 0.5 elif name in ('one-and-a-half-sharp', accidentalNameToModifier['one-and-a-half-sharp'], 'three-quarter-sharp', 'three-quarters-sharp', 'isih', 'sesquisharp', 1.5): self._name = 'one-and-a-half-sharp' self._alter = 1.5 elif name in ('half-flat', accidentalNameToModifier['half-flat'], 'quarter-flat', 'eh', 'semiflat', -0.5): self._name = 'half-flat' self._alter = -0.5 elif name in ('one-and-a-half-flat', accidentalNameToModifier['one-and-a-half-flat'], 'three-quarter-flat', 'three-quarters-flat', 'eseh', 'sesquiflat', -1.5): self._name = 'one-and-a-half-flat' self._alter = -1.5 elif name in ('triple-sharp', accidentalNameToModifier['triple-sharp'], 'isisis', 3): self._name = 'triple-sharp' self._alter = 3.0 elif name in ('quadruple-sharp', accidentalNameToModifier['quadruple-sharp'], 'isisisis', 4): self._name = 'quadruple-sharp' self._alter = 4.0 elif name in ('triple-flat', accidentalNameToModifier['triple-flat'], 'eseses', -3): self._name = 'triple-flat' self._alter = -3.0 elif name in ('quadruple-flat', accidentalNameToModifier['quadruple-flat'], 'eseseses', -4): self._name = 'quadruple-flat' self._alter = -4.0 else: if not allowNonStandardValue: raise AccidentalException(f'{name} is not a supported accidental type') if isinstance(name, str): self._name = name return elif isinstance(name, (int, float)): self._alter = name return else: # pragma: no cover raise AccidentalException(f'{name} is not a supported accidental type') self._modifier = accidentalNameToModifier[self._name] if self._client: self._client.informClient()
[docs] def isTwelveTone(self) -> bool: ''' Return True if this Accidental describes a twelve-tone, non-microtonal pitch. >>> a = pitch.Accidental('half-flat') >>> a.isTwelveTone() False >>> a = pitch.Accidental('###') >>> a.isTwelveTone() True ''' if self.name in ('half-sharp', 'one-and-a-half-sharp', 'half-flat', 'one-and-a-half-flat'): return False return True
# --------------------------------------------------------------------------
[docs] def setAttributeIndependently(self, attribute: str, value: str|int|float) -> None: ''' Set one of the attributes 'name', 'alter', or 'modifier', independently of the other attributes. >>> a = pitch.Accidental('natural') >>> a.setAttributeIndependently('alter', 1.0) >>> a.alter 1.0 >>> a.name 'natural' >>> a.setAttributeIndependently('name', 'sori') >>> a.setAttributeIndependently('modifier', '$') >>> a.modifier '$' >>> a.name 'sori' >>> a.alter 1.0 Only 'name', 'alter', and 'modifier' can be set independently: >>> a.setAttributeIndependently('color', 'red') Traceback (most recent call last): music21.pitch.AccidentalException: Cannot set attribute color independently of other parts. * New in v5: needed because .name, .alter, and .modifier run .set(). ''' if attribute not in ('name', 'alter', 'modifier'): raise AccidentalException( f'Cannot set attribute {attribute} independently of other parts.') if attribute == 'alter': value = float(value) privateAttrName = '_' + attribute setattr(self, privateAttrName, value) if self._client: self._client.informClient()
# --------------------------------------------------------------------------
[docs] def inheritDisplay(self, other: Accidental|None) -> None: ''' Given another Accidental object, inherit all the display properties of that object. This is needed when transposing Pitches: we need to retain accidental display properties. >>> a = pitch.Accidental('double-flat') >>> a.displayType = 'always' >>> b = pitch.Accidental('sharp') >>> b.inheritDisplay(a) >>> b.displayType 'always' ''' if other is not None: # empty accidental attributes are None for attr in ('displayType', 'displayStatus', 'displayStyle', 'displaySize', 'displayLocation'): value = getattr(other, attr) setattr(self, attr, value)
# ------------------------------------------------------------------------- # PUBLIC PROPERTIES # @property def name(self) -> str: ''' Get or set the name of the Accidental, like 'sharp' or 'double-flat' If the name is set to a standard name then it changes alter and modifier. If set to a non-standard name, then it does not change them: >>> a = pitch.Accidental() >>> a.name = 'flat' >>> a.alter -1.0 >>> a.modifier '-' >>> a.name = 'flat-flat-up' >>> a.alter -1.0 * Changed in v5: changing the name here changes other values, conditionally. ''' return self._name @name.setter def name(self, value: str) -> None: self.set(value, allowNonStandardValue=True) @property def alter(self) -> float: ''' Get or set the alter of the Accidental, or the semitone shift caused by the Accidental where 1.0 is a shift up of one semitone, and -1.0 is the shift down of one semitone. >>> sharp = pitch.Accidental('sharp') >>> sharp.alter 1.0 >>> sharp.alter = -1 After changing alter to a known other value, name changes: >>> sharp.name 'flat' But changing it to an unusual value does not change the name: >>> notSoFlat = pitch.Accidental('flat') >>> notSoFlat.alter = -0.9 >>> notSoFlat.name 'flat' * Changed in v5: changing the alter here changes other values, conditionally. ''' return self._alter @alter.setter def alter(self, value: int|float) -> None: # can alternatively call set() self.set(value, allowNonStandardValue=True) @property def modifier(self) -> str: ''' Get or set the alter of the modifier, or the string symbol used to modify the pitch name, such as "#" or "-" for sharp and flat, respectively. For a representation likely to be read by non-music21 users, see `.unicode`. >>> f = pitch.Accidental('flat') >>> f.modifier '-' >>> f.modifier = '#' >>> f.name 'sharp' However, an unknown modifier does not change anything but is preserved: >>> f.modifier = '&' >>> f.modifier '&' >>> f.name 'sharp' * Changed in v5: changing the modifier here changes other values, conditionally. ''' return self._modifier @modifier.setter def modifier(self, value: str) -> None: try: self.set(value, allowNonStandardValue=False) except AccidentalException: self._modifier = value @property def displayType(self) -> str: ''' Returns or sets the display type of the accidental `"normal"` (default) displays it if it is the first in measure, or is needed to contradict a previous accidental, etc. other valid terms: * "always" * "never" * "unless-repeated" (show always unless the immediately preceding note is the same) * "even-tied" (stronger than always: shows even if it is tied to the previous note) * "if-absolutely-necessary" (display only if it is absolutely necessary, like an F-natural after an F-sharp in the same measure, but not an F-natural following an F-sharp directly across a barline. Nor an F-natural in a different octave immediately following an F-sharp). This is not yet implemented. >>> a = pitch.Accidental('flat') >>> a.displayType = 'unless-repeated' >>> a.displayType 'unless-repeated' >>> a.displayType = 'underwater' Traceback (most recent call last): music21.pitch.AccidentalException: Supplied display type is not supported: 'underwater' ''' return self._displayType @displayType.setter def displayType(self, value: str) -> None: if value not in ('normal', 'always', 'never', 'unless-repeated', 'even-tied', 'if-absolutely-necessary'): raise AccidentalException(f'Supplied display type is not supported: {value!r}') self._displayType = value @property def displayStatus(self) -> bool|None: ''' Determines if this Accidental is to be displayed; can be None (for not set), True, or False. In general do not set this, set .displayType instead. Music21 will change displayStatus at any time without warning. While `.displayType` gives general rules about when this accidental should be displayed or not, `displayStatus` determines whether after applying those rules this accidental will be displayed. In general, a `displayStatus` of `None` means that no high-level processing of accidentals has happened. Can be set to True or False (or None) directly for contexts where the next program down the line cannot evaluate displayType. See stream.makeAccidentals() for more information. Example: >>> n0 = note.Note('C#4') >>> n1 = note.Note('C#4') >>> print(n0.pitch.accidental.displayStatus) None >>> print(n1.pitch.accidental.displayStatus) None >>> s = stream.Stream() >>> s.append([n0, n1]) >>> s.makeAccidentals(inPlace=True) >>> n0.pitch.accidental.displayStatus True >>> n1.pitch.accidental.displayStatus False >>> n1.pitch.accidental.displayStatus = 2 Traceback (most recent call last): music21.pitch.AccidentalException: Supplied display status is not supported: 2 ''' return self._displayStatus @displayStatus.setter def displayStatus(self, value: bool|None) -> None: if value not in (True, False, None): raise AccidentalException(f'Supplied display status is not supported: {value}') self._displayStatus = value @property def unicode(self) -> str: ''' Return a unicode representation of this accidental, or the best unicode representation if that is not possible. >>> flat = pitch.Accidental('flat') >>> flat.unicode '♭' Compare: >>> sharp = pitch.Accidental('sharp') >>> sharp.modifier '#' >>> sharp.unicode '♯' Some accidentals, such as double sharps, produce code points outside the 2-byte set (so called "astral plane" unicode) and thus cannot be used in every circumstance. >>> sharp = pitch.Accidental('quadruple-flat') >>> sharp.unicode '𝄫𝄫' ''' # all unicode musical symbols can be found here: # http://www.fileformat.info/info/unicode/block/musical_symbols/images.htm if self.modifier in unicodeFromModifier: return unicodeFromModifier[self.modifier] else: # get our best representation # pragma: no cover return self.modifier @property def fullName(self) -> str: ''' Return the most complete representation of this Accidental. >>> a = pitch.Accidental('double-flat') >>> a.fullName 'double-flat' Note that non-standard microtone names are converted to standard ones: >>> a = pitch.Accidental('quarter-flat') >>> a.fullName 'half-flat' For now this is the same as `.name`. ''' # keep lower case return self.name
# ------------------------------------------------------------------------------ # tried as SlottedObjectMixin -- made creation time slower! Not worth the restrictions
[docs] class Pitch(prebase.ProtoM21Object): ''' A fundamental object that represents a single pitch. `Pitch` objects are most often created by passing in a note name (C, D, E, F, G, A, B), an optional accidental (one or more "#"s or "-"s, where "-" means flat), and an optional octave number: >>> highEflat = pitch.Pitch('E-6') >>> highEflat.name 'E-' >>> highEflat.step 'E' >>> highEflat.accidental <music21.pitch.Accidental flat> >>> highEflat.octave 6 The `.nameWithOctave` property gives back what we put in, `'E-6'`: >>> highEflat.nameWithOctave 'E-6' `Pitch` objects represent themselves as the class name followed by the `.nameWithOctave`: >>> p1 = pitch.Pitch('a#4') >>> p1 <music21.pitch.Pitch A#4> Printing a `pitch.Pitch` object or converting it to a string gives a more compact form: >>> print(p1) A#4 >>> str(p1) 'A#4' .. warning:: A `Pitch` without an accidental has a `.accidental` of None, not Natural. This can lead to problems if you assume that every `Pitch` or `Note` has a `.accidental` that you can call `.alter` or something like that on: >>> c = pitch.Pitch('C4') >>> c.accidental is None True >>> alters = [] >>> for pName in ['G#5', 'B-5', 'C6']: ... p = pitch.Pitch(pName) ... alters.append(p.accidental.alter) Traceback (most recent call last): AttributeError: 'NoneType' object has no attribute 'alter' >>> alters [1.0, -1.0] If a `Pitch` doesn't have an associated octave, then its `.octave` value is None. This means that it represents any G#, regardless of octave. Transposing this note up an octave doesn't change anything. >>> anyGSharp = pitch.Pitch('G#') >>> anyGSharp.octave is None True >>> print(anyGSharp.transpose('P8')) G# Sometimes we need an octave for a `Pitch` even if it's not specified. For instance, we can't play an octave-less `Pitch` in MIDI or display it on a staff. So there is an `.implicitOctave` tag to deal with these situations; by default it's always 4 (unless defaults.pitchOctave is changed) >>> anyGSharp.implicitOctave 4 If a `Pitch` has its `.octave` explicitly set, then `.implicitOctave` always equals `.octave`. >>> highEflat.implicitOctave 6 If an integer or float >= 12 is passed to the constructor then it is used as the `.ps` attribute, which is for most common piano notes, the same as a MIDI number: >>> pitch.Pitch(65) <music21.pitch.Pitch F4> >>> pitch.Pitch(65.5).accidental <music21.pitch.Accidental half-sharp> A `pitch.Pitch` object can also be created using only a number from 0-11, that number is taken to be a `pitchClass`, where 0 = C, 1 = C#/D-, etc. and no octave is set. >>> p2 = pitch.Pitch(3) >>> p2 <music21.pitch.Pitch E-> >>> p2.octave is None True Since in instantiating pitches from numbers, `pitch.Pitch(3)` could be either a D# or an E-flat, this `Pitch` object has an attribute called `.spellingIsInferred` that is set to `True`. That means that when it is transposed or displayed, other functions or programs should feel free to substitute an enharmonically equivalent pitch in its place: >>> p2.spellingIsInferred True >>> p1.spellingIsInferred False As MIDI numbers < 12 are almost unheard of in actual music, there is unlikely to be confusion between a pitchClass instantiation and a MIDI number instantiation, but if one must be clear, use `midi=` in the constructor: >>> lowE = pitch.Pitch(midi=3) >>> lowE.name, lowE.octave ('E-', -1) Instead of using a single string or integer for creating the object, a succession of named keywords can be used instead: >>> p3 = pitch.Pitch(name='C', accidental='#', octave=7, microtone=-30) >>> p3.fullName 'C-sharp in octave 7 (-30c)' The full list of supported keywords are: `name`, `accidental` (which can be a string or an :class:`~music21.pitch.Accidental` object), `octave`, microtone (which can be a number or a :class:`~music21.pitch.Microtone` object), `pitchClass` (0-11), `fundamental` (another `Pitch` object representing the fundamental for this harmonic; `harmonic` is not yet supported, but should be), and `midi` or `ps` (two ways of specifying nearly the same thing, see below). Using keywords to create `Pitch` objects is especially important if extreme pitches might be found. For instance, the first `Pitch` is B-double flat in octave 3, not B-flat in octave -3. The second object creates that low `Pitch` properly: >>> p4 = pitch.Pitch('B--3') >>> p4.accidental <music21.pitch.Accidental double-flat> >>> p4.octave 3 >>> p5 = pitch.Pitch(step='B', accidental='-', octave=-3) >>> p5.accidental <music21.pitch.Accidental flat> >>> p5.octave -3 Internally, pitches are represented by their scale step (`self.step`), their octave, and their accidental. `Pitch` objects use these three elements to figure out their pitch space representation (`self.ps`); altering any of the first three changes the pitch space (ps) representation. Similarly, altering the .ps representation alters the first three. >>> aSharp = pitch.Pitch('A#5') >>> aSharp.ps 82.0 >>> aSharp.octave = 4 >>> aSharp.ps 70.0 >>> aSharp.ps = 60.0 >>> aSharp.nameWithOctave 'C4' Two Pitches are equal if they represent the same pitch and are spelled the same (enharmonics do not count). A Pitch is greater than another Pitch if its `.ps` is greater than the other. Thus, C##4 > D-4. >>> pitch.Pitch('C#5') == pitch.Pitch('C#5') True >>> pitch.Pitch('C#5') == pitch.Pitch('D-5') False >>> pitch.Pitch('C##5') > pitch.Pitch('D-5') True A consequence of comparing enharmonics for equality but .ps for comparisons is that a `Pitch` can be neither less than nor greater than another `Pitch` without being equal: >>> pitch.Pitch('C#5') == pitch.Pitch('D-5') False >>> pitch.Pitch('C#5') > pitch.Pitch('D-5') False >>> pitch.Pitch('C#5') < pitch.Pitch('D-5') False To check for enharmonic equality, use the .ps attribute: >>> pitch.Pitch('C#5').ps == pitch.Pitch('D-5').ps True Advanced construction of pitch with keywords: >>> pitch.Pitch(name='D', accidental=pitch.Accidental('double-flat')) <music21.pitch.Pitch D--> >>> f = pitch.Pitch(pitchClass=5, octave=4, ... microtone=pitch.Microtone(30), fundamental=pitch.Pitch('B-2')) >>> f <music21.pitch.Pitch F4(+30c)> >>> f.fundamental <music21.pitch.Pitch B-2> If contradictory keyword attributes (like `name='E-', accidental='#'`) are passed in, behavior is not defined, but unlikely to make you happy. Pitches are ProtoM21Objects, so they retain some attributes there such as .classes and .groups, but they don't have Duration or Sites objects and cannot be put into Streams ''' # define order for presenting names in documentation; use strings _DOC_ORDER = ['name', 'nameWithOctave', 'step', 'pitchClass', 'octave', 'midi', 'german', 'french', 'spanish', 'italian', 'dutch'] # documentation for all attributes (not properties or methods) # _DOC_ATTR: dict[str, str] = { # } # constants shared by all classes _twelfth_root_of_two = TWELFTH_ROOT_OF_TWO _DOC_ATTR: dict[str, str] = { 'spellingIsInferred': ''' Returns True or False about whether enharmonic spelling Has been inferred on pitch creation or whether it has been specified directly. MIDI 61 is C# or D- equally. >>> p = pitch.Pitch('C4') >>> p.spellingIsInferred False >>> p.ps = 61 >>> p.spellingIsInferred True >>> p.name 'C#' >>> p.name = 'C#' >>> p.spellingIsInferred False This makes a difference in transposing. For instance: >>> pInferred = pitch.Pitch(61) >>> pNotInferred = pitch.Pitch('C#4') >>> pInferred.nameWithOctave, pNotInferred.nameWithOctave ('C#4', 'C#4') >>> pInferred.spellingIsInferred, pNotInferred.spellingIsInferred (True, False) >>> inferredTransposed = pInferred.transpose('A1') >>> inferredTransposed.nameWithOctave 'D4' >>> notInferredTransposed = pNotInferred.transpose('A1') >>> notInferredTransposed.nameWithOctave 'C##4' An operation like diatonic transposition should retain the spelling is inferred for the resulting object >>> inferredTransposed.spellingIsInferred, notInferredTransposed.spellingIsInferred (True, False) But Chromatic transposition can change an object to inferred spelling: >>> p3 = notInferredTransposed.transpose(1) # C## -> E- not to C### >>> p3.nameWithOctave 'E-4' >>> p3.spellingIsInferred True ''', } def __init__(self, name: str|int|float|None = None, *, step: StepName|None = None, octave: int|None = None, accidental: Accidental|str|int|float|None = None, microtone: Microtone|int|float|None = None, pitchClass: int|PitchClassString|None = None, midi: int|None = None, ps: float|None = None, fundamental: Pitch|None = None, **keywords) -> None: # No need for super().__init__() on protoM21Object self._groups: base.Groups|None = None # this should not be set, as will be updated when needed self._step: StepName = defaults.pitchStep # this is only the pitch step self._overridden_freq440: float|None = None # store an Accidental and Microtone objects # note that creating an Accidental object is much more time-consuming # than a microtone self._accidental: Accidental|None = None # 5% of pitch creation time; it'll be created in a sec anyhow self._microtone: Microtone|None = None # # CA, Q: should this remain an attribute or only refer to value in defaults? # # MSC A: no, it's a useful attribute for cases such as scales where if there are # # no octaves we give a defaultOctave higher than the previous # # MSC 12 years later: maybe Chris was right! # self.defaultOctave: int = defaults.pitchOctave # # MSC: even later: Chris Ariza was right self._octave: int|None = None # if True, accidental is not known; is determined algorithmically # likely due to pitch data from midi or pitch space/class numbers self.spellingIsInferred = False # the fundamental attribute stores an optional pitch # that defines the fundamental used to create this Pitch self.fundamental: Pitch|None = None # so that we can tell clients about changes in pitches. self._client: note.Note|None = None # name combines step, octave, and accidental if name is not None: if isinstance(name, str): self.name = name # set based on string else: # is a number # is a midiNumber or a ps -- a float midiNumber # get step and accidental w/o octave self.step, self._accidental = _convertPsToStep(name)[0:2] self.spellingIsInferred = True if name >= 12: # is not a pitchClass self._octave = int(name / 12) - 1 elif step is not None: self.step = step if octave is not None: self._octave = octave if accidental is not None: if isinstance(accidental, Accidental): self.accidental = accidental else: self.accidental = Accidental(accidental) if microtone is not None: if isinstance(microtone, Microtone): self.microtone = microtone else: self.microtone = Microtone(microtone) if pitchClass is not None: # type ignore until https://github.com/python/mypy/issues/3004 resolved self.pitchClass = pitchClass # type: ignore if fundamental is not None: self.fundamental = fundamental if midi is not None: self.midi = midi if ps is not None: self.ps = ps def _reprInternal(self) -> str: return str(self) def __str__(self) -> str: name = self.nameWithOctave if self._microtone is not None and self._microtone.cents != 0: return name + str(self._microtone) else: return name
[docs] def __eq__(self, other: object) -> bool: ''' Are two pitches equal? They must both sound the same and be spelled the same. Enharmonic equivalence is not equality, nor is impliedOctave or is a pitch without an accidental equal to a pitch with a natural accidental. (See `:meth:`~music21.pitch.Pitch.isEnharmonic` for a method that does not require spelling equivalence, and for an example of how to compare notes without accidentals to notes with natural accidentals.) >>> c = pitch.Pitch('C2') >>> c.octave 2 >>> cs = pitch.Pitch('C#4') >>> cs.octave 4 >>> c == cs False >>> c != cs True >>> seven = 7 >>> c == seven False >>> c != seven True >>> df = pitch.Pitch('D-4') >>> cs == df False Implied octaves do not equal explicit octaves >>> c4 = pitch.Pitch('C4') >>> cImplied = pitch.Pitch('C') >>> c4 == cImplied False >>> c4.ps == cImplied.ps True Note: currently spellingIsInferred and fundamental are not checked -- this behavior may change in the future. ''' if not isinstance(other, Pitch): return NotImplemented if (self.octave == other.octave and self.step == other.step and self.accidental == other.accidental and self.microtone == other.microtone): return True return False
# noinspection PyArgumentList def __deepcopy__(self, memo: dict[int, t.Any]) -> Pitch: ''' highly optimized -- it knows exactly what can only have a scalar value and just sets that directly, only running deepcopy on the other bits. And _client should NOT be deepcopied. In fact, it is cleared to nothing. deepcopy of note will set it back. ''' if type(self) is Pitch: # pylint: disable=unidiomatic-typecheck new = Pitch.__new__(Pitch) for k in self.__dict__: v = getattr(self, k, None) if k in ('_step', '_overridden_freq440', '_octave', 'spellingIsInferred'): setattr(new, k, v) elif k == '_client': setattr(new, k, None) elif v is None: # common -- save time over deepcopy. setattr(new, k, None) else: setattr(new, k, copy.deepcopy(v, memo)) return new else: # pragma: no cover return common.defaultDeepcopy(self, memo) def __hash__(self) -> int: hashValues = ( self.accidental, self.fundamental, self.spellingIsInferred, self.microtone, self.octave, self.step, type(self), ) return hash(hashValues) def __lt__(self, other: object) -> bool: ''' Accepts enharmonic equivalence. Based entirely on pitch space representation. >>> a = pitch.Pitch('c4') >>> b = pitch.Pitch('c#4') >>> a < b True ''' if not isinstance(other, Pitch): return NotImplemented if self.ps < other.ps: return True else: return False def __le__(self, other: object) -> bool: ''' Less than or equal. Based on the accidentals' alter function. Note that to be equal enharmonics must be the same. So two pitches can be neither lt nor gt and not equal to each other! >>> a = pitch.Pitch('d4') >>> b = pitch.Pitch('d8') >>> c = pitch.Pitch('d4') >>> b <= a False >>> a <= b True >>> a <= c True >>> d = pitch.Pitch('c##4') >>> c >= d False >>> c <= d False Do not rely on this behavior -- it may be changed in a future version to create a total ordering. ''' lt_result = self.__lt__(other) if lt_result is NotImplemented: return NotImplemented if lt_result: return True return self.__eq__(other) def __gt__(self, other: object) -> bool: ''' Accepts enharmonic equivalence. Based entirely on pitch space representation. >>> a = pitch.Pitch('d4') >>> b = pitch.Pitch('d8') >>> a > b False ''' if not isinstance(other, Pitch): return NotImplemented if self.ps > other.ps: return True else: return False def __ge__(self, other: object) -> bool: ''' Greater than or equal. Based on the accidentals' alter function. Note that to be equal enharmonics must be the same. So two pitches can be neither lt nor gt and not equal to each other! >>> a = pitch.Pitch('d4') >>> b = pitch.Pitch('d8') >>> c = pitch.Pitch('d4') >>> a >= b False >>> b >= a True >>> a >= c True >>> c >= a True >>> d = pitch.Pitch('c##4') >>> c >= d False >>> c <= d False Do not rely on this behavior -- it may be changed in a future version to create a total ordering. ''' gt_result = self.__gt__(other) if gt_result is NotImplemented: return NotImplemented if gt_result: return True return self.__eq__(other) # -------------------------------------------------------------------------- @property def groups(self) -> base.Groups: ''' Similar to Groups on music21 object, returns or sets a Groups object. ''' if self._groups is None: self._groups = base.Groups() return self._groups @groups.setter def groups(self, new: base.Groups) -> None: self._groups = new @property def accidental(self) -> Accidental|None: ''' Stores an optional accidental object contained within the Pitch object. This might return None, which is different from a natural accidental: >>> a = pitch.Pitch('E-') >>> a.accidental.alter -1.0 >>> a.accidental.modifier '-' >>> b = pitch.Pitch('C4') >>> b.accidental is None True >>> b.accidental = pitch.Accidental('natural') >>> b.accidental is None False >>> b.accidental <music21.pitch.Accidental natural> ''' return self._accidental @accidental.setter def accidental(self, value: str|Accidental|None|int|float) -> None: if isinstance(value, Accidental): self._accidental = value elif value is None: self._accidental = None elif isinstance(value, str): # int version is used in interval.py which cannot import Pitch directly self._accidental = Accidental(value) elif isinstance(value, int): self._accidental = Accidental(value) self._microtone = None elif isinstance(value, float): # check and add any microtones alter, cents = _convertCentsToAlterAndCents(value * 100.0) self._accidental = Accidental(alter) if abs(cents) > 0.01: self.microtone = Microtone(cents) else: raise ValueError(f'Accidental should be an Accidental object, not {value!r}') self.informClient() @property def microtone(self) -> Microtone: ''' Returns or sets the microtone object contained within the Pitch object. Microtones must be supplied in cents. >>> p = pitch.Pitch('E-4') >>> p.microtone.cents == 0 True >>> p.ps 63.0 >>> p.microtone = 33 # adjustment in cents >>> str(p) 'E-4(+33c)' >>> p.microtone <music21.pitch.Microtone (+33c)> >>> (p.name, p.nameWithOctave) # these representations are unchanged ('E-', 'E-4') >>> p.microtone = '(-12c' # adjustment in cents >>> p <music21.pitch.Pitch E-4(-12c)> >>> p.microtone = pitch.Microtone(-30) >>> p <music21.pitch.Pitch E-4(-30c)> ''' if self._microtone is None: mt = Microtone(0) self._microtone = mt return mt else: return self._microtone @microtone.setter def microtone(self, value: float|int|str|None|Microtone) -> None: if isinstance(value, (str, float, int)): self._microtone = Microtone(value) elif value is None: # set to zero self._microtone = Microtone(0) elif isinstance(value, Microtone): self._microtone = value else: raise PitchException(f'Cannot get a microtone object from {value}') # look for microtones of 0 and set-back to None
[docs] def isTwelveTone(self) -> bool: ''' Return True if this Pitch is one of the twelve tones available on a piano keyboard. Returns False if it instead has a non-zero microtonal adjustment or has a quarter tone accidental. >>> p = pitch.Pitch('g4') >>> p.isTwelveTone() True >>> p.microtone = -20 >>> p.isTwelveTone() False >>> p2 = pitch.Pitch('g~4') >>> p2.isTwelveTone() False ''' if self.accidental is not None: if not self.accidental.isTwelveTone(): return False if self._microtone is not None and self.microtone.cents != 0: return False return True
[docs] def getCentShiftFromMidi(self) -> int: ''' Get cent deviation of this pitch from MIDI pitch. >>> p = pitch.Pitch('c~4') >>> p.ps 60.5 >>> p.midi # midi values automatically round up at 0.5 61 >>> p.getCentShiftFromMidi() -50 >>> p.microtone = -25 >>> p.ps 60.25 >>> p.midi 60 >>> p.getCentShiftFromMidi() 25 >>> p = pitch.Pitch('c#4') >>> p.microtone = -25 >>> p.ps 60.75 >>> p.midi 61 >>> p.getCentShiftFromMidi() -25 >>> p = pitch.Pitch('c#~4') >>> p.ps 61.5 >>> p.midi 62 >>> p.getCentShiftFromMidi() -50 >>> p.microtone = -3 >>> p.ps 61.47 >>> p.midi 61 >>> p.getCentShiftFromMidi() 47 >>> p.microtone = 3 >>> p.ps 61.53 >>> p.midi 62 >>> p.accidental <music21.pitch.Accidental one-and-a-half-sharp> >>> p.getCentShiftFromMidi() -47 >>> p = pitch.Pitch('c`4') # quarter tone flat >>> p.getCentShiftFromMidi() -50 >>> p.microtone = 3 >>> p.getCentShiftFromMidi() -47 Absurd octaves that MIDI can't handle will still give the right sounding pitch out of octave: >>> p = pitch.Pitch('c~4') >>> p.octave = 10 >>> p.ps 132.5 >>> p.midi 121 >>> p.getCentShiftFromMidi() -50 >>> p.octave = -1 >>> p.getCentShiftFromMidi() -50 >>> p.octave = -2 >>> p.getCentShiftFromMidi() -50 ''' midiDistance = self.ps - self.midi # correct for absurd octaves while midiDistance < -11.0: midiDistance += 12.0 while midiDistance > 11.0: midiDistance -= 12.0 return round(midiDistance * 100)
@property def alter(self) -> float: ''' Get the number of half-steps shifted by this pitch, such as 1.0 for a sharp, -1.0 for a flat, 0.0 for a natural, 2.0 for a double sharp, and -0.5 for a quarter tone flat. Thus, the alter value combines the pitch change suggested by the Accidental and the Microtone combined. >>> p = pitch.Pitch('g#4') >>> p.alter 1.0 >>> p.microtone = -25 # in cents >>> p.alter 0.75 To change the alter value, change either the accidental or the microtone. ''' post = 0.0 if self.accidental is not None: post += self.accidental.alter if self._microtone is not None: post += self.microtone.alter return post
[docs] def convertQuarterTonesToMicrotones(self, *, inPlace: bool = False) -> t.Self|None: ''' Convert any quarter tone Accidentals to Microtones. tilde is the symbol for half-sharp, so G#~ is G three-quarters sharp. >>> p = pitch.Pitch('G#~') >>> str(p), p.microtone ('G#~', <music21.pitch.Microtone (+0c)>) >>> p.convertQuarterTonesToMicrotones(inPlace=True) >>> p.ps 68.5 >>> str(p), p.microtone ('G#(+50c)', <music21.pitch.Microtone (+50c)>) >>> p = pitch.Pitch('A') >>> p.accidental = pitch.Accidental('half-flat') # back-tick >>> str(p), p.microtone ('A`', <music21.pitch.Microtone (+0c)>) >>> x = p.convertQuarterTonesToMicrotones(inPlace=False) >>> str(x), x.microtone ('A(-50c)', <music21.pitch.Microtone (-50c)>) >>> str(p), p.microtone ('A`', <music21.pitch.Microtone (+0c)>) ''' if inPlace: returnObj = self else: returnObj = copy.deepcopy(self) if returnObj.accidental is not None: if returnObj.accidental.name == 'half-flat': returnObj.accidental = None returnObj.microtone = returnObj.microtone.cents - 50 # in cents elif returnObj.accidental.name == 'half-sharp': returnObj.accidental = None returnObj.microtone = returnObj.microtone.cents + 50 # in cents elif returnObj.accidental.name == 'one-and-a-half-sharp': returnObj.accidental = 1.0 returnObj.microtone = returnObj.microtone.cents + 50 # in cents elif returnObj.accidental.name == 'one-and-a-half-flat': returnObj.accidental = -1.0 returnObj.microtone = returnObj.microtone.cents - 50 # in cents if not inPlace: return returnObj else: self.informClient()
[docs] def convertMicrotonesToQuarterTones(self, *, inPlace: bool = False) -> t.Self|None: ''' Convert any Microtones available to quarter tones, if possible. >>> p = pitch.Pitch('g3') >>> p.microtone = 78 >>> str(p) 'G3(+78c)' >>> p.convertMicrotonesToQuarterTones(inPlace=True) >>> str(p) 'G#3(-22c)' >>> p = pitch.Pitch('d#3') >>> p.microtone = 46 >>> p <music21.pitch.Pitch D#3(+46c)> >>> p.convertMicrotonesToQuarterTones(inPlace=True) >>> p <music21.pitch.Pitch D#~3(-4c)> >>> p = pitch.Pitch('f#2') >>> p.microtone = -38 >>> p.convertMicrotonesToQuarterTones(inPlace=True) >>> str(p) 'F~2(+12c)' ''' if inPlace: returnObj = self else: returnObj = copy.deepcopy(self) value = returnObj.microtone.cents alterShift, cents = _convertCentsToAlterAndCents(value) if returnObj.accidental is not None: returnObj.accidental = Accidental( returnObj.accidental.alter + alterShift) else: returnObj.accidental = Accidental(alterShift) returnObj.microtone = cents if not inPlace: return returnObj else: self.informClient()
# -------------------------------------------------------------------------- @property def ps(self) -> float: ''' The ps property permits getting and setting a pitch space value, a floating point number representing pitch space, where 60.0 is C4, middle C, 61.0 is C#4 or D-4, and floating point values are microtonal tunings (0.01 is equal to one cent), so a quarter-tone sharp above C5 is 72.5. Note that the choice of 60.0 for C4 makes it identical to the integer value of 60 for .midi, but .midi does not allow for microtones and is limited to 0-127 while .ps allows for notes before midi 0 or above midi 127. >>> a = pitch.Pitch('C4') >>> a.ps 60.0 Changing the ps value for `a` will change the step and octave: >>> a.ps = 45 >>> a <music21.pitch.Pitch A2> >>> a.ps 45.0 Notice that ps 61 represents both C# and D-flat. Thus, "spellingIsInferred" will be true after setting our pitch to 61: >>> a.ps = 61 >>> a <music21.pitch.Pitch C#4> >>> a.ps 61.0 >>> a.spellingIsInferred True Microtonal accidentals and pure Microtones are allowed, as are extreme ranges: >>> b = pitch.Pitch('B9') >>> b.accidental = pitch.Accidental('half-flat') >>> b <music21.pitch.Pitch B`9> >>> b.ps 130.5 >>> p = pitch.Pitch('c4') >>> p.microtone = 20 >>> print(f'{p.ps:.1f}') 60.2 Octaveless pitches use their .implicitOctave attributes: >>> d = pitch.Pitch('D#') >>> d.octave is None True >>> d.implicitOctave 4 >>> d.ps 63.0 >>> d.octave = 5 >>> d.ps 75.0 Setting with microtones >>> p = pitch.Pitch() >>> p.ps = 61 >>> p.ps 61.0 >>> p.spellingIsInferred True >>> p.ps = 61.5 # get a quarter tone >>> p <music21.pitch.Pitch C#~4> >>> p.ps = 61.7 # set a microtone >>> print(p) C#~4(+20c) >>> p.ps = 61.4 # set a microtone >>> print(p) C#~4(-10c) The property is called when self.step, self.octave or self.accidental are changed. ''' step = self._step ps = float(((self.implicitOctave + 1) * 12) + STEPREF[step]) if self.accidental is not None: ps = ps + self.accidental.alter if self._microtone is not None: ps = ps + self.microtone.alter return ps @ps.setter def ps(self, value: int|float) -> None: # can assign microtone here; will be either None or a Microtone object self.step, acc, self._microtone, octShift = _convertPsToStep(value) # replace a natural with a None if acc.name == 'natural': self.accidental = None else: self.accidental = acc self.octave = _convertPsToOct(value) + octShift # all ps settings must set implicit to True, as we do not know # what accidental this is self.spellingIsInferred = True @property def midi(self) -> int: ''' Get or set a pitch value in MIDI. MIDI pitch values are like ps values (pitchSpace) rounded to the nearest integer; while the ps attribute will accommodate floats. >>> c = pitch.Pitch('C4') >>> c.midi 60 >>> c.midi = 23.5 >>> c.midi 24 Note that like ps (pitchSpace), MIDI notes do not distinguish between sharps and flats, etc. >>> dSharp = pitch.Pitch('D#4') >>> dSharp.midi 63 >>> eFlat = pitch.Pitch('E-4') >>> eFlat.midi 63 Midi values are constrained to the space 0-127. Higher or lower values will be transposed octaves to fit in this space. >>> veryHighFHalfFlat = pitch.Pitch('F') >>> veryHighFHalfFlat.octave = 12 >>> veryHighFHalfFlat.accidental = pitch.Accidental('half-flat') >>> veryHighFHalfFlat <music21.pitch.Pitch F`12> >>> veryHighFHalfFlat.ps 160.5 >>> veryHighFHalfFlat.midi 125 >>> veryHighFHalfFlat.octave = 9 >>> veryHighFHalfFlat.midi 125 >>> notAsHighNote = pitch.Pitch() >>> notAsHighNote.ps = veryHighFHalfFlat.midi >>> notAsHighNote <music21.pitch.Pitch F9> Note that the conversion of improper midi values to proper midi values is done before assigning .ps: >>> a = pitch.Pitch() >>> a.midi = -10 >>> a.midi 2 >>> a.ps 2.0 >>> a.spellingIsInferred True More absurd octaves: >>> p = pitch.Pitch('c~4') >>> p.octave = -1 >>> p.ps 0.5 >>> p.midi 1 >>> p.octave = -2 >>> p.ps -11.5 >>> p.midi 1 ''' def schoolYardRounding(x: int|float) -> int: ''' This is round "up" at 0.5 (regardless of negative or positive) Python 3 now uses rounding mechanisms so that odd numbers round one way, even another. But we needed a consistent direction for all half-sharps/flats to go, and now long after Python 2 is no longer supported, this behavior is grandfathered in. ''' return math.floor(x + 0.5) roundedPS = schoolYardRounding(self.ps) if roundedPS > 127: value = (12 * 9) + (roundedPS % 12) if value < (127 - 12): value += 12 elif roundedPS < 0: value = 0 + (roundedPS % 12) else: value = roundedPS return value @midi.setter def midi(self, value: int|float) -> None: ''' MIDI values are constrained within the range of 0 to 127; floating-point values are rounded to the nearest integer. ''' value = round(value) if value > 127: value = (12 * 9) + (value % 12) # highest oct plus modulus if value < (127 - 12): value += 12 elif value < 0: value = 0 + (value % 12) # lowest oct plus modulus self.ps = value # will inform client. # all midi settings must set implicit to True, as we do not know # what accidental this is self.spellingIsInferred = True @property def name(self) -> str: ''' Gets or sets the name (pitch name with accidental but without octave) of the Pitch. >>> p = pitch.Pitch('D#5') >>> p.name 'D#' >>> p.name = 'C#' >>> p.name 'C#' >>> a = pitch.Pitch('G#') >>> a.name 'G#' Does not simplify enharmonics >>> a = pitch.Pitch('B---') >>> a.name 'B---' ''' if self.accidental is not None: return self.step + self.accidental.modifier else: return self.step @name.setter def name(self, usrStr: str) -> None: ''' Set name, which may be provided with or without octave values. C4 or D-3 are both accepted. ''' try: usrStr = usrStr.strip() except AttributeError: raise ValueError(f'Argument to name, {usrStr!r}, must be a string, not {type(usrStr)}.') # extract any numbers that may be octave designations octFound: list[str] = [] octNot: list[str] = [] foundNonOctave = False for char in usrStr: if char in '0123456789': if not foundNonOctave: raise ValueError(f'Cannot have octave given before pitch name in {usrStr!r}.') octFound.append(char) else: foundNonOctave = True octNot.append(char) usrStr = ''.join(octNot) octFoundStr = ''.join(octFound) # we have nothing but pitch specification if len(usrStr) == 1: self.step = usrStr # type: ignore self.accidental = None # assume everything following pitch is accidental specification elif len(usrStr) > 1: self.step = usrStr[0] # type: ignore self.accidental = Accidental(usrStr[1:]) else: raise PitchException(f'Cannot make a name out of {usrStr!r}') if octFoundStr: # bool('0') == True, so okay octave = int(octFoundStr) self.octave = octave @property def unicodeName(self) -> str: ''' Name presently returns pitch name and accidental without octave. >>> a = pitch.Pitch('G#') >>> a.unicodeName 'G♯' ''' if self.accidental is not None: return self.step + self.accidental.unicode else: return self.step @property def nameWithOctave(self) -> str: ''' Return or set the pitch name with an octave designation. If no octave has been set, no octave value is returned. >>> gSharp = pitch.Pitch('G#4') >>> gSharp.nameWithOctave 'G#4' >>> dFlatFive = pitch.Pitch() >>> dFlatFive.step = 'D' >>> dFlatFive.accidental = pitch.Accidental('flat') >>> dFlatFive.octave = 5 >>> dFlatFive.nameWithOctave 'D-5' >>> dFlatFive.nameWithOctave = 'C#6' >>> dFlatFive.name 'C#' >>> dFlatFive.octave 6 N.B. -- it's generally better to set the name and octave separately, especially since you may at some point encounter very low pitches such as "A octave -1", which will be interpreted as "A-flat, octave 1". Our crude setting algorithm also does not support octaves above 9. >>> lowA = pitch.Pitch() >>> lowA.name = 'A' >>> lowA.octave = -1 >>> lowA.nameWithOctave 'A-1' >>> lowA.nameWithOctave = lowA.nameWithOctave >>> lowA.name 'A-' >>> lowA.octave 1 Octave must be included in nameWithOctave or an exception is raised: >>> a = pitch.Pitch() >>> a.nameWithOctave = 'C#9' >>> a.nameWithOctave = 'C#' Traceback (most recent call last): music21.pitch.PitchException: Cannot set a nameWithOctave with 'C#' Set octave to None explicitly instead. ''' if self.octave is None: return self.name else: return self.name + str(self.octave) @nameWithOctave.setter def nameWithOctave(self, value: str) -> None: try: lenVal = len(value) name = value[0:lenVal - 1] octave = int(value[-1]) self.name = name self.octave = octave except (ValueError, PitchException) as e: raise PitchException(f'Cannot set a nameWithOctave with {value!r}') from e @property def unicodeNameWithOctave(self) -> str: ''' Return the pitch name with octave with unicode accidental symbols, if available. Read-only property. >>> p = pitch.Pitch('C#4') >>> p.unicodeNameWithOctave 'C♯4' ''' if self.octave is None: return self.unicodeName else: return self.unicodeName + str(self.octave) @property def fullName(self) -> str: ''' Return the most complete representation of this Pitch, providing name, octave, accidental, and any microtonal adjustments. >>> p = pitch.Pitch('A-3') >>> p.microtone = 33.33 >>> p.fullName 'A-flat in octave 3 (+33c)' >>> p = pitch.Pitch('A`7') >>> p.fullName 'A-half-flat in octave 7' ''' name: str = self.step if self.accidental is not None: name += f'-{self.accidental.fullName}' if self.octave is not None: name += f' in octave {self.octave}' if self._microtone is not None and self.microtone.cents != 0: name += f' {self._microtone}' return name @property def step(self) -> StepName: ''' The diatonic name of the note; i.e. does not give the accidental or octave. >>> a = pitch.Pitch('B-3') >>> a.step 'B' Upper-case or lower-case names can be given to `.step` -- they will be converted to upper-case >>> b = pitch.Pitch() >>> b.step = 'c' >>> b.step 'C' Changing the `.step` does not change the `.accidental` or `.octave`: >>> a = pitch.Pitch('F#5') >>> a.step = 'D' >>> a.nameWithOctave 'D#5' Giving a value that includes an accidental raises a PitchException. Use .name instead to change that. >>> b = pitch.Pitch('E4') >>> b.step = 'B-' Traceback (most recent call last): music21.pitch.PitchException: Cannot make a step out of 'B-' This is okay though: >>> b.name = 'B-' Note that if spelling is inferred, setting the step does NOT give that enharmonic. Perhaps it should, but best to make people use .getLowerEnharmonic or .getHigherEnharmonic instead. >>> b.ps = 60 >>> b.nameWithOctave 'C4' >>> b.spellingIsInferred True >>> b.step = 'B' >>> b.accidental is None # maybe this should set to B#? But that could be screwy. True >>> b.spellingIsInferred False ''' return self._step @step.setter def step(self, usrStr: StepName) -> None: ''' This does not change octave or accidental, only step ''' usrStr = usrStr.strip().upper() # type: ignore if len(usrStr) == 1 and usrStr in STEPNAMES: self._step = usrStr # type: ignore # when setting by step, we assume that the accidental intended self.spellingIsInferred = False else: raise PitchException(f'Cannot make a step out of {usrStr!r}') self.informClient() @property def pitchClass(self) -> int: ''' Returns or sets the integer value for the pitch, 0-11, where C=0, C#=1, D=2, etc. to B=11. Can be set using integers (0-11) or 'A' or 'B' for 10 or 11. >>> a = pitch.Pitch('a3') >>> a.pitchClass 9 >>> dis = pitch.Pitch('d3') >>> dis.pitchClass 2 >>> dis.accidental = pitch.Accidental('#') >>> dis.pitchClass 3 If a string "A" or "B" is given to pitchClass, it is still returned as an int. >>> dis.pitchClass = 'A' >>> dis.pitchClass 10 >>> dis.name 'B-' Extreme octaves will not affect pitchClass >>> dis.octave = -10 >>> dis.pitchClass 10 In the past, certain microtones and/or octaves were returning pc 12! This is now fixed. >>> flattedC = pitch.Pitch('C4') >>> flattedC.microtone = -4 >>> print(flattedC) C4(-4c) >>> flattedC.pitchClass 0 >>> print(flattedC.ps) 59.96 >>> flattedC.octave = -3 >>> print(flattedC.ps) -24.04 >>> flattedC.pitchClass 0 Note that the pitchClass of a microtonally altered pitch is the pitch class of the nearest pitch. Python 3 uses the "round-to-even" method so C~4 (C half sharp 4) is pitchClass 0, since 60.5 rounds to 60.0 >>> p = pitch.Pitch('C~4') >>> p.ps 60.5 >>> p.pitchClass 0 However, for backwards compatability, the MIDI number of a microtone is created by using "schoolyard" rounding which always rounds 0.5 upwards, which can cause some unusual behavior: >>> p.midi 61 >>> pitch.Pitch(midi=p.midi).pitchClass 1 This means that pitchClass + microtone is NOT a good way to estimate the frequency of a pitch. For instance, if we take a pitch that is 90% of the way between pitchClass 0 (C) and pitchClass 1 (C#/D-flat), this formula gives an inaccurate answer of 1.9, not 0.9: >>> p = pitch.Pitch('C4') >>> p.microtone = 90 >>> p <music21.pitch.Pitch C4(+90c)> >>> p.pitchClass + (p.microtone.cents / 100.0) 1.9 More examples of setting the pitchClass. >>> a = pitch.Pitch('a3') >>> a.pitchClass = 3 >>> a <music21.pitch.Pitch E-3> >>> a.spellingIsInferred True >>> a.pitchClass = 'A' >>> a <music21.pitch.Pitch B-3> Changing pitchClass does not remove microtones. >>> a.microtone = 20 >>> a.pitchClass = 1 >>> a <music21.pitch.Pitch C#3(+20c)> ''' return round(self.ps) % 12 @pitchClass.setter def pitchClass(self, value: int|PitchClassString) -> None: # permit the submission of strings, like "A" and "B" valueOut: int|float = _convertPitchClassToNumber(value) # get step and accidental w/o octave self.step, self._accidental = _convertPsToStep(valueOut)[0:2] # do not know what accidental is self.spellingIsInferred = True # setting step informs client @property def pitchClassString(self) -> str: ''' Returns or sets a string representation of the pitch class, where integers greater than 10 are replaced by A and B, respectively. Can be used to set pitch class by a string representation as well (though this is also possible with :attr:`~music21.pitch.Pitch.pitchClass`). >>> a = pitch.Pitch('a#3') >>> a.pitchClass 10 >>> a.pitchClassString 'A' We can set the pitchClassString as well: >>> a.pitchClassString = 'B' >>> a.pitchClass 11 ''' return convertPitchClassToStr(self.pitchClass) @pitchClassString.setter def pitchClassString(self, v: int|PitchClassString) -> None: self.pitchClass = v @property def octave(self) -> int|None: ''' Returns or sets the octave of the note. Setting the octave updates the pitchSpace attribute. >>> a = pitch.Pitch('g') >>> a.octave is None True >>> a.implicitOctave 4 >>> a.ps ## will use implicitOctave 67.0 >>> a.name 'G' >>> a.octave = 14 >>> a.octave 14 >>> a.implicitOctave 14 >>> a.name 'G' >>> a.ps 187.0 ''' return self._octave @octave.setter def octave(self, value: int|float|None) -> None: if value is not None: self._octave = int(value) else: self._octave = None self.informClient() @property def implicitOctave(self) -> int: ''' Returns the octave of the Pitch, or defaultOctave if octave was never set. To set an octave, use .octave. Default octave is usually 4. >>> p = pitch.Pitch('C#') >>> p.octave is None True >>> p.implicitOctave 4 Cannot be set. Instead, just change the `.octave` of the pitch ''' if self.octave is None: return defaults.pitchOctave else: return self.octave # noinspection SpellCheckingInspection,GrazieInspection @property def german(self) -> str: ''' Read-only property. Returns a unicode string of the name of a Pitch in the German system (where B-flat = B, B = H, etc.) (Microtones and Quarter tones raise an error). Note that Ases is used instead of the also acceptable Asas. >>> print(pitch.Pitch('B-').german) B >>> print(pitch.Pitch('B').german) H >>> print(pitch.Pitch('E-').german) Es >>> print(pitch.Pitch('C#').german) Cis >>> print(pitch.Pitch('A--').german) Ases >>> p1 = pitch.Pitch('C') >>> p1.accidental = pitch.Accidental('half-sharp') >>> p1.german Traceback (most recent call last): music21.pitch.PitchException: Es geht nicht "german" zu benutzen mit Microtönen. Schade! Note these rarely used pitches: >>> print(pitch.Pitch('B--').german) Heses >>> print(pitch.Pitch('B#').german) His ''' if self.accidental is not None: tempAlter = self.accidental.alter else: tempAlter = 0 tempStep: str = self.step if tempAlter != int(tempAlter): raise PitchException('Es geht nicht "german" zu benutzen mit Microtönen. Schade!') tempAlter = int(tempAlter) if tempStep == 'B': if tempAlter != -1: tempStep = 'H' else: tempAlter += 1 if tempAlter == 0: return tempStep elif tempAlter > 0: tempName = tempStep + (tempAlter * 'is') return tempName else: # flats if tempStep in ('C', 'D', 'F', 'G', 'H'): firstFlatName = 'es' else: # A, E. Bs should never occur firstFlatName = 's' multipleFlats = abs(tempAlter) - 1 tempName = tempStep + firstFlatName + (multipleFlats * 'es') return tempName # noinspection SpellCheckingInspection @property def italian(self) -> str: # noinspection SpellCheckingInspection,GrazieInspection ''' Read-only attribute. Returns the name of a Pitch in the Italian system (F-sharp is fa diesis, C-flat is do bemolle, etc.) (Microtones and Quarter tones raise an error). >>> print(pitch.Pitch('B-').italian) si bemolle >>> print(pitch.Pitch('B').italian) si >>> print(pitch.Pitch('E-9').italian) mi bemolle >>> print(pitch.Pitch('C#').italian) do diesis >>> print(pitch.Pitch('A--4').italian) la doppio bemolle >>> p1 = pitch.Pitch('C') >>> p1.accidental = pitch.Accidental('half-sharp') >>> p1.italian Traceback (most recent call last): music21.pitch.PitchException: Non si puo usare `italian` con microtoni Note these rarely used pitches: >>> print(pitch.Pitch('E####').italian) mi quadruplo diesis >>> print(pitch.Pitch('D---').italian) re triplo bemolle ''' if self.accidental is not None: tempAlter = self.accidental.alter else: tempAlter = 0 tempStep: str = self.step if tempAlter != int(tempAlter): raise PitchException('Non si puo usare `italian` con microtoni') tempAlter = int(tempAlter) cardinalityMap = {1: ' ', 2: ' doppio ', 3: ' triplo ', 4: ' quadruplo '} solfeggeMap = {'C': 'do', 'D': 're', 'E': 'mi', 'F': 'fa', 'G': 'sol', 'A': 'la', 'B': 'si'} if tempAlter == 0: return solfeggeMap[tempStep] elif tempAlter > 0: if tempAlter > 4: raise PitchException('Entirely too many sharps') return solfeggeMap[tempStep] + cardinalityMap[tempAlter] + 'diesis' else: # flats tempAlter = tempAlter * -1 if tempAlter > 4: raise PitchException('Entirely too many flats') return solfeggeMap[tempStep] + cardinalityMap[tempAlter] + 'bemolle' def _getSpanishCardinal(self) -> str: if self.accidental is None: return '' else: i = abs(self.accidental.alter) # already checked for microtones, etc. if i == 1: return '' elif i == 2: return ' doble' elif i == 3: return ' triple' elif i == 4: return ' cuádruple' return '' _SPANISH_DICT: dict[StepName, str] = { 'A': 'la', 'B': 'si', 'C': 'do', 'D': 're', 'E': 'mi', 'F': 'fa', 'G': 'sol', } # noinspection SpellCheckingInspection,GrazieInspection @property def spanish(self) -> str: ''' Read-only attribute. Returns the name of a Pitch in Spanish (Microtones and Quarter tones raise an error). >>> print(pitch.Pitch('B-').spanish) si bemol >>> print(pitch.Pitch('E-').spanish) mi bemol >>> print(pitch.Pitch('C#').spanish) do sostenido >>> print(pitch.Pitch('A--').spanish) la doble bemol >>> p1 = pitch.Pitch('C') >>> p1.accidental = pitch.Accidental('half-sharp') >>> p1.spanish Traceback (most recent call last): music21.pitch.PitchException: Unsupported accidental type. Note these rarely used pitches: >>> print(pitch.Pitch('B--').spanish) si doble bemol >>> print(pitch.Pitch('B#').spanish) si sostenido ''' if self.accidental is not None: tempAlter = self.accidental.alter else: tempAlter = 0 solfege = self._SPANISH_DICT[self.step] if tempAlter != int(tempAlter): raise PitchException('Unsupported accidental type.') if tempAlter == 0: return solfege elif tempAlter in {-4, -3, -2, -1}: return solfege + self._getSpanishCardinal() + ' bemol' elif tempAlter in {1, 2, 3, 4}: return solfege + self._getSpanishCardinal() + ' sostenido' else: raise PitchException('Unsupported accidental type.') _FRENCH_DICT: dict[StepName, str] = { 'A': 'la', 'B': 'si', 'C': 'do', 'D': 'ré', 'E': 'mi', 'F': 'fa', 'G': 'sol', } # noinspection SpellCheckingInspection @property def french(self) -> str: # noinspection GrazieInspection ''' Read-only attribute. Returns the name of a Pitch in the French system (where A = la, B = si, B-flat = si bémol, C-sharp = do dièse, etc.) (Microtones and Quarter tones raise an error). Note that do is used instead of the also acceptable ut. >>> print(pitch.Pitch('B-').french) si bémol >>> print(pitch.Pitch('B').french) si >>> print(pitch.Pitch('E-').french) mi bémol >>> print(pitch.Pitch('C#').french) do dièse >>> print(pitch.Pitch('A--').french) la double bémol >>> p1 = pitch.Pitch('C') >>> p1.accidental = pitch.Accidental('half-sharp') >>> p1.french Traceback (most recent call last): music21.pitch.PitchException: On ne peut pas utiliser les microtones avec "french." Quelle Dommage! ''' if self.accidental is not None: tempAlter = self.accidental.alter else: tempAlter = 0 tempStep: str = self._FRENCH_DICT[self.step] if tempAlter != int(tempAlter): raise PitchException( 'On ne peut pas utiliser les microtones avec "french." Quelle Dommage!') if abs(tempAlter) > 4.0: raise PitchException( 'On ne peut pas utiliser les altération avec puissance supérieure à quatre ' + 'avec "french." Ça me fait une belle jambe!' ) tempAlter = int(tempAlter) if tempAlter == 0: return tempStep elif abs(tempAlter) == 1.0: tempNumberedStep = tempStep elif abs(tempAlter) == 2.0: tempNumberedStep = tempStep + ' double' elif abs(tempAlter) == 3.0: tempNumberedStep = tempStep + ' triple' elif abs(tempAlter) == 4.0: tempNumberedStep = tempStep + ' quadruple' else: raise PitchException(f'Cannot deal with tempStep: {tempStep}') if tempAlter / abs(tempAlter) == 1.0: # sharps are positive tempName = tempNumberedStep + ' dièse' return tempName else: # flats are negative tempName = tempNumberedStep + ' bémol' return tempName @property def frequency(self) -> float: ''' The frequency property gets or sets the frequency of the pitch in hertz. If the frequency has not been overridden, then it is computed based on A440Hz and equal temperament >>> a = pitch.Pitch() >>> a.frequency = 440.0 >>> a.frequency 440.0 >>> a.name 'A' >>> a.octave 4 Microtones are captured if the frequency doesn't correspond to any standard note. >>> a.frequency = 450.0 >>> a <music21.pitch.Pitch A~4(-11c)> ''' return self.freq440 @frequency.setter def frequency(self, value: int|float) -> None: self.freq440 = value # these methods may belong in a temperament object # name of method and property could be more clear @property def freq440(self) -> float: ''' Gets the frequency of the note as if it's in an equal temperament context where A4 = 440hz. The same as .frequency so long as no other temperaments are currently being used. Since we don't have any other temperament objects at present, this is the same as .frequency always. >>> a = pitch.Pitch('A4') >>> a.freq440 440.0 ''' if self._overridden_freq440: return self._overridden_freq440 else: # works off of .ps values and thus will capture microtones A4offset = self.ps - 69 return 440.0 * (self._twelfth_root_of_two ** A4offset) @freq440.setter def freq440(self, value: int|float) -> None: post = 12 * (math.log(value / 440.0) / math.log(2)) + 69 # environLocal.printDebug(['convertFqToPs():', 'input', fq, 'output', repr(post)]) # rounding here is essential p2 = round(post, PITCH_SPACE_SIG_DIGITS) self.ps = p2 # --------------------------------------------------------------------------
[docs] def getHarmonic(self, number: int) -> Pitch: ''' Return a Pitch object representing the harmonic found above this Pitch. >>> p = pitch.Pitch('a4') >>> print(p.getHarmonic(2)) A5 >>> print(p.getHarmonic(3)) E6(+2c) >>> print(p.getHarmonic(4)) A6 >>> print(p.getHarmonic(5)) C#7(-14c) >>> print(p.getHarmonic(6)) E7(+2c) >>> print(p.getHarmonic(7)) F#~7(+19c) >>> print(p.getHarmonic(8)) A7 >>> p2 = p.getHarmonic(2) >>> p2 <music21.pitch.Pitch A5> >>> p2.fundamental <music21.pitch.Pitch A4> >>> p2.transpose('p5', inPlace=True) >>> p2 <music21.pitch.Pitch E6> >>> p2.fundamental <music21.pitch.Pitch E5> Or we can iterate over a list of the next 8 odd harmonics: >>> allHarmonics = '' >>> for i in [9, 11, 13, 15, 17, 19, 21, 23]: ... allHarmonics += ' ' + str(p.getHarmonic(i)) >>> print(allHarmonics) B7(+4c) D~8(+1c) F~8(-9c) G#8(-12c) B-8(+5c) C9(-2c) C#~9(+21c) E`9(-22c) Microtonally adjusted notes also generate harmonics: >>> q = pitch.Pitch('C4') >>> q.microtone = 10 >>> q.getHarmonic(2) <music21.pitch.Pitch C5(+10c)> >>> q.getHarmonic(3) <music21.pitch.Pitch G5(+12c)> The fundamental is stored with the harmonic. >>> h7 = pitch.Pitch('A4').getHarmonic(7) >>> print(h7) F#~7(+19c) >>> h7.fundamental <music21.pitch.Pitch A4> >>> h7.harmonicString() '7thH/A4' >>> h7.harmonicString('A3') '14thH/A3' >>> h2 = h7.getHarmonic(2) >>> h2 <music21.pitch.Pitch F#~8(+19c)> >>> h2.fundamental <music21.pitch.Pitch F#~7(+19c)> >>> h2.fundamental.fundamental <music21.pitch.Pitch A4> >>> h2.transpose(-24, inPlace=True) >>> h2 <music21.pitch.Pitch F#~6(+19c)> >>> h2.fundamental.fundamental <music21.pitch.Pitch A2> ''' centShift = _convertHarmonicToCents(number) temp: Pitch = copy.deepcopy(self) # if no change, just return what we start with if centShift == 0: return temp # add this pitch's microtones plus the necessary cent shift if self._microtone is not None: temp.microtone = Microtone(temp.microtone.cents + centShift) else: temp.microtone = Microtone(centShift) # environLocal.printDebug(['getHarmonic()', 'self', self, # 'self.frequency', self.frequency, 'centShift', centShift, 'temp', temp, # 'temp.frequency', temp.frequency, 'temp.microtone', temp.microtone]) # possibly optimize this to use only two Pitch objects final = Pitch() # set with this frequency final.frequency = temp.frequency # store a copy as the fundamental final.fundamental = copy.deepcopy(self) # environLocal.printDebug(['getHarmonic()', 'final', final, # 'final.frequency', final.frequency]) return final
[docs] def harmonicFromFundamental(self, fundamental: str|Pitch, ) -> tuple[int, float]: # noinspection PyShadowingNames ''' Given another Pitch as a fundamental, find the harmonic of that pitch that is equal to this Pitch. Returns a tuple of harmonic number and the number of cents that the first Pitch object would have to be shifted to be the exact harmonic of this fundamental. Microtones applied to the fundamental are irrelevant, as the fundamental may be microtonally shifted to find a match to this Pitch. Example: G4 is the third harmonic of C3, albeit 2 cents flatter than the true 3rd harmonic. >>> p = pitch.Pitch('g4') >>> f = pitch.Pitch('c3') >>> p.harmonicFromFundamental(f) (3, 2.0) >>> p.microtone = p.harmonicFromFundamental(f)[1] # adjust microtone >>> int(f.getHarmonic(3).frequency) == int(p.frequency) True The shift from B-5 to the 7th harmonic of C3 is more substantial and likely to be noticed by the audience. To make p the 7th harmonic it'd have to be lowered by 31 cents. Note that the second argument is a float, but because the default rounding of music21 is to the nearest cent, the 0.0 is not a significant digit. I.e. it might be more like 31.3 cents. >>> p = pitch.Pitch('B-5') >>> f = pitch.Pitch('C3') >>> p.harmonicFromFundamental(f) (7, -31.0) ''' if isinstance(fundamental, str): fundamental = Pitch(fundamental) # else assume a Pitch object # got through all harmonics and find the one closes to this ps value target = self if target.ps <= fundamental.ps: raise PitchException( 'cannot find an equivalent harmonic for a fundamental ' f'({fundamental}) that is not above this Pitch ({self})' ) # up to the 32 harmonic found = [] # store a list for i in range(1, 32): # gather all until we are above the target p = fundamental.getHarmonic(i) found.append((i, p)) if p.ps > target.ps: break # environLocal.printDebug(['harmonicFromFundamental():', 'fundamental', fundamental, # 'found', found]) # it is either the last or the second to last if len(found) < 2: # only 1 harmonicMatch, match = found[0] if match.ps > target.ps: gap = match.ps - target.ps elif match.ps < target.ps: gap = target.ps - match.ps else: gap = 0 else: harmonicLower, candidateLower = found[-2] harmonicHigher, candidateHigher = found[-1] distanceLower = target.ps - candidateLower.ps distanceHigher = candidateHigher.ps - target.ps # environLocal.printDebug(['harmonicFromFundamental():', # 'distanceLower', distanceLower, 'distanceHigher', distanceHigher, # 'target', target]) if distanceLower <= distanceHigher: # the lower is closer, thus we need to raise gap match = candidateLower gap = -abs(distanceLower) harmonicMatch = harmonicLower else: # the higher is closer, thus we need to lower the gap match = candidateHigher gap = abs(distanceHigher) harmonicMatch = harmonicHigher gap = round(gap, PITCH_SPACE_SIG_DIGITS) * 100 return harmonicMatch, gap
# environLocal.printDebug(['harmonicFromFundamental():', 'match', match, # 'gap', gap, 'harmonicMatch', harmonicMatch]) # # need to find gap, otherwise may get very small values # gap = round(gap, PITCH_SPACE_SIG_DIGITS) # # create a pitch with the appropriate gap as a Microtone # if fundamental.microtone is not None: # # if the result is zero, .microtone will automatically # # be set to None # fundamental.microtone = fundamental.microtone.cents + (gap * 100) # else: # if gap != 0: # fundamental.microtone = gap * 100 # return harmonicMatch, fundamental
[docs] def harmonicString(self, fundamental: str|Pitch|None = None ) -> str: ''' Return a string representation of a harmonic equivalence. N.B. this has nothing to do with what string a string player would use to play the harmonic on. (Perhaps should be renamed). >>> pitch.Pitch('g4').harmonicString('c3') '3rdH(-2c)/C3' >>> pitch.Pitch('c4').harmonicString('c3') '2ndH/C3' >>> p = pitch.Pitch('c4') >>> p.microtone = 20 # raise 20 >>> p.harmonicString('c3') '2ndH(+20c)/C3' >>> p.microtone = -20 # lower 20 >>> p.harmonicString('c3') '2ndH(-20c)/C3' >>> p = pitch.Pitch('c4') >>> f = pitch.Pitch('c3') >>> f.microtone = -20 >>> p.harmonicString(f) '2ndH(+20c)/C3(-20c)' >>> f.microtone = +20 >>> p.harmonicString(f) '2ndH(-20c)/C3(+20c)' >>> p = pitch.Pitch('A4') >>> p.microtone = 69 >>> p.harmonicString('c2') '7thH/C2' >>> p = pitch.Pitch('A4') >>> p.harmonicString('c2') '7thH(-69c)/C2' ''' if fundamental is None: if self.fundamental is None: raise PitchException('no fundamental is defined for this Pitch: ' + 'provide one as an argument') fundamental = self.fundamental if isinstance(fundamental, str): fundamental = Pitch(fundamental) harmonic, cents = self.harmonicFromFundamental(fundamental) # need to invert cent shift, as we are suggesting a shifted harmonic microtone = Microtone(-cents) abbr = common.ordinalAbbreviation(harmonic) if cents == 0: return f'{harmonic}{abbr}H/{fundamental}' else: return f'{harmonic}{abbr}H{microtone}/{fundamental}'
[docs] def harmonicAndFundamentalFromPitch( self, target: str|Pitch ) -> tuple[int, Pitch]: ''' Given a Pitch that is a plausible target for a fundamental, return the harmonic number and a potentially shifted fundamental that describes this Pitch. >>> g4 = pitch.Pitch('g4') >>> g4.harmonicAndFundamentalFromPitch('c3') (3, <music21.pitch.Pitch C3(-2c)>) ''' if isinstance(target, str): target = Pitch(target) else: # make a copy target = copy.deepcopy(target) harmonic, cents = self.harmonicFromFundamental(target) # flip direction cents = -cents # create a pitch with the appropriate gap as a Microtone if target.microtone is not None: # if the result is zero, .microtone will automatically # be set to None target.microtone = target.microtone.cents + cents else: if cents != 0: target.microtone = cents return harmonic, target
[docs] def harmonicAndFundamentalStringFromPitch( self, fundamental: str|Pitch ) -> str: ''' Given a Pitch that is a plausible target for a fundamental, find the harmonic number and a potentially shifted fundamental that describes this Pitch. Return a string representation. >>> pitch.Pitch('g4').harmonicAndFundamentalStringFromPitch('c3') '3rdH/C3(-2c)' >>> pitch.Pitch('c4').harmonicAndFundamentalStringFromPitch('c3') '2ndH/C3' >>> p = pitch.Pitch('c4') >>> p.microtone = 20 # raise 20 >>> p.harmonicAndFundamentalStringFromPitch('c3') '2ndH/C3(+20c)' >>> p.microtone = -20 # lower 20 >>> p.harmonicAndFundamentalStringFromPitch('c3') '2ndH/C3(-20c)' >>> p = pitch.Pitch('c4') >>> f = pitch.Pitch('c3') >>> f.microtone = -20 >>> p.harmonicAndFundamentalStringFromPitch(f) '2ndH/C3' >>> f.microtone = +20 >>> p.harmonicAndFundamentalStringFromPitch(f) '2ndH/C3' >>> p = pitch.Pitch('A4') >>> p.microtone = 69 >>> p.harmonicAndFundamentalStringFromPitch('c2') '7thH/C2' >>> p = pitch.Pitch('A4') >>> p.harmonicAndFundamentalStringFromPitch('c2') '7thH/C2(-69c)' ''' harmonic, fundamental = self.harmonicAndFundamentalFromPitch(fundamental) abbr = common.ordinalAbbreviation(harmonic) return f'{harmonic}{abbr}H/{fundamental}'
# --------------------------------------------------------------------------
[docs] def isEnharmonic(self, other: Pitch) -> bool: ''' Return True if another Pitch is an enharmonic equivalent of this Pitch. >>> p1 = pitch.Pitch('C#3') >>> p2 = pitch.Pitch('D-3') >>> p3 = pitch.Pitch('D#3') >>> p1.isEnharmonic(p2) True >>> p2.isEnharmonic(p1) True >>> p3.isEnharmonic(p1) False Pitches are enharmonics of themselves: >>> pC = pitch.Pitch('C4') >>> pC.isEnharmonic(pC) True Notes that sound in different octaves are not enharmonics: >>> pitch.Pitch('C#4').isEnharmonic( pitch.Pitch('D-5') ) False However, different octave numbers can be the same enharmonic, because octave number is relative to the `step` (natural form) of the pitch. >>> pitch.Pitch('C4').isEnharmonic( pitch.Pitch('B#3') ) True >>> pitch.Pitch('C4').isEnharmonic( pitch.Pitch('B#4') ) False If either pitch is octaveless, then a pitch in any octave will match: >>> pitch.Pitch('C#').isEnharmonic( pitch.Pitch('D-9') ) True >>> pitch.Pitch('C#4').isEnharmonic( pitch.Pitch('D-') ) True Quarter tone enharmonics work as well: >>> pC.accidental = pitch.Accidental('one-and-a-half-sharp') >>> pC <music21.pitch.Pitch C#~4> >>> pD = pitch.Pitch('D4') >>> pD.accidental = pitch.Accidental('half-flat') >>> pD <music21.pitch.Pitch D`4> >>> pC.isEnharmonic(pD) True Microtonally altered pitches do not return True unless the microtones are the same: >>> pSharp = pitch.Pitch('C#4') >>> pSharp.microtone = 20 >>> pFlat = pitch.Pitch('D-4') >>> pSharp.isEnharmonic(pFlat) False >>> pFlat.microtone = 20 >>> pSharp.isEnharmonic(pFlat) True Extreme enharmonics also work: >>> p4 = pitch.Pitch('B##3') >>> p5 = pitch.Pitch('E---4') >>> p4.isEnharmonic(p5) True If either pitch has no octave then the comparison is done without regard to octave: >>> pSharp4 = pitch.Pitch('C#4') >>> pFlatNoOctave = pitch.Pitch('D-') >>> pSharp4.isEnharmonic(pFlatNoOctave) True >>> pFlatNoOctave.isEnharmonic(pSharp4) True `isEnharmonic` can be combined with a test to see if two pitches have the same step to ensure that they both sound the same and are written the same, without regard to the presence or absence of an accidental (this is used in `:meth:~music21.stream.base.Stream.stripTies`): >>> pD4 = pitch.Pitch('D4') >>> pDNatural4 = pitch.Pitch('D4', accidental=pitch.Accidental('natural')) >>> pD4 == pDNatural4 False >>> pD4.isEnharmonic(pDNatural4) and pD4.step == pDNatural4.step True >>> pEbb4 = pitch.Pitch('E--4') >>> pD4.isEnharmonic(pEbb4) and pD4.step == pEbb4.step False ''' if other.octave is None or self.octave is None: return (other.ps - self.ps) % 12 == 0 else: # if pitch spaces are equal, these are enharmonics return other.ps == self.ps
# a cache so that interval objects can be reused _transpositionIntervals: dict[t.Literal['d2', '-d2'], interval.Interval] = {} @overload def _getEnharmonicHelper(self, inPlace: t.Literal[True], up: bool) -> None: ... @overload def _getEnharmonicHelper(self, inPlace: t.Literal[False], up: bool) -> t.Self: ... def _getEnharmonicHelper(self, inPlace: bool, up: bool) -> t.Self|None: ''' abstracts the code from `getHigherEnharmonic` and `getLowerEnharmonic` ''' intervalString: t.Literal['d2', '-d2'] = 'd2' if not up: intervalString = '-d2' if intervalString not in self._transpositionIntervals: self._transpositionIntervals[intervalString] = interval.Interval(intervalString) intervalObj = self._transpositionIntervals[intervalString] octaveStored = self.octave # may be None p = intervalObj.transposePitch(self, maxAccidental=None) if not inPlace: if octaveStored is None: p.octave = None return p else: self.step = p.step self.accidental = p.accidental if p.microtone is not None: self.microtone = p.microtone if octaveStored is None: self.octave = None else: self.octave = p.octave return None @overload def getHigherEnharmonic(self, *, inPlace: t.Literal[False]) -> t.Self: return self @overload def getHigherEnharmonic(self, *, inPlace: t.Literal[True]) -> None: return None
[docs] def getHigherEnharmonic(self, *, inPlace: bool = False) -> t.Self|None: ''' Returns an enharmonic `Pitch` object that is a higher enharmonic. That is, the `Pitch` a diminished-second above the current `Pitch`. >>> p1 = pitch.Pitch('C#3') >>> p2 = p1.getHigherEnharmonic() >>> print(p2) D-3 We can also set it in place (in which case it returns None): >>> p1 = pitch.Pitch('C#3') >>> p1.getHigherEnharmonic(inPlace=True) >>> print(p1) D-3 The method even works for certain CRAZY enharmonics >>> p3 = pitch.Pitch('D--3') >>> p4 = p3.getHigherEnharmonic() >>> print(p4) E----3 But not for things that are just utterly insane: >>> p4.getHigherEnharmonic() Traceback (most recent call last): music21.pitch.AccidentalException: -5 is not a supported accidental type Note that half accidentals (~ = half-sharp, ` = half-flat) get converted to microtones: >>> pHalfSharp = pitch.Pitch('D~4') >>> p3QuartersFlat = pHalfSharp.getHigherEnharmonic() >>> print(p3QuartersFlat) E-4(-50c) OMIT_FROM_DOCS (Same thing if done in place; prior bug) >>> pHalfSharp = pitch.Pitch('D~4') >>> pHalfSharp.getHigherEnharmonic(inPlace=True) >>> print(pHalfSharp) E-4(-50c) ''' # sigh: mypy if inPlace: self._getEnharmonicHelper(inPlace=True, up=True) return None else: return self._getEnharmonicHelper(inPlace=False, up=True)
@overload def getLowerEnharmonic(self, *, inPlace: t.Literal[False]) -> t.Self: return self @overload def getLowerEnharmonic(self, *, inPlace: t.Literal[True]) -> None: return None
[docs] def getLowerEnharmonic(self, *, inPlace: bool = False) -> t.Self|None: ''' returns a Pitch enharmonic that is a diminished second below the current note If `inPlace` is set to true, changes the current Pitch and returns None. >>> p1 = pitch.Pitch('E-') >>> p2 = p1.getLowerEnharmonic() >>> print(p2) D# The lower enharmonic can have a different octave than the original. >>> p1 = pitch.Pitch('C-3') >>> p2 = p1.getLowerEnharmonic() >>> print(p2) B2 >>> p1 = pitch.Pitch('C#3') >>> p1.getLowerEnharmonic(inPlace=True) >>> print(p1) B##2 ''' # sigh: mypy if inPlace: self._getEnharmonicHelper(inPlace=True, up=False) return None else: return self._getEnharmonicHelper(inPlace=False, up=False)
[docs] def simplifyEnharmonic( self, *, inPlace: bool = False, mostCommon: bool = False ) -> t.Self|None: ''' Returns a new Pitch (or sets the current one if inPlace is True) that is either the same as the current pitch or has fewer sharps or flats if possible. For instance, E# returns F, while A# remains A# (i.e., does not take into account that B- is more common than A#). Useful to call if you ever have an algorithm that might take your piece far into the realm of double or triple flats or sharps. If mostCommon is set to True, then the most commonly used enharmonic spelling is chosen (that is, the one that appears first in key signatures as you move away from C on the circle of fifths). Thus, G-flat becomes F#, A# becomes B-flat, D# becomes E-flat, D-flat becomes C#, G# and A-flat are left alone. >>> p1 = pitch.Pitch('B#5') >>> p1.simplifyEnharmonic().nameWithOctave 'C6' >>> p2 = pitch.Pitch('A#2') >>> p2.simplifyEnharmonic(inPlace=True) >>> p2 <music21.pitch.Pitch A#2> >>> p3 = pitch.Pitch('E--3') >>> p4 = p3.transpose(interval.Interval('-A5')) >>> p4.simplifyEnharmonic() <music21.pitch.Pitch F#2> Setting `mostCommon` = `True` simplifies enharmonics even further. >>> pList = [pitch.Pitch('A#4'), pitch.Pitch('B-4'), ... pitch.Pitch('G-4'), pitch.Pitch('F#4')] >>> [str(p.simplifyEnharmonic(mostCommon=True)) for p in pList] ['B-4', 'B-4', 'F#4', 'F#4'] Note that pitches with implicit octaves retain their implicit octaves. This might change the pitch space for B#s and C-s. >>> pList = [pitch.Pitch('B'), pitch.Pitch('C#'), pitch.Pitch('G'), pitch.Pitch('A--')] >>> [str(p.simplifyEnharmonic()) for p in pList] ['B', 'C#', 'G', 'G'] >>> pList = [pitch.Pitch('C-'), pitch.Pitch('B#')] >>> [p.ps for p in pList] [59.0, 72.0] >>> [p.simplifyEnharmonic().ps for p in pList] [71.0, 60.0] ''' if inPlace: returnObj = self else: returnObj = copy.deepcopy(self) if returnObj.accidental is not None: if (abs(returnObj.accidental.alter) < 2.0 and returnObj.name not in ('E#', 'B#', 'C-', 'F-')): pass else: # by resetting the pitch space value, we will get a simpler # enharmonic spelling saveOctave = self.octave returnObj.ps = self.ps if saveOctave is None: returnObj.octave = None if mostCommon: if returnObj.name == 'D#': returnObj.step = 'E' returnObj.accidental = Accidental('flat') elif returnObj.name == 'A#': returnObj.step = 'B' returnObj.accidental = Accidental('flat') elif returnObj.name == 'G-': returnObj.step = 'F' returnObj.accidental = Accidental('sharp') elif returnObj.name == 'D-': returnObj.step = 'C' returnObj.accidental = Accidental('sharp') if inPlace: return None else: return returnObj
[docs] def getEnharmonic(self, *, inPlace: bool = False) -> t.Self|None: # noinspection GrazieInspection ''' Returns a new Pitch that is the (or at least an) enharmonic equivalent of this Pitch. Can be thought of as flipEnharmonic or something like that. N.B.: n1.name == getEnharmonic(getEnharmonic(n1)).name is not necessarily true. For instance: getEnharmonic(E##) => F# getEnharmonic(F#) => G- getEnharmonic(A--) => G getEnharmonic(G) => F## However, for all cases not involving double sharps or flats (and even many that do), getEnharmonic(getEnharmonic(n)) = n For the most ambiguous cases, it's good to know that these are the enharmonics: C <-> B#, D <-> C##, E <-> F-; F <-> E#, G <-> F##, A <-> B--, B <-> C- However, isEnharmonic() for A## and B certainly returns True. >>> p = pitch.Pitch('d#') >>> print(p.getEnharmonic()) E- >>> p = pitch.Pitch('e-8') >>> print(p.getEnharmonic()) D#8 Other tests: >>> print(pitch.Pitch('c-3').getEnharmonic()) B2 >>> print(pitch.Pitch('e#2').getEnharmonic()) F2 >>> print(pitch.Pitch('f#2').getEnharmonic()) G-2 >>> print(pitch.Pitch('c##5').getEnharmonic()) D5 >>> print(pitch.Pitch('g3').getEnharmonic()) F##3 >>> print(pitch.Pitch('B7').getEnharmonic()) C-8 Octaveless Pitches remain octaveless: >>> p = pitch.Pitch('a-') >>> p.getEnharmonic() <music21.pitch.Pitch G#> >>> p = pitch.Pitch('B#') >>> p.getEnharmonic() <music21.pitch.Pitch C> Works with half-sharps, but converts them to microtones: >>> dHalfSharp = pitch.Pitch('D~') >>> print(dHalfSharp.getEnharmonic()) E-(-50c) ''' if inPlace: post = self else: post = copy.deepcopy(self) if post.accidental is not None: if post.accidental.alter > 0: # we have a sharp, need to get the equivalent flat post.getHigherEnharmonic(inPlace=True) elif post.accidental.alter < 0: post.getLowerEnharmonic(inPlace=True) else: # assume some direction, perhaps using a dictionary if self.step in ('C', 'D', 'G'): post.getLowerEnharmonic(inPlace=True) else: post.getHigherEnharmonic(inPlace=True) else: if self.step in ('C', 'D', 'G'): post.getLowerEnharmonic(inPlace=True) else: post.getHigherEnharmonic(inPlace=True) if inPlace: self.informClient() return None else: return post
[docs] def informClient(self) -> None: ''' If this pitch is attached to a note, then let it know that it has changed. ''' if self._client is not None: self._client.pitchChanged() # pylint: disable=no-member
[docs] def getAllCommonEnharmonics(self, alterLimit: int = 2) -> list[t.Self]: ''' Return all common unique enharmonics for a pitch, or those that do not involve more than two accidentals. >>> p = pitch.Pitch('c#3') >>> p.getAllCommonEnharmonics() [<music21.pitch.Pitch D-3>, <music21.pitch.Pitch B##2>] "Higher" enharmonics are listed before "Lower": >>> p = pitch.Pitch('G4') >>> p.getAllCommonEnharmonics() [<music21.pitch.Pitch A--4>, <music21.pitch.Pitch F##4>] By setting `alterLimit` to a higher or lower number we can limit the maximum number of notes to return: >>> p = pitch.Pitch('G-6') >>> p.getAllCommonEnharmonics(alterLimit=1) [<music21.pitch.Pitch F#6>] If you set `alterLimit` to 3 or 4, you're stretching the name of the method; some of these are certainly not *common* enharmonics: >>> p = pitch.Pitch('G-6') >>> enharmonics = p.getAllCommonEnharmonics(alterLimit=3) >>> [str(enh) for enh in enharmonics] ['A---6', 'F#6', 'E##6'] Music21 does not support accidentals beyond quadruple sharp/flat, so `alterLimit` = 4 is the most you can use. (Thank goodness!) ''' post: list[t.Self] = [] c = self.simplifyEnharmonic(inPlace=False) if c is None: # pragma: no follow return post # not going to happen if c.name != self.name: post.append(c) # iterative scan upward c = self while True: try: c = c.getHigherEnharmonic(inPlace=False) except AccidentalException: break # ran out of accidentals if c is None: break if c.accidental is not None: if abs(c.accidental.alter) > alterLimit: break if c not in post: post.append(c) else: # we are looping break # iterative scan downward c = self while True: try: c = c.getLowerEnharmonic(inPlace=False) except AccidentalException: break # ran out of accidentals if c is None: break if c.accidental is not None: if abs(c.accidental.alter) > alterLimit: break if c not in post: post.append(c) else: # we are looping break return post
# -------------------------------------------------------------------------- @property def diatonicNoteNum(self) -> int: ''' Returns (or takes) an integer that uniquely identifies the diatonic version of a note, that is ignoring accidentals. The number returned is the diatonic interval above C0 (the lowest C on a Bösendorfer Imperial Grand), so G0 = 5, C1 = 8, etc. Numbers can be negative for very low notes. C4 (middleC) = 29, C#4 = 29, C##4 = 29, D-4 = 30, D4 = 30, etc. >>> c = pitch.Pitch('c4') >>> c.diatonicNoteNum 29 Unlike MIDI numbers (or `.ps`), C and C# has the same `diatonicNoteNum`: >>> c = pitch.Pitch('c#4') >>> c.diatonicNoteNum 29 But D-double-flat has a different `diatonicNoteNum` than C. >>> d = pitch.Pitch('d--4') >>> d.accidental.name 'double-flat' >>> d.diatonicNoteNum 30 >>> lowC = pitch.Pitch('c1') >>> lowC.diatonicNoteNum 8 >>> b = pitch.Pitch() >>> b.step = 'B' >>> b.octave = -1 >>> b.diatonicNoteNum 0 An `implicitOctave` of 4 is used if octave is not set: >>> c = pitch.Pitch('C') >>> c.diatonicNoteNum 29 `diatonicNoteNum` can also be set. Changing it does not change the Accidental associated with the `Pitch`. >>> lowDSharp = pitch.Pitch('C#7') # start high !!! >>> lowDSharp.diatonicNoteNum = 9 # move low >>> lowDSharp.octave 1 >>> lowDSharp.name 'D#' Negative diatonicNoteNums are possible, in case, like John Luther Adams, you want to notate the sounds of sub-sonic Earth rumblings. >>> lowLowA = pitch.Pitch('A') >>> lowLowA.octave = -1 >>> lowLowA.diatonicNoteNum -1 >>> lowLowLowD = pitch.Pitch('D') >>> lowLowLowD.octave = -3 >>> lowLowLowD.diatonicNoteNum -19 ''' return STEP_TO_DNN_OFFSET[self.step] + 1 + (7 * self.implicitOctave) @diatonicNoteNum.setter def diatonicNoteNum(self, newNum: int) -> None: octave = int((newNum - 1) / 7) noteNameNum = newNum - 1 - (7 * octave) pitchList: tuple[StepName, ...] = ('C', 'D', 'E', 'F', 'G', 'A', 'B') noteName: StepName = pitchList[noteNameNum] self.octave = octave self.step = noteName @overload def transpose( self, value: interval.IntervalBase|str|int, *, inPlace: t.Literal[True] ) -> None: ... @overload def transpose( self, value: interval.IntervalBase|str|int, *, inPlace: t.Literal[False] = False ) -> t.Self: ...
[docs] def transpose( self, value: interval.IntervalBase|str|int, *, inPlace: bool = False ) -> t.Self|None: ''' Transpose the pitch by the user-provided value. If the value is an integer, the transposition is treated in half steps. If the value is a string, any Interval string specification can be provided. Alternatively, a :class:`music21.interval.Interval` object can be supplied. >>> aPitch = pitch.Pitch('g4') >>> bPitch = aPitch.transpose('m3') >>> bPitch <music21.pitch.Pitch B-4> >>> cPitch = bPitch.transpose(interval.GenericInterval(2)) >>> cPitch <music21.pitch.Pitch C-5> An interval object can also be a certain number of semitones, in which case, the spelling of the resulting note (sharp or flat, etc.) is up to the system to choose. >>> aInterval = interval.Interval(-6) >>> bPitch = aPitch.transpose(aInterval) >>> bPitch <music21.pitch.Pitch C#4> Transpose fFlat down 5 semitones -- sort of like a Perfect 4th, but should be respelled: >>> fFlat = pitch.Pitch('F-4') >>> newPitch = fFlat.transpose(-5) >>> newPitch <music21.pitch.Pitch B3> >>> aPitch <music21.pitch.Pitch G4> >>> aPitch.transpose(aInterval, inPlace=True) >>> aPitch <music21.pitch.Pitch C#4> Implicit octaves remain implicit: >>> anyGSharp = pitch.Pitch('G#') >>> print(anyGSharp.transpose('P8')) G# >>> print(anyGSharp.transpose('P5')) D# If the accidental of a pitch is chosen by music21, not given by the user, then after transposing, music21 will simplify the spelling again: >>> pc6 = pitch.Pitch(6) >>> pc6 <music21.pitch.Pitch F#> >>> pc6.spellingIsInferred True >>> pc6.transpose('-m2') <music21.pitch.Pitch F> OMIT_FROM_DOCS Test to make sure that extreme ranges work >>> dPitch = pitch.Pitch('D2') >>> lowC = dPitch.transpose('m-23') >>> lowC <music21.pitch.Pitch C#-1> >>> otherPitch = pitch.Pitch('D2') >>> otherPitch.transpose('m-23', inPlace=True) >>> print(otherPitch) C#-1 Test an issue with inPlace not setting spellingIsInferred >>> pc6.transpose(10, inPlace=True) >>> pc6.spellingIsInferred True Test an issue with inPlace not setting microtone. >>> flatAndAHalf = pitch.Accidental('one-and-a-half-flat') >>> dFlatAndAHalf = pitch.Pitch('D2') >>> dFlatAndAHalf.accidental = flatAndAHalf >>> dPitch = pitch.Pitch('D2') >>> intv = interval.Interval(dFlatAndAHalf, dPitch) >>> dPitch.transpose(intv, inPlace=True) >>> dPitch <music21.pitch.Pitch D#2(+50c)> ''' # environLocal.printDebug(['Pitch.transpose()', value]) if isinstance(value, interval.IntervalBase): intervalObj = value else: # try to process intervalObj = interval.Interval(value) p = intervalObj.transposePitch(self) if not isinstance(value, int): p.spellingIsInferred = self.spellingIsInferred if p.spellingIsInferred is True: p.simplifyEnharmonic(inPlace=True, mostCommon=True) if not inPlace: return p else: # can setName with nameWithOctave to recreate all essential # pitch attributes # NOTE: in some cases this may not return exactly the proper config self.name = p.name if self.octave is not None: self.octave = p.octave # manually copy accidental object self.accidental = p.accidental # set fundamental self.fundamental = p.fundamental self.spellingIsInferred = p.spellingIsInferred # deepcopy _microtone if present (not thru microtone property to detect None) if p._microtone is not None: self._microtone = copy.deepcopy(p._microtone) return None
# -------------------------------------------------------------------------- # utilities for pitch object manipulation
[docs] def transposeBelowTarget( self, target: Pitch, *, minimize: bool = False, inPlace: bool = False ) -> t.Self|None: # noinspection PyShadowingNames ''' Given a source Pitch, shift it down some number of octaves until it is below the target. If `minimize` is True, a pitch below the target will move up to the nearest octave. >>> higherG = pitch.Pitch('G5') >>> lowerG = higherG.transposeBelowTarget(pitch.Pitch('C#4')) >>> lowerG <music21.pitch.Pitch G3> >>> higherG <music21.pitch.Pitch G5> To change the pitch itself, set inPlace to True: >>> p = pitch.Pitch('G5') >>> p.transposeBelowTarget(pitch.Pitch('C#4'), inPlace=True) >>> p <music21.pitch.Pitch G3> If already below the target, make no change: >>> pitch.Pitch('G#3').transposeBelowTarget(pitch.Pitch('C#6')) <music21.pitch.Pitch G#3> Below target includes being the same as the target >>> pitch.Pitch('g#8').transposeBelowTarget(pitch.Pitch('g#1')) <music21.pitch.Pitch G#1> This does nothing because it is already low enough: >>> pitch.Pitch('g#2').transposeBelowTarget(pitch.Pitch('f#8')) <music21.pitch.Pitch G#2> But with minimize=True, it will actually RAISE the pitch so that it is the closest pitch to the target >>> target = pitch.Pitch('f#8') >>> pitch.Pitch('g#2').transposeBelowTarget(target, minimize=True) <music21.pitch.Pitch G#7> >>> pitch.Pitch('f#2').transposeBelowTarget(target, minimize=True) <music21.pitch.Pitch F#8> If the original pitch is octaveless, raises a PitchException: >>> pitch.Pitch('d').transposeBelowTarget(pitch.Pitch('e2'), minimize=True) Traceback (most recent call last): music21.pitch.PitchException: Cannot call transposeBelowTarget with an octaveless Pitch. If the target pitch is octaveless, assumes it has the default of octave 4. (The reason for this asymmetry is that the target pitch is never altered while the original pitch (or its copy) is). >>> pitch.Pitch('f4').transposeBelowTarget(pitch.Pitch('e'), minimize=True) <music21.pitch.Pitch F3> * Changed in v3: default for inPlace=False. ''' if self.octave is None: raise PitchException('Cannot call transposeBelowTarget with an octaveless Pitch.') if inPlace: src = self else: src = copy.deepcopy(self) assert src.octave is not None while True: # ref 20, min 10, lower ref. # ref 5, min 10, do not lower. if src.ps - target.ps <= 0: break # lower one octave src.octave -= 1 # case where self is below target and minimize is True if minimize: while True: if target.ps - src.ps < 12: break else: src.octave += 1 if not inPlace: return src return None
[docs] def transposeAboveTarget(self, target: Pitch, *, minimize: bool = False, inPlace: bool = False) -> t.Self|None: ''' Given a source Pitch, shift it up octaves until it is above the target. If `minimize` is True, a pitch above the target will move down to the nearest octave. >>> pitch.Pitch('d2').transposeAboveTarget(pitch.Pitch('e4')) <music21.pitch.Pitch D5> To change the pitch itself, set inPlace to True: >>> p = pitch.Pitch('d2') >>> p.transposeAboveTarget(pitch.Pitch('e4'), inPlace=True) >>> p <music21.pitch.Pitch D5> If already above the target, make no change: >>> pitch.Pitch('d7').transposeAboveTarget(pitch.Pitch('e2')) <music21.pitch.Pitch D7> Accept the same pitch: >>> pitch.Pitch('d2').transposeAboveTarget(pitch.Pitch('d8')) <music21.pitch.Pitch D8> If minimize is True, we go the closest position: >>> pitch.Pitch('d#8').transposeAboveTarget(pitch.Pitch('d2'), minimize=True) <music21.pitch.Pitch D#2> >>> pitch.Pitch('d7').transposeAboveTarget(pitch.Pitch('e2'), minimize=True) <music21.pitch.Pitch D3> >>> pitch.Pitch('d0').transposeAboveTarget(pitch.Pitch('e2'), minimize=True) <music21.pitch.Pitch D3> If the original pitch is octaveless, raises a PitchException: >>> pitch.Pitch('d').transposeAboveTarget(pitch.Pitch('e2'), minimize=True) Traceback (most recent call last): music21.pitch.PitchException: Cannot call transposeAboveTarget with an octaveless Pitch. If the target pitch is octaveless, assumes it has the default of octave 4. (The reason for this asymmetry is that the target pitch is never altered while the original pitch (or its copy) is). >>> pitch.Pitch('d4').transposeAboveTarget(pitch.Pitch('e'), minimize=True) <music21.pitch.Pitch D5> * Changed in v3: default for inPlace=False. ''' if self.octave is None: raise PitchException('Cannot call transposeAboveTarget with an octaveless Pitch.') if inPlace: src = self else: src = copy.deepcopy(self) assert src.octave is not None # case where self is below target while True: # ref 20, max 10, do not raise ref. # ref 5, max 10, raise ref to above max. if src.ps - target.ps >= 0: break # raise one octave src.octave += 1 # case where self is above target and minimize is True if minimize: while True: if src.ps - target.ps < 12: break else: src.octave -= 1 if not inPlace: return src return None
# -------------------------------------------------------------------------- def _nameInKeySignature(self, alteredPitches: list[Pitch]) -> bool: ''' Determine if this pitch is in the collection of supplied altered pitches, derived from a KeySignature object. >>> ks = key.KeySignature(2) >>> altered = ks.alteredPitches >>> altered [<music21.pitch.Pitch F#>, <music21.pitch.Pitch C#>] >>> cs = pitch.Pitch('c#') >>> gs = pitch.Pitch('g#') >>> cs._nameInKeySignature(altered) True >>> gs._nameInKeySignature(altered) False Note that False is returned regardless of the name if the key signature has no entry for the pitch: >>> pitch.Pitch('G')._nameInKeySignature(altered) False Other accidentals for pitches whose `.step` is in the key signature also do not match: >>> f = pitch.Pitch('F') >>> f._nameInKeySignature(altered) False >>> f.accidental = pitch.Accidental('natural') >>> f._nameInKeySignature(altered) False >>> pitch.Pitch('F##')._nameInKeySignature(altered) False >>> pitch.Pitch('C-')._nameInKeySignature(altered) False ''' selfAccidental = self.accidental if selfAccidental is None: return False for p in alteredPitches: # all are altered tones, must have acc pAccidental = p.accidental if p.step == self.step and pAccidental is not None: # A# to A or A# to A-, etc if pAccidental.name == selfAccidental.name: return True return False def _stepInKeySignature(self, alteredPitches: list[Pitch]) -> bool: ''' Determine if this pitch is in the collection of supplied altered pitches, derived from a KeySignature object. >>> a = pitch.Pitch('c') >>> b = pitch.Pitch('g') >>> ks = key.KeySignature(2) >>> a._stepInKeySignature(ks.alteredPitches) True >>> b._stepInKeySignature(ks.alteredPitches) False ''' for p in alteredPitches: # all are altered tones, must have acc if p.step == self.step: # A# to A or A# to A-, etc return True return False
[docs] def updateAccidentalDisplay( self, *, pitchPast: list[Pitch]|None = None, pitchPastMeasure: list[Pitch]|None = None, otherSimultaneousPitches: list[Pitch]|None = None, alteredPitches: list[Pitch]|None = None, cautionaryPitchClass: bool = True, cautionaryAll: bool = False, overrideStatus: bool = False, cautionaryNotImmediateRepeat: bool = True, lastNoteWasTied: bool = False, ) -> None: ''' Given an ordered list of Pitch objects in `pitchPast`, determine if this pitch's Accidental object needs to be created or updated with a natural or other cautionary accidental. Changes to this Pitch object's Accidental object are made in-place. `pitchPast` is a list of pitches preceding this pitch in the same measure. If None, a new list will be made. `pitchPastMeasure` is a list of pitches preceding this pitch but in a previous measure. If None, a new list will be made. `otherSimultaneousPitches` is a list of other pitches in this simultaneity, for use when `cautionaryPitchClass` is True. The `alteredPitches` list supplies pitches from a :class:`~music21.key.KeySignature` object using the :attr:`~music21.key.KeySignature.alteredPitches` property. If None, a new list will be made. If `cautionaryPitchClass` is True, comparisons to past accidentals are made regardless of register. That is, if a past sharp is found two octaves above a present natural, a natural sign is still displayed. Note that this has nothing to do with whether a sharp (not in the key signature) is found in a different octave from the same note in a different octave. The sharp must always be displayed. Notes with displayType = 'if-absolutely-necessary' will ignore the True setting. If `overrideStatus` is True, this method will ignore any current `displayStatus` setting found on the Accidental. By default, this does not happen. If `displayStatus` is set to None, the Accidental's `displayStatus` is set. If `cautionaryNotImmediateRepeat` is True, cautionary accidentals will be displayed for an altered pitch even if that pitch had already been displayed as altered (unless it's an immediate repetition). Notes with displayType = 'if-absolutely-necessary' will ignore the True setting. If `lastNoteWasTied` is True then this note will be treated as immediately following a tie. >>> a = pitch.Pitch('a') >>> past = [pitch.Pitch('a#'), pitch.Pitch('c#'), pitch.Pitch('c')] >>> a.updateAccidentalDisplay(pitchPast=past, cautionaryAll=True) >>> a.accidental, a.accidental.displayStatus (<music21.pitch.Accidental natural>, True) >>> b = pitch.Pitch('a') >>> past = [pitch.Pitch('a#'), pitch.Pitch('c#'), pitch.Pitch('c')] >>> b.updateAccidentalDisplay(pitchPast=past) # should add a natural >>> b.accidental, b.accidental.displayStatus (<music21.pitch.Accidental natural>, True) In this example, the method will not add a natural because the match is pitchSpace and our octave is different. >>> a4 = pitch.Pitch('a4') >>> past = [pitch.Pitch('a#3'), pitch.Pitch('c#'), pitch.Pitch('c')] >>> a4.updateAccidentalDisplay(pitchPast=past, cautionaryPitchClass=False) >>> a4.accidental is None True v8 -- made keyword-only and added `otherSimultaneousPitches`. ''' # N.B. -- this is a very complex method # do not alter it without significant testing. acc = self.accidental def none_to_natural() -> None: nonlocal acc if acc is None: acc = Accidental('natural') self.accidental = acc def set_displayStatus(newDisplayStatus: bool) -> None: none_to_natural() assert acc is not None # mypy acc.displayStatus = newDisplayStatus if pitchPast is None: pitchPast = [] if pitchPastMeasure is None: pitchPastMeasure = [] if alteredPitches is None: alteredPitches = [] # should we display accidental if no previous accidentals have been displayed # i.e. if it's the first instance of an accidental after a tie displayAccidentalIfNoPreviousAccidentals = False pitchPastAll = pitchPastMeasure + pitchPast if overrideStatus is False: # go with what we have defined if acc is None: pass # no accidental defined; we may need to add one elif acc.displayStatus is None: # not set; need to set # configure based on displayStatus alone, continue w/ normal pass elif acc.displayStatus in (True, False): return # exit: already set, do not override # remove the never possibilities. if acc is not None and acc.displayType == 'never': acc.displayStatus = False return if lastNoteWasTied is True: if acc is not None: if acc.displayType != 'even-tied': acc.displayStatus = False else: acc.displayStatus = True return else: return # exit: nothing more to do if ( otherSimultaneousPitches and cautionaryPitchClass and any(pSimult.step == self.step and pSimult.pitchClass != self.pitchClass for pSimult in otherSimultaneousPitches) ): set_displayStatus(True) return # no pitches in the past list if not pitchPastAll: # if we have no past, we show the accidental if this pitch name # is not in the alteredPitches list, or for naturals: if the # step is IN the altered pitches if (acc is not None and (overrideStatus or acc.displayStatus in (False, None))): if acc.name == 'natural': acc.displayStatus = self._stepInKeySignature(alteredPitches) else: acc.displayStatus = not self._nameInKeySignature(alteredPitches) # in case display set to True and in alteredPitches, makeFalse elif (acc is not None and acc.displayStatus is True and self._nameInKeySignature(alteredPitches)): acc.displayStatus = False # if we have no accidental or a natural accidental, # but our step matches a step in the key sig # we need to show or add or an accidental. elif ((acc is None or acc.name == 'natural') and self._stepInKeySignature(alteredPitches)): set_displayStatus(True) return # do not search past # pitches in the past list (this measure): # first search if the last pitch in our measure # with the same step and at this octave contradicts this pitch. # if so, then no matter what we need an accidental. for thisPPast in reversed(pitchPast): # check previous in measure. if thisPPast.step == self.step and thisPPast.octave == self.octave: # conflicting alters, need accidental and return if thisPPast.name != self.name: set_displayStatus(True) return else: # names are the same, skip this line of questioning break # nope, no conflicting accidentals at this name and octave in the past # here tied and always are treated the same; we assume that # making ties sets the displayStatus, and thus we would not be # overriding that display status here if (cautionaryAll is True or (acc is not None and acc.displayType in ('even-tied', 'always'))): # show all accidentals, even if past encountered set_displayStatus(True) return # do not search past # store if a match was found and display set from past pitches setFromPitchPast = False if (cautionaryPitchClass is True and (acc is None or acc.displayType != 'if-absolutely-necessary')): # warn no matter what octave; thus create new pSelf without octave pSelf = Pitch(self.name) pSelf.accidental = acc else: pSelf = self # where does the line divide between in measure and out of measure? outOfMeasureLength = len(pitchPastMeasure) # need to step through pitchPast in reverse # comparing this pitch to the past pitches; if we find a match # in terms of name, then decide what to do # are we only comparing a list of past pitches all of # which are the same as this one and in the same measure? # if so, set continuousRepeatsInMeasure to True # else, set to False continuousRepeatsInMeasure: bool # figure out if this pitch is in the measure (pPastInMeasure = True) # or not. Walk backwards for i in range(len(pitchPastAll) - 1, -1, -1): # is the past pitch in the measure or out of the measure? pPastInMeasure: bool if i < outOfMeasureLength: pPastInMeasure = False continuousRepeatsInMeasure = False if acc is not None and acc.displayType == 'if-absolutely-necessary': # pitches outside the measure do not affect # "if-absolutely-necessary" accidentals break else: pPastInMeasure = True for j in range(i, len(pitchPastAll)): # this could be optimized to only walk to last space, in case # of enormous measure of all same, leading to O(n^2) operation # but fine for the size of n we see. # do we have a continuous stream of the same note leading up to this one if pitchPastAll[j].nameWithOctave != self.nameWithOctave: continuousRepeatsInMeasure = False break else: continuousRepeatsInMeasure = True # if the pitch is the first of a measure, has an accidental, # it is not an altered key signature pitch, # and it is not a natural, it should always be set to display if (pPastInMeasure is False and acc is not None and not self._nameInKeySignature(alteredPitches)): acc.displayStatus = True return # do not search past # create Pitch objects for comparison; remove pitch space # information if we are only doing a pitch class comparison if (cautionaryPitchClass is True and (acc is None or acc.displayType != 'if-absolutely-necessary')): # no octave; create new without oct pPast = Pitch(pitchPastAll[i].name) # must manually assign reference to the same accidentals # as name alone will not transfer display status pPast.accidental = pitchPastAll[i].accidental else: # cautionary in terms of pitch space; must match exact pPast = pitchPastAll[i] # if we do not match steps (A and A#), we can continue if pPast.step != pSelf.step: continue # store whether these match at the same octave; needed for some # comparisons even if not matching pitchSpace if self.octave == pitchPastAll[i].octave: octaveMatch = True else: octaveMatch = False # repeats of the same pitch immediately following, in the same measure # where one previous pitch has displayStatus = True; don't display if (continuousRepeatsInMeasure is True and pPast.accidental is not None and pPast.accidental.displayStatus is True): # only needed if one has a natural and this does not if acc is not None: acc.displayStatus = False return # repeats of the same accidental immediately following # if An to An or A# to A#: do not need unless repeats requested, # regardless of if 'unless-repeated' is set, this will catch # a repeated case elif (continuousRepeatsInMeasure is True and pPast.accidental is not None and pSelf.accidental is not None and acc is not None # redundant with pSelf -- for mypy and pPast.accidental.name == pSelf.accidental.name): # BUG! what about C#4 C#5 C#4 C#5 -- last C#4 and C#5 # should not show accidental if cautionaryNotImmediateRepeat is False # if not in the same octave, and not in the key sig, do show accidental if (self._nameInKeySignature(alteredPitches) is False and (octaveMatch is False or pPast.accidental.displayStatus is False)): displayAccidentalIfNoPreviousAccidentals = True continue else: acc.displayStatus = False setFromPitchPast = True break # if An to A: do not need another natural # yet, if we are against the key sig, then we need another natural if in another octave elif (pPast.accidental is not None and pPast.accidental.name == 'natural' and (pSelf.accidental is None or pSelf.accidental.name == 'natural')): if continuousRepeatsInMeasure is True: # an immediate repeat; do not show # unless we are altering the key signature and in # a different register if (self._stepInKeySignature(alteredPitches) is True and octaveMatch is False): set_displayStatus(True) else: if acc is not None: acc.displayStatus = False # if we match the step in a key signature, and we want # cautionary not immediate repeated elif (self._stepInKeySignature(alteredPitches) is True and cautionaryNotImmediateRepeat is True): set_displayStatus(True) # cautionaryNotImmediateRepeat is False # but the previous note was not in this measure, # so do the previous step anyhow elif (self._stepInKeySignature(alteredPitches) is True and cautionaryNotImmediateRepeat is False and pPastInMeasure is False): set_displayStatus(True) # other cases: already natural in past usage, do not need # natural again (and not in key sig) else: if acc is not None: acc.displayStatus = False setFromPitchPast = True break # if A# to A, or A- to A, but not A# to A# # we use step and octave though not necessarily a ps comparison elif (pPast.accidental is not None and pPast.name != pSelf.name and pPast.accidental.name != 'natural' and (pSelf.accidental is None or pSelf.accidental.displayStatus is False)): if octaveMatch is False and cautionaryPitchClass is False: continue if (octaveMatch is False and acc is not None and acc.displayType == 'if-absolutely-necessary'): continue set_displayStatus(True) setFromPitchPast = True break # if An or A to A#: need to make sure display is set elif ((pPast.accidental is None or pPast.accidental.name == 'natural') and acc is not None # redundant. for mypy and pSelf.accidental is not None and pSelf.accidental.name != 'natural'): acc.displayStatus = True setFromPitchPast = True break # if A- or An to A#: need to make sure display is set elif (pPast.accidental is not None and pSelf.accidental is not None and pPast.accidental.name != pSelf.accidental.name and acc is not None # redundant. for mypy and (octaveMatch or pSelf.accidental.displayType != 'if-absolutely-necessary')): acc.displayStatus = True setFromPitchPast = True break # going from a natural to an accidental, we should already be # showing the accidental, but just to check # if A to A#, or A to A-, but not A# to A, nor A (implicit) to An (explicit) elif (pPast.accidental is None and pSelf.accidental is not None and acc is not None): # acc -> redundant, for mypy if pSelf.accidental.name == 'natural': acc.displayStatus = self._stepInKeySignature(alteredPitches) else: acc.displayStatus = True # environLocal.printDebug(['match previous no mark']) setFromPitchPast = True break # if A# to A# and not immediately repeated: # default is to show accidental # if cautionaryNotImmediateRepeat is False, will not be shown elif (continuousRepeatsInMeasure is False and pPast.accidental is not None and pSelf.accidental is not None and pPast.accidental.name == pSelf.accidental.name and acc is not None # redundant. for mypy and octaveMatch is True): if (cautionaryNotImmediateRepeat is False and pPast.accidental.displayStatus is not False): # do not show (unless previous note's accidental wasn't displayed # because of a tie or some other reason) # result will be False, do not need to check altered tones acc.displayStatus = False displayAccidentalIfNoPreviousAccidentals = False setFromPitchPast = True break elif pPast.accidental.displayStatus is False: # in case of ties displayAccidentalIfNoPreviousAccidentals = True else: if not self._nameInKeySignature(alteredPitches): acc.displayStatus = True else: acc.displayStatus = False setFromPitchPast = True return else: pass # if we have no previous matches for this pitch and there is # an accidental: show, unless in alteredPitches. # cases of displayAlways and related are matched above if displayAccidentalIfNoPreviousAccidentals is True: # not the first pitch of this nameWithOctave in the measure # but, because of ties, the first to be displayed if self._nameInKeySignature(alteredPitches) is False: set_displayStatus(True) else: if acc is not None: acc.displayStatus = False # displayAccidentalIfNoPreviousAccidentals = False # just to be sure elif not setFromPitchPast and acc is not None: if acc.name == 'natural': acc.displayStatus = self._stepInKeySignature(alteredPitches) else: acc.displayStatus = not self._nameInKeySignature(alteredPitches) # if we have natural that alters the key sig, create a natural elif not setFromPitchPast and acc is None: if self._stepInKeySignature(alteredPitches): set_displayStatus(True)
[docs] def getStringHarmonic(self, chordIn: chord.Chord) -> chord.Chord|t.Literal[False]: # noinspection PyShadowingNames ''' Given a chord, determine whether the chord constitutes a string harmonic and, if so, return a new chord with the proper sounding pitch added. >>> n1 = note.Note('d3') >>> n2 = note.Note('g3') >>> n2.notehead = 'diamond' >>> n2.noteheadFill = False >>> p1 = pitch.Pitch('d3') >>> harmChord = chord.Chord([n1, n2]) >>> harmChord.quarterLength = 1 >>> newChord = p1.getStringHarmonic(harmChord) >>> newChord.quarterLength = 1 >>> pitchList = newChord.pitches >>> pitchList (<music21.pitch.Pitch D3>, <music21.pitch.Pitch G3>, <music21.pitch.Pitch D5>) otherwise returns False ''' # Takes in a chord, finds the interval between the notes from music21 import note from music21 import chord pitchList = chordIn.pitches isStringHarmonic = False if chordIn[1].notehead == 'diamond': isStringHarmonic = True if not isStringHarmonic: return False chordInt = interval.notesToChromatic(pitchList[0], pitchList[1]) if chordInt.intervalClass == 12: soundingPitch = pitchList[0].getHarmonic(2) elif chordInt.intervalClass == 7: soundingPitch = pitchList[0].getHarmonic(3) elif chordInt.intervalClass == 5: soundingPitch = pitchList[0].getHarmonic(4) elif chordInt.intervalClass == 4: soundingPitch = pitchList[0].getHarmonic(5) elif chordInt.intervalClass == 3: soundingPitch = pitchList[0].getHarmonic(6) elif chordInt.intervalClass == 6: soundingPitch = pitchList[0].getHarmonic(7) elif chordInt.intervalClass == 8: soundingPitch = pitchList[0].getHarmonic(8) else: # make this give an error or something soundingPitch = pitchList[0] noteOut = note.Note(soundingPitch.nameWithOctave) noteOut.noteheadParenthesis = True noteOut.noteheadFill = True noteOut.stemDirection = 'noStem' note1 = note.Note(pitchList[0].nameWithOctave) note2 = note.Note(pitchList[1].nameWithOctave) note2.notehead = chordIn[1].notehead # TODO: make note small chordOut = chord.Chord([note1, note2, noteOut]) return chordOut
# ------------------------------------------------------------------------------ # nearly all tests moved to test_pitch.py class Test(unittest.TestCase): def testCopyAndDeepcopy(self) -> None: from music21.test.commonTest import testCopyAll testCopyAll(self, globals()) # define presented order in documentation _DOC_ORDER = [Pitch, Accidental, Microtone] if __name__ == '__main__': import music21 music21.mainTest(Test)