From 3740e3bcb14b840e942e355274072b614b6a3170 Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Sun, 1 May 2022 14:19:30 -1000 Subject: [PATCH 1/2] Add typing to key.py --- .github/workflows/pythonpylint.yml | 2 +- music21/common/types.py | 3 +- music21/key.py | 212 ++++++++++++++++++----------- music21/pitch.py | 32 +++-- music21/scale/__init__.py | 16 ++- 5 files changed, 169 insertions(+), 96 deletions(-) diff --git a/.github/workflows/pythonpylint.yml b/.github/workflows/pythonpylint.yml index 7f702b8868..f42ba7597b 100644 --- a/.github/workflows/pythonpylint.yml +++ b/.github/workflows/pythonpylint.yml @@ -64,4 +64,4 @@ jobs: - name: Type-check certain modules with mypy run: | mypy --follow-imports=silent music21/capella music21/common music21/corpus music21/features music21/figuredBass music21/humdrum music21/ipython21 music21/languageExcerpts music21/lily music21/mei music21/metadata music21/musedata music21/noteworthy music21/omr music21/romanText music21/test music21/vexflow - mypy --follow-imports=silent music21/articulations.py music21/bar.py music21/base.py music21/beam.py music21/clef.py music21/configure.py music21/defaults.py music21/derivation.py music21/duration.py music21/dynamics.py music21/editorial.py music21/environment.py music21/exceptions21.py music21/expressions.py music21/freezeThaw.py music21/harmony.py music21/instrument.py music21/interval.py music21/layout.py music21/percussion.py music21/prebase.py music21/repeat.py music21/roman.py music21/serial.py music21/sieve.py music21/sites.py music21/sorting.py music21/spanner.py music21/style.py music21/tablature.py music21/tempo.py music21/text.py music21/tie.py music21/tinyNotation.py music21/variant.py music21/voiceLeading.py music21/volpiano.py music21/volume.py + mypy --follow-imports=silent music21/articulations.py music21/bar.py music21/base.py music21/beam.py music21/clef.py music21/configure.py music21/defaults.py music21/derivation.py music21/duration.py music21/dynamics.py music21/editorial.py music21/environment.py music21/exceptions21.py music21/expressions.py music21/freezeThaw.py music21/harmony.py music21/instrument.py music21/interval.py music21/key.py music21/layout.py music21/percussion.py music21/prebase.py music21/repeat.py music21/roman.py music21/serial.py music21/sieve.py music21/sites.py music21/sorting.py music21/spanner.py music21/style.py music21/tablature.py music21/tempo.py music21/text.py music21/tie.py music21/tinyNotation.py music21/variant.py music21/voiceLeading.py music21/volpiano.py music21/volume.py diff --git a/music21/common/types.py b/music21/common/types.py index e4781c650d..3df6210bfd 100644 --- a/music21/common/types.py +++ b/music21/common/types.py @@ -9,7 +9,7 @@ # License: BSD, see license.txt # ------------------------------------------------------------------------------ from fractions import Fraction -from typing import Union, TypeVar, TYPE_CHECKING, Iterable, Type +from typing import Union, TypeVar, TYPE_CHECKING, Iterable, Type, Literal from music21.common.enums import OffsetSpecial @@ -23,3 +23,4 @@ StreamType = TypeVar('StreamType', bound='music21.stream.Stream') M21ObjType = TypeVar('M21ObjType', bound='music21.base.Music21Object') ClassListType = Union[str, Iterable[str], Type[M21ObjType], Iterable[Type[M21ObjType]]] +StepName = Literal['C', 'D', 'E', 'F', 'G', 'A', 'B'] diff --git a/music21/key.py b/music21/key.py index cc701bc98c..b9bdea5e09 100644 --- a/music21/key.py +++ b/music21/key.py @@ -6,7 +6,7 @@ # Authors: Michael Scott Asato Cuthbert # Christopher Ariza # -# Copyright: Copyright © 2009, 2010, 2012 Michael Scott Asato Cuthbert and the music21 Project +# Copyright: Copyright © 2009-22 Michael Scott Asato Cuthbert and the music21 Project # License: BSD, see license.txt # ------------------------------------------------------------------------------ ''' @@ -17,10 +17,12 @@ The :class:`~music21.key.Key` object is a fuller representation not just of a key signature but also of the key of a region. ''' +from __future__ import annotations + import copy import re import unittest -from typing import Union, Optional +from typing import Union, Optional, Dict, List, TypeVar, overload, Literal, cast import warnings from music21 import base @@ -32,13 +34,18 @@ from music21 import style from music21.common.decorators import cacheMethod +from music21.common.types import StepName from music21 import environment environLocal = environment.Environment('key') +KeySignatureType = TypeVar('KeySignatureType', bound='KeySignature') +KeyType = TypeVar('KeyType', bound='Key') +TransposeTypes = Union[int, str, interval.Interval, interval.GenericInterval] + # ------------------------------------------------------------------------------ # store a cache of already-found values -_sharpsToPitchCache = {} +_sharpsToPitchCache: Dict[int, pitch.Pitch] = {} def convertKeyStringToMusic21KeyString(textString): @@ -136,7 +143,7 @@ def sharpsToPitch(sharpCount): # store a cache of already-found values # _pitchToSharpsCache = {} -fifthsOrder = ['F', 'C', 'G', 'D', 'A', 'E', 'B'] +fifthsOrder = ('F', 'C', 'G', 'D', 'A', 'E', 'B') modeSharpsAlter = {'major': 0, 'ionian': 0, 'minor': -3, @@ -149,9 +156,11 @@ def sharpsToPitch(sharpCount): } -def pitchToSharps(value, mode=None): +def pitchToSharps(value: Union[str, pitch.Pitch, note.Note], + mode: str = None) -> int: ''' - Given a pitch or :class:`music21.pitch.Pitch` object, + Given a pitch string or :class:`music21.pitch.Pitch` or + :class:`music21.note.Note` object, return the number of sharps found in that mode. The `mode` parameter can be 'major', 'minor', or most @@ -161,7 +170,7 @@ def pitchToSharps(value, mode=None): If `mode` is omitted or not found, the default mode is major. (extra points to anyone who can find the earliest reference to - the Locrian mode in print. David Cohen and I (MSC) have been + the Locrian mode in print. David Cohen and I (MSAC) have been looking for this for years). >>> key.pitchToSharps('c') @@ -202,9 +211,9 @@ def pitchToSharps(value, mode=None): 0 >>> key.pitchToSharps('f#') 6 - >>> key.pitchToSharps('f-') + >>> key.pitchToSharps(note.Note('f-')) -8 - >>> key.pitchToSharps('f--') + >>> key.pitchToSharps(pitch.Pitch('f--')) -15 >>> key.pitchToSharps('f--', 'locrian') -20 @@ -213,28 +222,28 @@ def pitchToSharps(value, mode=None): >>> key.pitchToSharps('a', 'aeolian') 0 - But quarter tones don't work: >>> key.pitchToSharps('C~') Traceback (most recent call last): music21.key.KeyException: Cannot determine sharps for quarter-tone keys! silly! ''' + pitchValue: pitch.Pitch if isinstance(value, str): - value = pitch.Pitch(value) + pitchValue = pitch.Pitch(value) elif isinstance(value, pitch.Pitch): - pass + pitchValue = value elif isinstance(value, note.Note): - value = value.pitch + pitchValue = value.pitch else: - raise KeyException('Cannot get a sharp number from value') + raise KeyException(f'Cannot get a sharp number from value: {value}') # the -1 is because we begin with F not C. - sharps = fifthsOrder.index(value.step) - 1 - if value.accidental is not None: - if value.accidental.isTwelveTone() is False: + sharps = fifthsOrder.index(pitchValue.step) - 1 + if pitchValue.accidental is not None: + if pitchValue.accidental.isTwelveTone() is False: raise KeyException('Cannot determine sharps for quarter-tone keys! silly!') - vaa = int(value.accidental.alter) + vaa = int(pitchValue.accidental.alter) sharps = sharps + 7 * vaa if mode is not None and mode in modeSharpsAlter: @@ -258,9 +267,9 @@ class KeyWarning(Warning): # ------------------------------------------------------------------------------ class KeySignature(base.Music21Object): ''' - A KeySignature object specifies the signature to be used for a piece; it takes - in zero or one arguments. The only argument is an int giving the number of sharps, - or if negative the number of flats. + A KeySignature object specifies the signature to be used for a piece; it normally + takes one argument: an `int` giving the number of sharps, + or, if negative, the number of flats. If you are starting with the name of a key, see the :class:`~music21.key.Key` object. @@ -339,11 +348,8 @@ def __init__(self, sharps: Optional[int] = 0): self._sharps = sharps # need to store a list of pitch objects, used for creating a - # non traditional key - self._alteredPitches = None - - # cache altered pitches - self._alteredPitchesCached = [] + # non-traditional key + self._alteredPitches: Optional[List[pitch.Pitch]] = None self.accidentalsApplyOnlyToOctave = False def __hash__(self): @@ -430,6 +436,7 @@ def asKey(self, mode: Optional[str] = None, tonic: Optional[str] = None): except KeyError as ke: raise KeyException( f'Could not solve for mode from sharps={self.sharps}, tonic={tonic}') from ke + mode = cast(str, mode) mode = mode.lower() if mode not in modeSharpsAlter: raise KeyException(f'Mode {mode} is unknown') @@ -439,9 +446,10 @@ def asKey(self, mode: Optional[str] = None, tonic: Optional[str] = None): return Key(pitchObj.name, mode) - @property + @property # type: ignore @cacheMethod - def alteredPitches(self): + def alteredPitches(self) -> List[pitch.Pitch]: + # unfortunately, mypy cannot deal with @property on decorated methods. # noinspection PyShadowingNames ''' Return or set a list of music21.pitch.Pitch objects that are altered by this @@ -498,7 +506,7 @@ def alteredPitches(self): if self._alteredPitches is not None: return self._alteredPitches - post = [] + post: List[pitch.Pitch] = [] if self.sharps is None: return post @@ -523,11 +531,11 @@ def alteredPitches(self): return post @alteredPitches.setter - def alteredPitches(self, newAlteredPitches): + def alteredPitches(self, newAlteredPitches: List[Union[str, pitch.Pitch, note.Note]]) -> None: self.clearCache() - newList = [] + newList: List[pitch.Pitch] = [] for p in newAlteredPitches: - if not hasattr(p, 'classes'): + if isinstance(p, str): newList.append(pitch.Pitch(p)) elif isinstance(p, pitch.Pitch): newList.append(p) @@ -536,7 +544,7 @@ def alteredPitches(self, newAlteredPitches): self._alteredPitches = newList @property - def isNonTraditional(self): + def isNonTraditional(self) -> bool: ''' Returns bool if this is a non-traditional KeySignature: @@ -560,7 +568,7 @@ def isNonTraditional(self): else: return False - def accidentalByStep(self, step): + def accidentalByStep(self, step: StepName) -> Optional[pitch.Accidental]: ''' Given a step (C, D, E, F, etc.) return the accidental for that note in this key (using the natural minor for minor) @@ -569,7 +577,8 @@ def accidentalByStep(self, step): >>> g = key.KeySignature(1) >>> g.accidentalByStep('F') - >>> g.accidentalByStep('G') + >>> g.accidentalByStep('G') is None + True >>> f = key.KeySignature(-1) >>> bbNote = note.Note('B-5') @@ -641,8 +650,24 @@ def accidentalByStep(self, step): # -------------------------------------------------------------------------- # methods - - def transpose(self, value, *, inPlace=False): + @overload + def transpose(self: KeySignatureType, + value: TransposeTypes, + *, + inPlace: Literal[False] = False) -> KeySignatureType: + return self # astroid 1015 + + @overload + def transpose(self: KeySignatureType, + value: TransposeTypes, + *, + inPlace: Literal[True]) -> None: + return None # astroid 1015 + + def transpose(self: KeySignatureType, + value: TransposeTypes, + *, + inPlace: bool = False) -> Optional[KeySignatureType]: ''' Transpose the KeySignature by the user-provided value. If the value is an integer, the transposition is treated @@ -689,9 +714,10 @@ def transpose(self, value, *, inPlace=False): >>> eFlat ''' - if hasattr(value, 'diatonic'): # its an Interval class + intervalObj: Union[interval.Interval, interval.GenericInterval] + if isinstance(value, interval.Interval): # it is an Interval class intervalObj = value - elif hasattr(value, 'classes') and 'GenericInterval' in value.classes: + elif isinstance(value, interval.GenericInterval): intervalObj = value else: # try to process intervalObj = interval.Interval(value) @@ -748,15 +774,16 @@ def transposePitchFromC(self, p: pitch.Pitch, *, inPlace=False) -> Optional[pitc >>> p4.nameWithOctave 'B--4' - If inPlace is True then nothing is returned and the original pitch is + If inPlace is True then the original pitch is modified. >>> p5 = pitch.Pitch('C5') >>> ks.transposePitchFromC(p5, inPlace=True) + >>> p5.nameWithOctave 'E-5' - New method in v6. + Changed in v8: original pitch returned if inPlace=True ''' transInterval = None transTimes = 0 @@ -767,9 +794,8 @@ def transposePitchFromC(self, p: pitch.Pitch, *, inPlace=False) -> Optional[pitc if self.sharps == 0: if inPlace: - return - else: - return p + return None + return p elif self.sharps < 0: transTimes = abs(self.sharps) transInterval = interval.Interval('P4') @@ -785,6 +811,8 @@ def transposePitchFromC(self, p: pitch.Pitch, *, inPlace=False) -> Optional[pitc if not inPlace: return p + else: + return None def getScale(self, mode='major'): ''' @@ -895,49 +923,50 @@ class Key(KeySignature, scale.DiatonicScale): ''' _sharps = 0 _mode = None + tonic: pitch.Pitch def __init__(self, tonic: Union[str, pitch.Pitch, note.Note] = 'C', mode=None): - if isinstance(tonic, (base.Music21Object, pitch.Pitch)): - if hasattr(tonic, 'name'): - tonic = tonic.name - elif hasattr(tonic, 'pitches') and tonic.pitches: # chord w/ >= 1 pitch - if mode is None: - if tonic.isMinorTriad() is True: - mode = 'minor' - else: - mode = 'major' - tonic = tonic.root().name + if isinstance(tonic, (note.Note, pitch.Pitch)): + tonicStr = tonic.name + else: + tonicStr = tonic + if mode is None: - if 'm' in tonic: + if 'm' in tonicStr: mode = 'minor' - tonic = re.sub('m', '', tonic) - elif 'M' in tonic: + tonicStr = re.sub('m', '', tonicStr) + elif 'M' in tonicStr: mode = 'major' - tonic = re.sub('M', '', tonic) - elif tonic.lower() == tonic: + tonicStr = re.sub('M', '', tonicStr) + elif tonicStr.lower() == tonicStr: mode = 'minor' else: mode = 'major' else: mode = mode.lower() - sharps = pitchToSharps(tonic, mode) - - KeySignature.__init__(self, sharps) - scale.DiatonicScale.__init__(self, tonic=tonic) + sharps = pitchToSharps(tonicStr, mode) + tonicPitch: pitch.Pitch if isinstance(tonic, pitch.Pitch): - self.tonic: pitch.Pitch = tonic + tonicPitch = tonic + elif isinstance(tonic, note.Note): + tonicPitch = tonic.pitch else: - self.tonic = pitch.Pitch(tonic) + tonicPitch = pitch.Pitch(tonic) + + KeySignature.__init__(self, sharps) + scale.DiatonicScale.__init__(self, tonic=tonicPitch) + self.tonic = tonicPitch self.type: str = mode self.mode: str = mode # build the network for the appropriate scale - self._abstract.buildNetwork(self.type) + if self._abstract is not None: + self._abstract.buildNetwork(self.type) # optionally filled attributes # store a floating point value between 0 and 1 regarding @@ -945,7 +974,7 @@ def __init__(self, self.correlationCoefficient = None # store an ordered list of alternative Key objects - self.alternateInterpretations = [] + self.alternateInterpretations: List[Key] = [] def __hash__(self): hashTuple = (self.tonic, self.mode) @@ -973,7 +1002,7 @@ def __eq__(self, other): return False @property - def relative(self): + def relative(self) -> Key: ''' if the Key is major or minor, return the relative minor or major. @@ -1003,7 +1032,7 @@ def relative(self): return KeySignature(self.sharps).asKey('major') @property - def parallel(self): + def parallel(self) -> Key: ''' if the Key is major or minor, return the parallel minor or major. @@ -1030,7 +1059,7 @@ def parallel(self): return Key(self.tonic, 'major') @property - def tonicPitchNameWithCase(self): + def tonicPitchNameWithCase(self) -> str: ''' Return the pitch name as a string with the proper case (upper = major; lower = minor) @@ -1052,12 +1081,12 @@ def tonicPitchNameWithCase(self): >>> k.tonicPitchNameWithCase 'B' ''' - tonic = self.tonic.name + tonicStr = self.tonic.name if self.mode == 'major': - tonic = tonic.upper() + tonicStr = tonicStr.upper() elif self.mode == 'minor': - tonic = tonic.lower() - return tonic + tonicStr = tonicStr.lower() + return tonicStr def deriveByDegree(self, degree, pitchRef): ''' @@ -1134,7 +1163,7 @@ def _tonalCertaintyCorrelationCoefficient(self, *args, **keywords): def tonalCertainty(self, method='correlationCoefficient', *args, - **keywords): + **keywords) -> float: ''' Provide a measure of tonal ambiguity for Key determined with one of many methods. @@ -1180,8 +1209,30 @@ def tonalCertainty(self, if method == 'correlationCoefficient': return self._tonalCertaintyCorrelationCoefficient( args, keywords) + else: + raise ValueError(f'Unknown method: {method}') + + @overload + def transpose(self: KeyType, + value: TransposeTypes, + *, + inPlace: Literal[False] = False + ) -> KeyType: + return self # astroid 1015 + + @overload + def transpose(self: KeyType, + value: TransposeTypes, + *, + inPlace: Literal[True] + ) -> None: + return None - def transpose(self, value, *, inPlace=False): + def transpose(self: KeyType, + value: TransposeTypes, + *, + inPlace: bool = False + ) -> Optional[KeyType]: ''' Transpose the Key by the user-provided value. If the value is an integer, the transposition is treated @@ -1203,7 +1254,7 @@ def transpose(self, value, *, inPlace=False): >>> aMaj.mode 'major' - inPlace works here + `inPlace=True` works here and returns None while changing the Key itself. >>> changingKey = key.Key('g') >>> changingKey @@ -1230,7 +1281,6 @@ def transpose(self, value, *, inPlace=False): >>> changingKey.transpose(1, inPlace=True) >>> changingKey - ''' if inPlace is True: super().transpose(value, inPlace=inPlace) @@ -1245,6 +1295,7 @@ def transpose(self, value, *, inPlace=False): # mode is already set if not inPlace: return post + return None # ------------------------------------------------------------------------------ @@ -1286,8 +1337,9 @@ def testSetTonic(self): k.tonic = b self.assertIs(k.tonic, b) - # Initialize with tonic from chord (i.e., the root) - b_flat_maj = chord.Chord('Bb4 D5 F5') + # Initialize with tonic from chord - no longer allowed. + # Call root explicitly + b_flat_maj = chord.Chord('Bb4 D5 F5').root() k = Key(tonic=b_flat_maj) self.assertEqual(k.tonic.name, 'B-') diff --git a/music21/pitch.py b/music21/pitch.py index 81ade69e6b..fad0bcca4d 100644 --- a/music21/pitch.py +++ b/music21/pitch.py @@ -21,7 +21,7 @@ import itertools import unittest from collections import OrderedDict -from typing import List, Optional, Union, TypeVar, Tuple, Dict, Literal +from typing import List, Optional, Union, TypeVar, Tuple, Dict, Literal, Set from music21 import base from music21 import common @@ -32,6 +32,7 @@ from music21 import prebase from music21.common.objects import SlottedObjectMixin +from music21.common.types import StepName from music21 import environment _T = TypeVar('_T') @@ -40,7 +41,7 @@ PitchClassString = Literal['a', 'A', 't', 'T', 'b', 'B', 'e', 'E'] -STEPREF = { +STEPREF: Dict[StepName, int] = { 'C': 0, 'D': 2, 'E': 4, @@ -50,7 +51,7 @@ 'B': 11, } NATURAL_PCS = (0, 2, 4, 5, 7, 9, 11) -STEPREF_REVERSED = { +STEPREF_REVERSED: Dict[int, StepName] = { 0: 'C', 2: 'D', 4: 'E', @@ -59,8 +60,14 @@ 9: 'A', 11: 'B', } -STEPNAMES = {'C', 'D', 'E', 'F', 'G', 'A', 'B'} # set -STEP_TO_DNN_OFFSET = {'C': 0, 'D': 1, 'E': 2, 'F': 3, 'G': 4, 'A': 5, 'B': 6} +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) @@ -152,7 +159,7 @@ def _convertPitchClassToNumber( return int(ps) -def convertPitchClassToStr(pc) -> str: +def convertPitchClassToStr(pc: int) -> str: ''' Given a pitch class number, return a string. @@ -195,7 +202,12 @@ def _convertPsToOct(ps: Union[int, float]) -> int: return int(math.floor(ps / 12.)) - 1 -def _convertPsToStep(ps) -> Tuple[str, 'Accidental', 'Microtone', int]: +def _convertPsToStep( + ps: Union[int, float] +) -> Tuple[Union[StepName, Literal['']], + 'Accidental', + 'Microtone', + int]: ''' Utility conversion; does not process internal representations. @@ -2743,7 +2755,7 @@ def fullName(self): return name @property - def step(self) -> str: + def step(self) -> StepName: ''' The diatonic name of the note; i.e. does not give the accidental or octave. @@ -2798,7 +2810,7 @@ def step(self) -> str: return self._step @step.setter - def step(self, usrStr: str) -> None: + def step(self, usrStr: StepName) -> None: ''' This does not change octave or accidental, only step ''' @@ -2913,7 +2925,7 @@ def pitchClass(self) -> int: return round(self.ps) % 12 @pitchClass.setter - def pitchClass(self, value: Union[str, int]): + def pitchClass(self, value: Union[int, PitchClassString]): # permit the submission of strings, like "A" and "B" value = _convertPitchClassToNumber(value) # get step and accidental w/o octave diff --git a/music21/scale/__init__.py b/music21/scale/__init__.py index a1eee06f77..749fa6ed7c 100644 --- a/music21/scale/__init__.py +++ b/music21/scale/__init__.py @@ -1252,6 +1252,12 @@ class ConcreteScale(Scale): >>> [str(p) for p in complexScale.getPitches('C7', 'C5')] ['A6', 'F#6', 'D~6', 'B5', 'G5', 'F5', 'E-5', 'C#5'] + + OMIT_FROM_DOCS + + >>> scale.ConcreteScale(tonic=4) + Traceback (most recent call last): + ValueError: Tonic must be a Pitch, Note, or str, not ''' usePitchDegreeCache = False @@ -1263,7 +1269,7 @@ def __init__(self, self.type = 'Concrete' # store an instance of an abstract scale # subclasses might use multiple abstract scales? - self._abstract = None + self._abstract: Optional[AbstractScale] = None # determine whether this is a limited range self.boundRange = False @@ -1282,10 +1288,12 @@ def __init__(self, self.tonic = None # pitch.Pitch() elif isinstance(tonic, str): self.tonic = pitch.Pitch(tonic) - elif isinstance(tonic, note.GeneralNote): + elif isinstance(tonic, note.Note): self.tonic = tonic.pitch - else: # assume this is a pitch object + elif isinstance(tonic, pitch.Pitch): # assume this is a pitch object self.tonic = tonic + else: + raise ValueError(f'Tonic must be a Pitch, Note, or str, not {type(tonic)}') if (pitches is not None and common.isListLike(pitches) @@ -2462,7 +2470,7 @@ class DiatonicScale(ConcreteScale): def __init__(self, tonic=None): super().__init__(tonic=tonic) - self._abstract = AbstractDiatonicScale() + self._abstract: AbstractDiatonicScale = AbstractDiatonicScale() self.type = 'diatonic' def getTonic(self): From bb1fc845286a38fd3950dba2aa2b2c7f789b4e84 Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Sun, 1 May 2022 14:37:56 -1000 Subject: [PATCH 2/2] Accidentally broke key.Key('F#m') etc. --- music21/harmony.py | 2 +- music21/key.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/music21/harmony.py b/music21/harmony.py index 9a156f102c..f82eea76ff 100644 --- a/music21/harmony.py +++ b/music21/harmony.py @@ -369,7 +369,7 @@ def key(self): >>> [str(p) for p in h1.pitches] ['D-2', 'F-2', 'A-2', 'C-3', 'E-3', 'G-3'] - >>> h1.key = 'CM' + >>> h1.key = 'CM' # = C-Major >>> [str(p) for p in h1.pitches] ['D-2', 'F-2', 'A-2', 'C-3', 'E-3', 'G-3'] diff --git a/music21/key.py b/music21/key.py index b9bdea5e09..c7576efd6c 100644 --- a/music21/key.py +++ b/music21/key.py @@ -775,15 +775,12 @@ def transposePitchFromC(self, p: pitch.Pitch, *, inPlace=False) -> Optional[pitc 'B--4' If inPlace is True then the original pitch is - modified. + modified and nothing is returned. >>> p5 = pitch.Pitch('C5') >>> ks.transposePitchFromC(p5, inPlace=True) - >>> p5.nameWithOctave 'E-5' - - Changed in v8: original pitch returned if inPlace=True ''' transInterval = None transTimes = 0 @@ -920,6 +917,13 @@ class Key(KeySignature, scale.DiatonicScale): , ] + If you prefer not to use uppercase and lowercase to distinguish major and minor, + the shorthand of CM or Cm can also be used: + + >>> key.Key('EM') + + >>> key.Key('F#m') + ''' _sharps = 0 _mode = None @@ -955,7 +959,7 @@ def __init__(self, elif isinstance(tonic, note.Note): tonicPitch = tonic.pitch else: - tonicPitch = pitch.Pitch(tonic) + tonicPitch = pitch.Pitch(tonicStr) KeySignature.__init__(self, sharps) scale.DiatonicScale.__init__(self, tonic=tonicPitch)