From 902d68f463c45867f31ac8f41c9aba75f64d93c0 Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 12:46:37 -1000 Subject: [PATCH 1/8] Remove deprecations --- music21/base.py | 45 ------ music21/braille/noteGrouping.py | 2 +- music21/common/numberTools.py | 21 --- music21/common/objects.py | 30 +--- music21/duration.py | 13 -- music21/meter/base.py | 12 -- music21/pitch.py | 2 +- music21/stream/base.py | 273 -------------------------------- 8 files changed, 3 insertions(+), 395 deletions(-) diff --git a/music21/base.py b/music21/base.py index e1d1ce4d48..e5a44c0916 100644 --- a/music21/base.py +++ b/music21/base.py @@ -837,7 +837,6 @@ def getOffsetBySite( site: 'music21.stream.Stream', *, returnSpecial=False, - stringReturns=False, ) -> Union[float, fractions.Fraction, str]: ''' If this class has been registered in a container such as a Stream, @@ -913,10 +912,6 @@ def getOffsetBySite( Changed in v7. -- stringReturns renamed to returnSpecial. Returns an OffsetSpecial Enum. ''' - if stringReturns and not returnSpecial: # pragma: no cover - returnSpecial = stringReturns - environLocal.warn('stringReturns is deprecated: use returnSpecial instead') - if site is None: return self._naiveOffset @@ -4019,48 +4014,8 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(f'Could not get attribute {name!r} in an object-less element') return object.__getattribute__(storedObj, name) - def isTwin(self, other: 'ElementWrapper') -> bool: - ''' - DEPRECATED: Just run:: - - (wrapper1.obj == wrapper2.obj) - - A weaker form of equality. a.isTwin(b) is true if - a and b store either the same object OR objects that are equal. - In other words, it is essentially the same object in a different context - - >>> import copy - >>> import music21 - - >>> aE = music21.ElementWrapper(obj='hello') - - >>> bE = copy.copy(aE) - >>> aE is bE - False - >>> aE == bE - True - >>> aE.isTwin(bE) - True - - >>> bE.offset = 14.0 - >>> bE.priority = -4 - >>> aE == bE - False - >>> aE.isTwin(bE) - True - ''' - if not hasattr(other, 'obj'): - return False - - if self.obj is other.obj or self.obj == other.obj: - return True - else: - return False - # ----------------------------------------------------------------------------- - - class TestMock(Music21Object): pass diff --git a/music21/braille/noteGrouping.py b/music21/braille/noteGrouping.py index f4bdb37ccb..d8a4d498e1 100644 --- a/music21/braille/noteGrouping.py +++ b/music21/braille/noteGrouping.py @@ -271,7 +271,7 @@ def transcribeNoteGrouping(brailleElementGrouping, showLeadingOctave=True): ''' transcribe a group of notes, possibly excluding certain attributes. - To be DEPRECATED -- called only be BrailleGrandSegment now. + To be DEPRECATED -- called only by BrailleGrandSegment now. ''' ngt = NoteGroupingTranscriber() ngt.showLeadingOctave = showLeadingOctave diff --git a/music21/common/numberTools.py b/music21/common/numberTools.py index fbdc6b04ad..bc8a0569d5 100644 --- a/music21/common/numberTools.py +++ b/music21/common/numberTools.py @@ -431,27 +431,6 @@ def roundToHalfInteger(num: Union[float, int]) -> Union[float, int]: floatVal = 1 return intVal + floatVal -@deprecated('v.7', 'v.8', 'just call math.isclose(x, y, abs_tol=1e-7)') -def almostEquals(x, y=0.0, grain=1e-7) -> bool: - # noinspection PyShadowingNames - ''' - almostEquals(x, y) -- returns True if x and y are - within grain (default 0.0000001) of each other - - Allows comparisons between floats that are normally inconsistent. - - DEPRECATED in v.7 -- just call `isclose` with `abs_tol`: - - >>> from math import isclose - >>> isclose(1.000000001, 1, abs_tol=1e-7) - True - >>> isclose(1.001, 1, abs_tol=1e-7) - False - >>> isclose(1.001, 1, abs_tol=0.1) - True - ''' - return isclose(x, y, abs_tol=grain) - def addFloatPrecision(x, grain=1e-2) -> Union[float, Fraction]: ''' diff --git a/music21/common/objects.py b/music21/common/objects.py index 772436eda7..30207c283f 100644 --- a/music21/common/objects.py +++ b/music21/common/objects.py @@ -248,7 +248,7 @@ class EqualSlottedObjectMixin(SlottedObjectMixin): Slots are the only things compared, so do not mix with a __dict__ based object. - Ignores differences in .id + The equal comparison ignores differences in .id ''' def __eq__(self, other): if type(self) is not type(other): @@ -268,34 +268,6 @@ def __ne__(self, other): # ------------------------------------------------------------------------------ -class Iterator(collections.abc.Iterator): # pragma: no cover - ''' - A simple Iterator object used to handle iteration of Streams and other - list-like objects. - - Deprecated in v7 -- not needed since Python 2.6 became music21 minimum! - ''' - # TODO: remove in v.8 - def __init__(self, data): - self.data = data - self.index = 0 - - @deprecated('2021 Jan v7', '2022 Jan', - 'common.Iterator is deprecated. use `iter(X)` instead.') - def __iter__(self): - self.index = 0 - return self - - def __next__(self): - if self.index >= len(self.data): - raise StopIteration - post = self.data[self.index] - self.index += 1 - return post - -# ------------------------------------------------------------------------------ - - class Timer: ''' An object for timing. Call it to get the current time since starting. diff --git a/music21/duration.py b/music21/duration.py index d7fd04a1b6..72f32c9cd3 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -2183,19 +2183,6 @@ def consolidate(self): # some notations will not properly unlink, and raise an error self.components = [dur] - @common.deprecated('v7', 'v8', 'Was intended for testing only') - def fill(self, quarterLengthList=('quarter', 'half', 'quarter')): # pragma: no cover - ''' - Utility method for testing; a quick way to fill components. This will - remove any existing values. - - Deprecated in v7. - ''' - self.components = [] - for x in quarterLengthList: - self.addDurationTuple(Duration(x)) - self.informClient() - def getGraceDuration( self, appoggiatura=False diff --git a/music21/meter/base.py b/music21/meter/base.py index 3b4eff8d32..0b29e1fd22 100644 --- a/music21/meter/base.py +++ b/music21/meter/base.py @@ -561,18 +561,6 @@ def load(self, value: str, divisions=None): except MeterException: environLocal.printDebug(['cannot set default accents for:', self]) - @common.deprecated('v7', 'v8', 'call .ratioString or .load()') - def loadRatio(self, numerator, denominator, divisions=None): # pragma: no cover - ''' - Change the numerator and denominator, like ratioString, but with - optional divisions and without resetting other parameters. - - DEPRECATED in v7. -- call .ratioString or .load with - value = f'{numerator}/{denominator}' - ''' - value = f'{numerator}/{denominator}' - self.load(value, divisions) - @property def ratioString(self): ''' diff --git a/music21/pitch.py b/music21/pitch.py index 90f698cd98..8b7db05037 100644 --- a/music21/pitch.py +++ b/music21/pitch.py @@ -2036,7 +2036,7 @@ def accidental(self) -> Optional[Accidental]: Deprecated usage allows setting accidental to - a number or string. Will be a warning in v.7 and removed in v.8. + a number or string. Will be a warning in v.8 and removed in v.9. >>> b = pitch.Pitch('C4') >>> b.accidental = 1.5 diff --git a/music21/stream/base.py b/music21/stream/base.py index d9d22b0a29..3b0897fab2 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -5766,279 +5766,6 @@ def _uniqueOffsetsAndEndTimes(self, offsetsOnly=False, endTimesOnly=False): for v in offsetDictValues} return sorted(offsets.union(endTimes)) - @common.deprecated('v7', 'v8', 'use chordify() instead') - def makeChords(self, - minimumWindowSize=0.125, - includePostWindow=True, - removeRedundantPitches=True, - useExactOffsets=False, - gatherArticulations=True, - gatherExpressions=True, - inPlace=False, - transferGroupsToPitches=False, - makeRests=True): # pragma: no cover - ''' - DEPRECATED in v7. Use Chordify instead! - - Gathers simultaneously sounding :class:`~music21.note.Note` objects - into :class:`~music21.chord.Chord` objects, each of which - contains all the pitches sounding together. - - If useExactOffsets is True (default=False), then do an exact - makeChords using the offsets in the piece. - If this parameter is set, then minimumWindowSize is ignored. - - This first example puts a part with three quarter notes (C4, D4, E4) - together with a part consisting of a half note (C#5) and a - quarter note (E#5) to make two Chords, the first containing the - three :class:`~music21.pitch.Pitch` objects sounding at the - beginning, the second consisting of the two Pitches sounding - on offset 2.0 (beat 3): - - >>> p1 = stream.Part() - >>> p1.append([note.Note('C4', type='quarter'), - ... note.Note('D4', type='quarter'), - ... note.Note('E4', type='quarter'), - ... note.Note('B2', type='quarter')]) - >>> p2 = stream.Part() - >>> p2.append([note.Note('C#5', type='half'), - ... note.Note('E#5', type='quarter'), - ... chord.Chord(['E4', 'G5', 'C#7'])]) - >>> sc1 = stream.Score() - >>> sc1.insert(0, p1) - >>> sc1.insert(0, p2) - >>> #_DOCS_SHOW scChords = sc1.flatten().makeChords() - >>> #_DOCS_SHOW scChords.show('text') - {0.0} - {2.0} - {3.0} - - The gathering of elements, starting from offset 0.0, uses - the `minimumWindowSize`, in quarter lengths, to collect - all Notes that start between 0.0 and the minimum window - size (this permits overlaps within a minimum tolerance). - - After collection, the maximum duration of collected elements - is found; this duration is then used to set the new - starting offset. A possible gap then results between the end - of the window and offset specified by the maximum duration; - these additional notes are gathered in a second pass if - `includePostWindow` is True. - - The new start offset is shifted to the larger of either - the minimum window or the maximum duration found in the - collected group. The process is repeated until all offsets - are covered. - - Each collection of Notes is formed into a Chord. The Chord - is given the longest duration of all constituents, and is - inserted at the start offset of the window from which it - was gathered. - - Chords can gather both articulations and expressions from found Notes - using `gatherArticulations` and `gatherExpressions`. - - If `transferGroupsToPitches` is True, and group defined on the source - elements Groups object will be transferred to the Pitch - objects contained in the resulting Chord. - - The resulting Stream, if not in-place, can also gather - additional objects by placing class names in the `collect` list. - By default, TimeSignature and KeySignature objects are collected. - ''' - if not inPlace: # make a copy - # since we do not return Scores, this probably should always be - # a Stream - # returnObj = Stream() - # returnObj = self.__class__() # for output - returnObj = self.coreCopyAsDerivation('makeChords') - else: - returnObj = self - - def dealWithSubNotes(chordLength, noteList): - # environLocal.printDebug(['creating chord from noteList', - # noteList, 'inPlace', inPlace]) - c = chord.Chord() - c.duration.quarterLength = chordLength - # these are references, not copies, for now - tempComponents = [] - for n in noteList: - if n.isChord: - cSub = list(n) - else: - cSub = [n] - - if transferGroupsToPitches and n.groups: - for comp in cSub: - for g in n.groups: - comp.pitch.groups.append(g) - - for comp in cSub: - tempComponents.append(comp) - - c.pitches = [comp.pitch for comp in tempComponents] - for comp in tempComponents: - if comp.tie is not None: - c.setTie(comp.tie.type, comp.pitch) - - if gatherArticulations: - for n in noteList: - c.articulations += n.articulations - if gatherExpressions: - for n in noteList: - c.expressions += n.expressions - # always remove all the previous elements - for n in noteList: - returnObj.remove(n) - # remove all rests found in source - for r in list(returnObj.getElementsByClass('Rest')): - returnObj.remove(r) - - if removeRedundantPitches: - removedPitches = c.removeRedundantPitches(inPlace=True) - - if transferGroupsToPitches: - for rem_p, cn in itertools.product(removedPitches, c): - if cn.pitch.nameWithOctave == rem_p.nameWithOctave: - # print(cn.pitch, rem_p) - # print(cn.pitch.groups, rem_p.groups) - cn.pitch.groups.extend(rem_p.groups) - return c - # environLocal.printDebug(['makeChords():', - # 'transferGroupsToPitches', transferGroupsToPitches]) - - if returnObj.hasMeasures(): - # call on component measures - for m in returnObj.getElementsByClass('Measure'): - # offset values are not relative to measure; need to - # shift by each measure's offset - m.makeChords( - minimumWindowSize=minimumWindowSize, - includePostWindow=includePostWindow, - removeRedundantPitches=removeRedundantPitches, - gatherArticulations=gatherArticulations, - gatherExpressions=gatherExpressions, - transferGroupsToPitches=transferGroupsToPitches, - inPlace=True, - makeRests=makeRests - ) - return returnObj # exit - - if returnObj.hasPartLikeStreams(): - # must get Streams, not Parts here - for p in returnObj.getElementsByClass('Stream'): - p.makeChords( - minimumWindowSize=minimumWindowSize, - includePostWindow=includePostWindow, - removeRedundantPitches=removeRedundantPitches, - gatherArticulations=gatherArticulations, - gatherExpressions=gatherExpressions, - transferGroupsToPitches=transferGroupsToPitches, - inPlace=True, - makeRests=makeRests - ) - return returnObj # exit - - # TODO: gather lyrics as an option - # define classes that are gathered; assume they have pitches - # matchClasses = ['Note', 'Chord', 'Rest'] - matchClasses = ['Note', 'Chord'] - o = 0.0 # start at zero - oTerminate = returnObj.highestOffset - - # get temporary boundaries for making rests - preHighestTime = returnObj.highestTime - preLowestOffset = returnObj.lowestOffset - # environLocal.printDebug(['got preLowest, preHighest', preLowestOffset, preHighestTime]) - if useExactOffsets is False: - while True: # TODO: Remove while True always... - # get all notes within the start and the min window size - oStart = o - oEnd = oStart + minimumWindowSize - subNotes = list(returnObj.getElementsByOffset( - oStart, - oEnd, - includeEndBoundary=False, - mustFinishInSpan=False, - mustBeginInSpan=True - ).getElementsByClass(matchClasses)) # get once for speed - # environLocal.printDebug(['subNotes', subNotes]) - qlMax: Optional[float] = None - # get the max duration found from within the window - if subNotes: - # get largest duration, use for duration of Chord, next span - qlMax = max([n.quarterLength for n in subNotes]) - - # if the max duration found in the window is greater than the min - # window size, it is possible that there are notes that will not - # be gathered; those starting at the end of this window but before - # the max found duration (as that will become the start of the next - # window - # so: if ql > min window, gather notes between - # oStart + minimumWindowSize and oStart + qlMax - if (includePostWindow and qlMax is not None - and qlMax > minimumWindowSize): - subAdd = list(returnObj.getElementsByOffset( - oStart + minimumWindowSize, - oStart + qlMax, - includeEndBoundary=False, - mustFinishInSpan=False, - mustBeginInSpan=True - ).getElementsByClass(matchClasses)) - # concatenate any additional notes found - subNotes += subAdd - - # make subNotes into a chord - if subNotes: - cOut = dealWithSubNotes(qlMax, subNotes) - # insert chord at start location - returnObj.coreInsert(o, cOut) - # environLocal.printDebug(['len of returnObj', len(returnObj)]) - # shift offset to qlMax or minimumWindowSize - if qlMax is not None and qlMax >= minimumWindowSize: - # update start offset to what was old boundary - # note: this assumes that the start of the longest duration - # was at oStart; it could have been between oStart and oEnd - o += qlMax - else: - o += minimumWindowSize - # end While loop conditions - if o > oTerminate: - break - else: # useExactOffsets is True: - onAndOffOffsets = self.flatten().notesAndRests.stream()._uniqueOffsetsAndEndTimes() - # environLocal.printDebug(['makeChords: useExactOffsets=True; - # onAndOffOffsets:', onAndOffOffsets]) - - for i in range(len(onAndOffOffsets) - 1): - # get all notes within the start and the min window size - oStart = onAndOffOffsets[i] - oEnd = onAndOffOffsets[i + 1] - subNotes = list(returnObj.getElementsByOffset( - oStart, - oEnd, - includeEndBoundary=False, - mustFinishInSpan=False, - mustBeginInSpan=True - ).getElementsByClass(matchClasses)) - # environLocal.printDebug(['subNotes', subNotes]) - # subNotes.show('t') - - # make subNotes into a chord - if subNotes: - cOut = dealWithSubNotes(oEnd - oStart, subNotes) - # insert chord at start location - returnObj.coreInsert(oStart, cOut) - - # makeRests to fill any gaps produced by stripping - # environLocal.printDebug(['pre makeRests show()']) - returnObj.coreElementsChanged() - if makeRests: - returnObj.makeRests( - refStreamOrTimeRange=(preLowestOffset, preHighestTime), - fillGaps=True, inPlace=True) - return returnObj - def chordify( self, *, From 5341823e52b0486c57d7cf0a4b17a706dde77c8a Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 12:58:36 -1000 Subject: [PATCH 2/8] More literal typing and grammar fixes --- music21/pitch.py | 94 ++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 52 deletions(-) diff --git a/music21/pitch.py b/music21/pitch.py index 8b7db05037..3a22eed81a 100644 --- a/music21/pitch.py +++ b/music21/pitch.py @@ -13,7 +13,7 @@ Classes for representing and manipulating pitches, pitch-space, and accidentals. Each :class:`~music21.note.Note` object has a `Pitch` object embedded in it. -Some of the methods below, such as `Pitch.name`, `Pitch.step`, etc. are +Some methods below, such as `Pitch.name`, `Pitch.step`, etc. are made available directly in the `Note` object, so they will seem familiar. ''' import copy @@ -21,7 +21,7 @@ import itertools import unittest from collections import OrderedDict -from typing import List, Optional, Union, TypeVar, Tuple, Dict +from typing import List, Optional, Union, TypeVar, Tuple, Dict, Literal from music21 import base from music21 import common @@ -39,6 +39,8 @@ _MOD = 'pitch' environLocal = environment.Environment(_MOD) +PitchClassString = Literal['a', 'A', 't', 'T', 'b', 'B', 'e', 'E'] + STEPREF = { 'C': 0, 'D': 2, @@ -123,7 +125,9 @@ def _sortModifiers(): # ------------------------------------------------------------------------------ # utility functions -def _convertPitchClassToNumber(ps) -> int: +def _convertPitchClassToNumber( + ps: Union[int, PitchClassString] +) -> int: ''' Given a pitch class string return the pitch class representation. @@ -140,10 +144,10 @@ def _convertPitchClassToNumber(ps) -> int: ''' if common.isNum(ps): return ps - else: # assume is is a string - if ps in ('a', 'A'): + else: # assume it is a string + if ps in ('a', 'A', 't', 'T'): return 10 - if ps in ('b', 'B'): + if ps in ('b', 'B', 'e', 'E'): return 11 # maybe it is a string of an integer? return int(ps) @@ -259,7 +263,7 @@ def _convertPsToStep(ps) -> Tuple[str, 'Accidental', 'Microtone', int]: # if close enough to a quarter tone if round(micro, 1) == 0.5: - # if can round to 0.5, than this is a quarter-tone accidental + # 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 # of alter is 0.5 and micro is 0.7 than micro should be 0.2 @@ -293,7 +297,7 @@ def _convertPsToStep(ps) -> Tuple[str, 'Accidental', 'Microtone', int]: # the above octave, which may not be represented in ps value if pc == 11: octShift = 1 - # its a natural; nothing to do + # 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 @@ -658,8 +662,8 @@ def __init__(self, self._centShift = centsOrString # specify harmonic in cents else: self._parseString(centsOrString) - # need to additional store a reference to a position in a - # another pitches overtone series? + # need to additional store a reference to a position in + # another pitch's overtone series? # such as: A4(+69c [7thH/C3])? # SPECIAL METHODS # @@ -722,7 +726,7 @@ def __str__(self): >>> m1 ''' - # cent values may be of any resolution, but round to nearest int + # cent values may be of any resolution, but round to the nearest int sub = '' roundShift = round(self._centShift) if self._centShift >= 0: @@ -838,7 +842,6 @@ class Accidental(prebase.ProtoM21Object, style.StyleMixin): 'displayStyle', ) - # define order to present names in documentation; use strings _DOC_ORDER = ['name', 'modifier', 'alter', 'set'] # documentation for all attributes (not properties or methods) @@ -1026,8 +1029,8 @@ def listNames(cls): def set(self, name, *, allowNonStandardValue=False): ''' - Change the type of the Accidental. Strings values, numbers, and Lilypond - Abbreviations are all accepted. All other values will change + 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() @@ -1599,7 +1602,7 @@ class Pitch(prebase.ProtoM21Object): >>> p3.fullName 'C-sharp in octave 7 (-30c)' - The full list of supported key words are: `name`, `accidental` (which + 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 @@ -1657,7 +1660,7 @@ class Pitch(prebase.ProtoM21Object): A consequence of comparing enharmonics for equality but .ps for comparisons is that a `Pitch` can be neither less than - or greater than another `Pitch` without being equal: + nor greater than another `Pitch` without being equal: >>> pitch.Pitch('C#5') == pitch.Pitch('D-5') False @@ -1686,11 +1689,11 @@ class Pitch(prebase.ProtoM21Object): 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 of the attributes there + 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 to present names in documentation; use strings + # 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) @@ -1768,7 +1771,7 @@ def __init__(self, self._overridden_freq440 = None # store an Accidental and Microtone objects - # note that creating an Accidental object is much more time consuming + # note that creating an Accidental object is much more time-consuming # than a microtone self._accidental = None self._microtone = None # 5% of pitch creation time; it'll be created in a sec anyhow @@ -1932,7 +1935,7 @@ def __le__(self, other) -> 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 or gt and not equal to each other! + be neither lt nor gt and not equal to each other! >>> a = pitch.Pitch('d4') >>> b = pitch.Pitch('d8') @@ -1974,7 +1977,7 @@ def __ge__(self, other) -> 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 or gt and not equal to each other! + be neither lt nor gt and not equal to each other! >>> a = pitch.Pitch('d4') >>> b = pitch.Pitch('d8') @@ -2018,7 +2021,7 @@ def accidental(self) -> Optional[Accidental]: ''' Stores an optional accidental object contained within the Pitch object. This might return None, which is different - than a natural accidental: + from a natural accidental: >>> a = pitch.Pitch('E-') >>> a.accidental.alter @@ -2034,20 +2037,6 @@ def accidental(self) -> Optional[Accidental]: False >>> b.accidental - - Deprecated usage allows setting accidental to - a number or string. Will be a warning in v.8 and removed in v.9. - - >>> b = pitch.Pitch('C4') - >>> b.accidental = 1.5 - >>> print(b) - C#4(+50c) - >>> b.accidental = 1.65 - >>> print(b) - C#~4(+15c) - >>> b.accidental = 1.95 - >>> print(b) - C##4(-5c) ''' return self._accidental @@ -2055,7 +2044,7 @@ def accidental(self) -> Optional[Accidental]: def accidental(self, value: Union[str, int, float, Accidental]): if isinstance(value, str): self._accidental = Accidental(value) - elif common.isNum(value): + elif common.isNum(value): # pragma: no cover # check and add any microtones alter, cents = _convertCentsToAlterAndCents(value * 100.0) self._accidental = Accidental(alter) @@ -2224,7 +2213,7 @@ def alter(self) -> float: ''' Get or set 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.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 @@ -2926,7 +2915,7 @@ def pitchClass(self) -> int: @pitchClass.setter def pitchClass(self, value: Union[str, int]): - # permit the submission of strings, like A an dB + # permit the submission of strings, like "A" and "B" value = _convertPitchClassToNumber(value) # get step and accidental w/o octave self.step, self._accidental = _convertPsToStep(value)[0:2] @@ -2941,7 +2930,7 @@ def pitchClassString(self) -> str: 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`. + possible with :attr:`~music21.pitch.Pitch.pitchClass`). >>> a = pitch.Pitch('a#3') >>> a.pitchClass @@ -3220,6 +3209,7 @@ def spanish(self) -> str: # noinspection SpellCheckingInspection @property def french(self) -> str: + # noinspection GrazieInspection ''' Read-only attribute. Returns the name of a Pitch in the French system @@ -3331,7 +3321,7 @@ def frequency(self) -> float: def frequency(self, value: Union[int, float]): self.freq440 = value - # these methods may belong in in a temperament object + # these methods may belong in a temperament object # name of method and property could be more clear @property @@ -3765,7 +3755,7 @@ def isEnharmonic(self, other: 'Pitch') -> bool: >>> pitch.Pitch('C4').isEnharmonic( pitch.Pitch('B#4') ) False - If either pitch is octaveless, then they a pitch in any octave will match: + If either pitch is octaveless, then a pitch in any octave will match: >>> pitch.Pitch('C#').isEnharmonic( pitch.Pitch('D-9') ) True @@ -4422,7 +4412,7 @@ def transposeBelowTarget( >>> pitch.Pitch('g#2').transposeBelowTarget(pitch.Pitch('f#8')) - But with minimize=True, it will actually RAISE the pitch so it is the closest + 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') @@ -4437,8 +4427,8 @@ def transposeBelowTarget( else: src = copy.deepcopy(self) while True: - # ref 20, min 10, lower ref - # ref 5, min 10, do not lower + # ref 20, min 10, lower ref. + # ref 5, min 10, do not lower. if src.ps - target.ps <= 0: break # lower one octave @@ -4502,8 +4492,8 @@ def transposeAboveTarget(self: _T, target, *, minimize=False, inPlace=False) -> src = copy.deepcopy(self) # 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 + # 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 @@ -4700,7 +4690,7 @@ def updateAccidentalDisplay( self.accidental.displayStatus = True return # do not search past - # pitches in past... first search if last pitch in measure + # pitches in the past... first search if last pitch in measure # at this octave contradicts this pitch. if so then no matter what # we need an accidental. for i in reversed(range(len(pitchPast))): @@ -4713,7 +4703,7 @@ def updateAccidentalDisplay( return else: # names are the same, skip this line of questioning break - # nope, no conflicting accidentals at this name and octave in past... + # 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 @@ -4850,7 +4840,7 @@ def updateAccidentalDisplay( else: if self.accidental is not None: self.accidental.displayStatus = False - # if we match the step in a key signature and we want + # 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): @@ -5281,7 +5271,7 @@ def compare(past, _result): proc(pList, [], ks.alteredPitches) compare(pList, result) - # non initial scale tones with chromatic alteration + # non-initial scale tones with chromatic alteration pList = [Pitch('a3'), Pitch('c#3'), Pitch('g#3'), Pitch('g3'), Pitch('c#4'), Pitch('g#4')] result = [(None, None), ('sharp', False), ('sharp', False), @@ -5290,7 +5280,7 @@ def compare(past, _result): proc(pList, [], ks.alteredPitches) compare(pList, result) - # non initial scale tones with chromatic alteration + # non-initial scale tones with chromatic alteration pList = [Pitch('a3'), Pitch('c#3'), Pitch('g#3'), Pitch('g3'), Pitch('c#4'), Pitch('g#4')] result = [(None, None), ('sharp', False), ('sharp', False), From 406a02213d2f520606e628fca30ea90eb3ade83f Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 13:18:00 -1000 Subject: [PATCH 3/8] add more typing in spanner --- music21/spanner.py | 92 ++++++++++++++++++------------- music21/stream/base.py | 119 +---------------------------------------- 2 files changed, 55 insertions(+), 156 deletions(-) diff --git a/music21/spanner.py b/music21/spanner.py index 02a4029708..f7a59f297b 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -20,7 +20,7 @@ ''' import unittest import copy -from typing import Any, Dict, Sequence, Union, List, Optional +from typing import Any, Dict, Sequence, Union, List, Optional, TypedDict from music21 import exceptions21 from music21 import base @@ -220,8 +220,8 @@ def __init__(self, *arguments, **keywords): self.spannerStorage = stream.SpannerStorage(spannerParent=self) # we do not want to auto sort based on offset or class, as - # both are meaningless inside of this Stream (and only have meaning - # in Stream external to this + # both are meaningless inside this Stream (and only have meaning + # in Stream external to this) self.spannerStorage.autoSort = False # add arguments as a list or single item @@ -278,13 +278,16 @@ def _deepcopySubclassable(self, memo=None, ignoreAttributes=None, removeFromIgno def __deepcopy__(self, memo=None): ''' - This produces a new, independent object containing references to the same spannedElements. - SpannedElements linked in this Spanner must be manually re-set, likely using the + This produces a new, independent object containing references + to the same spannedElements. + SpannedElements linked in this Spanner must be manually re-set, + likely using the replaceSpannedElement() method. - Notice that we put the references to the same object so that later we can replace them; + Notice that we put the references to the same object so that + later we can replace them; otherwise in a deepcopy of a stream, the notes in the stream - will become independent from the notes in the spanner. + will become independent of the notes in the spanner. >>> import copy >>> n1 = note.Note('g') @@ -403,18 +406,19 @@ def getSpannedElementIds(self): ''' return [id(n) for n in self.spannerStorage._elements] - def addSpannedElements(self, - spannedElements: Union[Sequence[base.Music21Object], - base.Music21Object], - *arguments, - **keywords): + def addSpannedElements( + self, + spannedElements: Union[Sequence[base.Music21Object], + base.Music21Object], + *arguments, + **keywords + ): ''' Associate one or more elements with this Spanner. The order in which elements are added is retained and may or may not be significant to the spanner. - >>> n1 = note.Note('g') >>> n2 = note.Note('f#') >>> n3 = note.Note('e') @@ -435,7 +439,7 @@ def addSpannedElements(self, if arguments: # copy spannedElements = spannedElements[:] # type: ignore[index] - # assume all other arguments + # assume all other arguments are spanners spannedElements += arguments # type: ignore[operator] for c in spannedElements: # type: ignore[union-attr] if c is None: @@ -451,7 +455,7 @@ def addSpannedElements(self, self.spannerStorage.coreElementsChanged() - def hasSpannedElement(self, spannedElement): + def hasSpannedElement(self, spannedElement: Music21Object) -> bool: ''' Return True if this Spanner has the spannedElement. @@ -603,12 +607,17 @@ def getLast(self): # ------------------------------------------------------------------------------ +class _SpannerRef(TypedDict): + # noinspection PyTypedDict + spanner: 'Spanner' + className: str + class SpannerBundle(prebase.ProtoM21Object): ''' An advanced utility object for collecting and processing collections of Spanner objects. This is necessary because often processing routines that happen at many different - levels need access to the same collection of spanners. + levels still need access to the same collection of spanners. Because SpannerBundles are so commonly used with :class:`~music21.stream.Stream` objects, the Stream has a @@ -621,10 +630,9 @@ class SpannerBundle(prebase.ProtoM21Object): Not to be confused with SpannerStorage (which is a Stream class inside a spanner that stores Elements that are spanned) - Changed in v7: only argument must be a List of spanners. Creators of SpannerBundles - are required to check that this constraint is True + Changed in v7: only argument must be a List of spanners. + Creators of SpannerBundles are required to check that this constraint is True ''' - def __init__(self, spanners: Optional[List[Spanner]] = None): self._cache: Dict[str, Any] = {} # cache is defined on Music21Object not ProtoM21Object @@ -638,7 +646,7 @@ def __init__(self, spanners: Optional[List[Spanner]] = None): # SpannerBundle as missing a spannedElement; the next obj that meets # the class expectation will then be assigned and the spannedElement # cleared - self._pendingSpannedElementAssignment: List[Dict[Spanner, str]] = [] + self._pendingSpannedElementAssignment: List[_SpannerRef] = [] def append(self, other): ''' @@ -794,7 +802,11 @@ def getBySpannedElement(self, spannedElement): self._cache[cacheKey] = post return self._cache[cacheKey] - def replaceSpannedElement(self, old, new): + def replaceSpannedElement( + self, + old: Music21Object, + new: Music21Object + ) -> List['Spanner']: # noinspection PyShadowingNames ''' Given a spanner spannedElement (an object), replace all old spannedElements @@ -834,13 +846,10 @@ def replaceSpannedElement(self, old, new): >>> sb.replaceSpannedElement(id(n1), n2) Traceback (most recent call last): - TypeError: send elements to replaceSpannedElement(), not ids (deprecated) + TypeError: send elements to replaceSpannedElement(), not ids. ''' - # environLocal.printDebug(['SpannerBundle.replaceSpannedElement()', 'old', old, - # 'new', new, 'len(self._storage)', len(self._storage)]) - # TODO: remove in v.8 for speed? if isinstance(old, int): - raise TypeError('send elements to replaceSpannedElement(), not ids (deprecated)') + raise TypeError('send elements to replaceSpannedElement(), not ids.') replacedSpanners = [] # post = self.__class__() # return a bundle of spanners that had changes @@ -864,7 +873,7 @@ def replaceSpannedElement(self, old, new): return replacedSpanners - def getByClass(self, className): + def getByClass(self, className: Union[str, type]) -> 'SpannerBundle': ''' Given a spanner class, return a new SpannerBundle of all Spanners of the desired class. @@ -947,20 +956,22 @@ class have been closed. The example below demonstrates that the def setIdLocals(self): # noinspection PyShadowingNames ''' - Utility method for outputting MusicXML (and potentially other formats) for spanners. + Utility method for outputting MusicXML (and potentially + other formats) for spanners. - Each Spanner type (slur, line, glissando, etc.) in MusicXML has a number assigned to it. - We call this number, `idLocal`. idLocal is a number from 1 to 6. This does not mean - that your piece can only have six slurs total! But it does mean that within a single - part, only up to 6 slurs can happen simultaneously. But as soon as a slur stops, its - idLocal can be reused. + Each Spanner type (slur, line, glissando, etc.) in MusicXML + has a number assigned to it. + We call this number, `idLocal`. idLocal is a number from 1 to 6. + This does not mean that your piece can only have six slurs total! + But it does mean that within a single + part, only up to 6 slurs can happen simultaneously. + But as soon as a slur stops, its idLocal can be reused. This method sets all idLocals for all classes in this SpannerBundle. This will assure that each class has a unique idLocal number. Calling this method is destructive: existing idLocal values will be lost. - >>> su1 = spanner.Slur() >>> su2 = layout.StaffGroup() >>> su3 = spanner.Slur() @@ -977,7 +988,8 @@ def setIdLocals(self): (, 2)] :class:`~music21.dynamics.DynamicWedge` objects are commingled. That is, - :class:`~music21.dynamics.Crescendo` and :class:`~music21.dynamics.Diminuendo` + :class:`~music21.dynamics.Crescendo` and + :class:`~music21.dynamics.Diminuendo` are not numbered separately: >>> sb2 = spanner.SpannerBundle() @@ -1023,7 +1035,11 @@ def getByClassIdLocalComplete(self, className, idLocal, completeStatus): return self.getByClass(className).getByIdLocal( idLocal).getByCompleteStatus(completeStatus) - def setPendingSpannedElementAssignment(self, sp, className): + def setPendingSpannedElementAssignment( + self, + sp: Spanner, + className: str, + ): ''' A SpannerBundle can be set up so that a particular spanner (sp) is looking for an element of class (className) to complete it. Any future @@ -1075,7 +1091,7 @@ def setPendingSpannedElementAssignment(self, sp, className): [] ''' - ref = {'spanner': sp, 'className': className} + ref: _SpannerRef = {'spanner': sp, 'className': className} self._pendingSpannedElementAssignment.append(ref) def freePendingSpannedElementAssignment(self, spannedElementCandidate): @@ -1517,7 +1533,7 @@ def shiftDirection(self, reverse=False): ''' Returns up or down depending on the type of shift: ''' - # an 8va means that the notes must be shifted down with the mark + # an 8va mark means that the notes must be shifted down with the mark if self._type.endswith('a'): if reverse: return 'down' diff --git a/music21/stream/base.py b/music21/stream/base.py index 3b0897fab2..dbcbedac21 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -1252,7 +1252,7 @@ def hasElementOfClass(self, className, forceFlat=False): >>> s.hasElementOfClass('Measure') False - To be deprecated in v.7 -- to be removed in version 8, use: + To be deprecated in v.8 -- to be removed in version 9, use: >>> bool(s.getElementsByClass('TimeSignature')) True @@ -5297,84 +5297,6 @@ def getInstrument(self, recurse=recurse) return post.first() - @common.deprecated('v7', 'v8', 'use getElementsByClass() or getContextByClass() or bestClef()') - def getClefs(self, searchActiveSite=False, searchContext=True, - returnDefault=True): # pragma: no cover - ''' - DEPRECATED in v7. - - Collect all :class:`~music21.clef.Clef` objects in - this Stream in a list. Optionally search the - activeSite Stream and/or contexts. - - If no Clef objects are defined, get a default - using :meth:`~music21.clef.bestClef` - - - >>> a = stream.Stream() - >>> b = clef.AltoClef() - >>> a.insert(0, b) - >>> a.repeatInsert(note.Note('C#'), list(range(10))) - >>> #_DOCS_SHOW c = a.getClefs() - >>> #_DOCS_SHOW len(c) == 1 - True - ''' - # TODO: activeSite searching is not yet implemented - # this may not be useful unless a stream is flat - post = list(self.getElementsByClass('Clef')) - - # environLocal.printDebug(['getClefs(); count of local', len(post), post]) - if not post and searchActiveSite and self.activeSite is not None: - # environLocal.printDebug(['getClefs(): search activeSite']) - post = list(self.activeSite.getElementsByClass('Clef')) - - if not post and searchContext: - # returns a single element match - # post = self.__class__() - obj = self.getContextByClass('Clef') - if obj is not None: - post.append(obj) - - # get a default and/or place default at zero if nothing at zero - if returnDefault and (not post or post[0].offset > 0): - # environLocal.printDebug(['getClefs(): using bestClef()']) - post.insert(0, clef.bestClef(self)) - return post - - @common.deprecated('v7', 'v8', 'use getElementsByClass() or getContextByClass()') - def getKeySignatures(self, searchActiveSite=True, searchContext=True): # pragma: no cover - ''' - Collect all :class:`~music21.key.KeySignature` objects in this - Stream in a new Stream. Optionally search the activeSite - stream and/or contexts. - - If no KeySignature objects are defined, returns an empty Stream - - DEPRECATED in v7. - - >>> a = stream.Stream() - >>> b = key.KeySignature(3) - >>> a.insert(0, b) - >>> a.repeatInsert(note.Note('C#'), list(range(10))) - >>> #_DOCS_SHOW c = a.getKeySignatures() - >>> #_DOCS_SHOW len(c) == 1 - True - ''' - # TODO: activeSite searching is not yet implemented - # this may not be useful unless a stream is flat - post = self.getElementsByClass('KeySignature') - if not post and searchContext: - # returns a single value - post = self.cloneEmpty(derivationMethod='getKeySignatures') - obj = self.getContextByClass(key.KeySignature) - if obj is not None: - post.append(obj) - - # do nothing if empty - if not post or post[0].offset > 0: - pass - return post - def invertDiatonic(self, inversionNote=note.Note('C4'), *, inPlace=False): ''' inverts a stream diatonically around the given note (by default, middle C) @@ -6660,45 +6582,6 @@ def extendDuration(self, objName, *, inPlace=False): if not inPlace: return returnObj - @common.deprecated('v7', 'v8', 'call extendDurations() and getElementsByClass() separately') - def extendDurationAndGetBoundaries(self, objName, *, inPlace=False): # pragma: no cover - ''' - DEPRECATED in v.7 -- to be removed in v.8 - - Extend the Duration of elements specified by objName; - then, collect a dictionary for every matched element of objName class, - where the matched element is the value and the key is the (start, end) offset value. - - >>> from pprint import pprint as pp - >>> s = stream.Stream() - >>> s.insert(3, dynamics.Dynamic('mf')) - >>> s.insert(7, dynamics.Dynamic('f')) - >>> s.insert(12, dynamics.Dynamic('ff')) - >>> #_DOCS_SHOW pp(s.extendDurationAndGetBoundaries('Dynamic')) - {(3.0, 7.0): , - (7.0, 12.0): , - (12.0, 12.0): } - - - TODO: only allow inPlace=True or delete or something, can't return two different things - ''' - if not inPlace: # make a copy - returnObj = copy.deepcopy(self) - else: - returnObj = self - returnObj.extendDuration(objName, inPlace=True) - # TODO: use iteration. - elements = returnObj.getElementsByClass(objName) - boundaries = {} - if not elements: - raise StreamException('no elements of this class defined in this Stream') - - for e in elements: - start = returnObj.elementOffset(e) - end = start + e.duration.quarterLength - boundaries[(start, end)] = e - return boundaries - def stripTies( self, inPlace=False, From 6d9273b69b5196f8e75a7d9d07f339de95010bbc Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 14:33:53 -1000 Subject: [PATCH 4/8] remove PyDev tags, typos, duration speed --- .idea/inspectionProfiles/Project_Default.xml | 3 + documentation/nbvalNotebook.py | 4 +- documentation/source/conf.py | 2 +- music21/analysis/reduction.py | 72 ++++++++++---------- music21/audioSearch/recording.py | 2 +- music21/bar.py | 2 +- music21/clef.py | 2 +- music21/common/classTools.py | 2 +- music21/common/fileTools.py | 4 +- music21/common/formats.py | 2 +- music21/common/numberTools.py | 39 ++++++----- music21/common/objects.py | 1 - music21/common/parallel.py | 2 +- music21/common/pathTools.py | 8 +-- music21/common/stringTools.py | 2 +- music21/converter/__init__.py | 50 ++++++++------ music21/converter/qmConverter.py | 4 +- music21/converter/subConverters.py | 17 ++--- music21/corpus/__init__.py | 4 +- music21/corpus/chorales.py | 9 ++- music21/corpus/manager.py | 2 +- music21/duration.py | 18 ++--- music21/features/base.py | 18 ++--- music21/lily/lilyObjects.py | 2 +- music21/lily/translate.py | 4 +- music21/metadata/caching.py | 2 +- music21/meter/tools.py | 10 +-- music21/midi/__init__.py | 3 +- music21/musicxml/m21ToXml.py | 5 +- music21/spanner.py | 8 +-- music21/tie.py | 2 +- 31 files changed, 164 insertions(+), 141 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 22acb09a64..97fe104cfd 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -35,6 +35,9 @@ diff --git a/documentation/nbvalNotebook.py b/documentation/nbvalNotebook.py index 1a58800feb..f5fa7f0c30 100644 --- a/documentation/nbvalNotebook.py +++ b/documentation/nbvalNotebook.py @@ -9,9 +9,9 @@ import sys import subprocess # noinspection PyPackageRequirements -import pytest # @UnusedImport # pylint: disable=unused-import,import-error +import pytest # pylint: disable=unused-import,import-error # noinspection PyPackageRequirements -import nbval # @UnusedImport # pylint: disable=unused-import,import-error +import nbval ## pylint: disable=unused-import,import-error from music21 import environment from music21 import common diff --git a/documentation/source/conf.py b/documentation/source/conf.py index 5456fec905..884e2ed9d9 100644 --- a/documentation/source/conf.py +++ b/documentation/source/conf.py @@ -48,7 +48,7 @@ project = 'music21' # pylint: disable=redefined-builtin # noinspection PyShadowingBuiltins -copyright = '2006-2021, Michael Scott Cuthbert and cuthbertLab' # @ReservedAssignment +copyright = '2006-2021, Michael Scott Cuthbert and cuthbertLab' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index 309217e93e..1b93afd6f3 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -952,42 +952,42 @@ def testExtractionB(self): self.assertEqual(len(match), 3) # post.show() - def testExtractionC(self): - from music21 import analysis - from music21 import corpus - # http://solomonsmusic.net/schenker.htm - # shows extracting an Ursatz line - - # BACH pre;ide !, WTC - - src = corpus.parse('bwv846') - import warnings - with warnings.catch_warnings(): # catch deprecation warning - warnings.simplefilter('ignore', category=exceptions21.Music21DeprecationWarning) - chords = src.flattenParts().makeChords(minimumWindowSize=4, - makeRests=False) - for c in chords.flatten().notes: - c.quarterLength = 4 - for m in chords.getElementsByClass('Measure'): - m.clef = clef.bestClef(m, recurse=True) - - chords.measure(1).notes[0].addLyric('::/p:e/o:5/nf:no/ta:3/g:Ursatz') - chords.measure(1).notes[0].addLyric('::/p:c/o:4/nf:no/tb:I') - - chords.measure(24).notes[0].addLyric('::/p:d/o:5/nf:no/ta:2') - chords.measure(24).notes[0].addLyric('::/p:g/o:3/nf:no/tb:V') - - chords.measure(30).notes[0].addLyric('::/p:f/o:4/tb:7') - - chords.measure(34).notes[0].addLyric('::/p:c/o:5/nf:no/v:1/ta:1') - chords.measure(34).notes[0].addLyric('::/p:g/o:4/nf:no/v:2') - chords.measure(34).notes[0].addLyric('::/p:c/o:4/nf:no/v:1/tb:I') - - sr = analysis.reduction.ScoreReduction() - sr.chordReduction = chords - # sr.score = src - unused_post = sr.reduce() - # unused_post.show() + # def testExtractionC(self): + # from music21 import analysis + # from music21 import corpus + # # http://solomonsmusic.net/schenker.htm + # # shows extracting an Ursatz line + # + # # BACH pre;ide !, WTC + # + # src = corpus.parse('bwv846') + # import warnings + # with warnings.catch_warnings(): # catch deprecation warning + # warnings.simplefilter('ignore', category=exceptions21.Music21DeprecationWarning) + # chords = src.flattenParts().makeChords(minimumWindowSize=4, + # makeRests=False) + # for c in chords.flatten().notes: + # c.quarterLength = 4 + # for m in chords.getElementsByClass('Measure'): + # m.clef = clef.bestClef(m, recurse=True) + # + # chords.measure(1).notes[0].addLyric('::/p:e/o:5/nf:no/ta:3/g:Ursatz') + # chords.measure(1).notes[0].addLyric('::/p:c/o:4/nf:no/tb:I') + # + # chords.measure(24).notes[0].addLyric('::/p:d/o:5/nf:no/ta:2') + # chords.measure(24).notes[0].addLyric('::/p:g/o:3/nf:no/tb:V') + # + # chords.measure(30).notes[0].addLyric('::/p:f/o:4/tb:7') + # + # chords.measure(34).notes[0].addLyric('::/p:c/o:5/nf:no/v:1/ta:1') + # chords.measure(34).notes[0].addLyric('::/p:g/o:4/nf:no/v:2') + # chords.measure(34).notes[0].addLyric('::/p:c/o:4/nf:no/v:1/tb:I') + # + # sr = analysis.reduction.ScoreReduction() + # sr.chordReduction = chords + # # sr.score = src + # unused_post = sr.reduce() + # # unused_post.show() def testExtractionD(self): diff --git a/music21/audioSearch/recording.py b/music21/audioSearch/recording.py index 9071510900..9a8613ff70 100644 --- a/music21/audioSearch/recording.py +++ b/music21/audioSearch/recording.py @@ -55,7 +55,7 @@ def samplesFromRecording(seconds=10.0, storeFile=True, Returns a list of samples. ''' # noinspection PyPackageRequirements - import pyaudio # @UnresolvedImport # pylint: disable=import-error + import pyaudio # pylint: disable=import-error recordFormatDefault = pyaudio.paInt16 if recordFormat is None: diff --git a/music21/bar.py b/music21/bar.py index e2513d4c07..2d52df348d 100644 --- a/music21/bar.py +++ b/music21/bar.py @@ -129,7 +129,7 @@ class Barline(base.Music21Object): classSortOrder = -5 def __init__(self, - type=None, # @ReservedAssignment # pylint: disable=redefined-builtin + type=None, # pylint: disable=redefined-builtin location=None): super().__init__() diff --git a/music21/clef.py b/music21/clef.py index fe03fa3e2f..86348819ab 100644 --- a/music21/clef.py +++ b/music21/clef.py @@ -820,7 +820,7 @@ def clefFromString(clefString, octaveShift=0) -> Clef: else: lineNum = False elif len(xnStr) > 2: - from music21 import clef as myself # @UnresolvedImport + from music21 import clef as myself xnLower = xnStr.lower() for x in dir(myself): if 'Clef' not in x: diff --git a/music21/common/classTools.py b/music21/common/classTools.py index 9e62bc8284..bfb75db50a 100644 --- a/music21/common/classTools.py +++ b/music21/common/classTools.py @@ -137,7 +137,7 @@ def classToClassStr(classObj: Type) -> str: def getClassSet(instance, classNameTuple=None): ''' - Return the classSet for an instance (whether a Music21Object or something else. + Return the classSet for an instance (whether a Music21Object or something else). See base.Music21Object.classSet for more details. >>> p = pitch.Pitch() diff --git a/music21/common/fileTools.py b/music21/common/fileTools.py index 26687eeb26..c9f9d66554 100644 --- a/music21/common/fileTools.py +++ b/music21/common/fileTools.py @@ -78,7 +78,7 @@ def readFileEncodingSafe(filePath, firstGuess='utf-8') -> str: Slow, but will read a file of unknown encoding as safely as possible using the chardet package. - Let's try to load this file as ascii -- it has a copyright symbol at the top + Let's try to load this file as ascii -- it has a copyright symbol at the top, so it won't load in Python3: >>> import os @@ -96,7 +96,7 @@ def readFileEncodingSafe(filePath, firstGuess='utf-8') -> str: >>> data[0:30] '# -*- coding: utf-8 -*-\n# ----' - Well, that's nothing, since the first guess here is utf-8 and it's right. So let's + Well, that's nothing, since the first guess here is utf-8, and it's right. So let's give a worse first guess: >>> data = common.readFileEncodingSafe(c, firstGuess='SHIFT_JIS') # old Japanese standard diff --git a/music21/common/formats.py b/music21/common/formats.py index a486dfa949..70c2786204 100644 --- a/music21/common/formats.py +++ b/music21/common/formats.py @@ -307,7 +307,7 @@ def findFormatExtURL(url): >>> urlA = 'http://somesite.com/?l=cc/schubert/piano/d0576&file=d0576-06.krn&f=xml' >>> urlB = 'http://somesite.com/cgi-bin/ksdata?l=cc/schubert/d0576&file=d0576-06.krn&f=kern' >>> urlC = 'http://somesite.com/cgi-bin/ksdata?l=cc/bach/cello&file=bwv1007-01.krn&f=xml' - >>> urlF = 'http://junk' + >>> urlF = 'https://junk' >>> urlM = 'http://somesite.com/files/mid001.mid' >>> common.findFormatExtURL(urlA) diff --git a/music21/common/numberTools.py b/music21/common/numberTools.py index bc8a0569d5..90b111a654 100644 --- a/music21/common/numberTools.py +++ b/music21/common/numberTools.py @@ -6,10 +6,12 @@ # Authors: Michael Scott Cuthbert # Christopher Ariza # -# Copyright: Copyright © 2009-2015 Michael Scott Cuthbert and the music21 Project +# Copyright: Copyright © 2009-2022 Michael Scott Cuthbert and the music21 Project # License: BSD, see license.txt # ------------------------------------------------------------------------------ +from functools import lru_cache import math +import numbers import random import unittest from typing import List, no_type_check, Tuple, Union, Sequence, Iterable @@ -29,7 +31,6 @@ 'opFrac', 'mixedNumeral', 'roundToHalfInteger', - 'almostEquals', 'addFloatPrecision', 'strTrimFloat', 'nearestMultiple', @@ -147,14 +148,14 @@ def numToIntOrFloat(value: Union[int, float]) -> Union[int, float]: DENOM_LIMIT = defaults.limitOffsetDenominator -def _preFracLimitDenominator(n, d): +def _preFracLimitDenominator(n: int, d: int) -> Tuple[int, int]: # noinspection PyShadowingNames ''' Used in opFrac Copied from fractions.limit_denominator. Their method - requires creating three new Fraction instances to get one back. this doesn't create any - call before Fraction... + requires creating three new Fraction instances to get one back. + This doesn't create any call before Fraction... DENOM_LIMIT is hardcoded to defaults.limitOffsetDenominator for speed... @@ -228,6 +229,7 @@ def _preFracLimitDenominator(n, d): # no type checking due to accessing protected attributes (for speed) @no_type_check +@lru_cache(1024) # speeds up x2! but all fractions will be the same object... def opFrac(num: Union[OffsetQLIn, None]) -> Union[OffsetQL, None]: ''' opFrac -> optionally convert a number to a fraction or back. @@ -270,7 +272,8 @@ def opFrac(num: Union[OffsetQLIn, None]) -> Union[OffsetQL, None]: ''' # This is a performance critical operation, tuned to go as fast as possible. # hence redundancy -- first we check for type (no inheritance) and then we - # repeat exact same test with inheritance. Note that the later examples are more verbose + # repeat exact same test with inheritance. + # Note that the later examples are more verbose t = type(num) if t is float: # quick test of power of whether denominator is a power @@ -318,7 +321,8 @@ def opFrac(num: Union[OffsetQLIn, None]) -> Union[OffsetQL, None]: raise TypeError(f'Cannot convert num: {num}') -def mixedNumeral(expr, limitDenominator=defaults.limitOffsetDenominator): +def mixedNumeral(expr: numbers.Real, + limitDenominator=defaults.limitOffsetDenominator): ''' Returns a string representing a mixedNumeral form of a number @@ -367,8 +371,7 @@ def mixedNumeral(expr, limitDenominator=defaults.limitOffsetDenominator): quotient = 0.0 remainderFrac = remainderFrac - 1 else: - # noinspection PyTypeChecker - quotient = int(expr) # int seems completely supported for Fractions + quotient = int(float(expr)) remainderFrac = expr - quotient if quotient < 0: remainderFrac *= -1 @@ -504,7 +507,8 @@ def nearestMultiple(n: float, unit: float) -> Tuple[float, float, float]: >>> print(common.nearestMultiple(0.20, 0.25)) (0.25, 0.05..., -0.05...) - Note that this one also has an error of 0.1 but it's a positive error off of 0.5 + Note that this one also has an error of 0.1, but it's a positive error off of 0.5 + >>> print(common.nearestMultiple(0.4, 0.25)) (0.5, 0.1..., -0.1...) @@ -535,12 +539,12 @@ def nearestMultiple(n: float, unit: float) -> Tuple[float, float, float]: >>> common.nearestMultiple(-0.5, 0.125) Traceback (most recent call last): - ValueError: n (-0.5) is less than zero. Thus cannot find nearest + ValueError: n (-0.5) is less than zero. Thus cannot find the nearest multiple for a value less than the unit, 0.125 ''' if n < 0: raise ValueError(f'n ({n}) is less than zero. ' - + 'Thus cannot find nearest multiple for a value ' + + 'Thus cannot find the nearest multiple for a value ' + f'less than the unit, {unit}') mult = math.floor(n / unit) # can start with the floor @@ -561,6 +565,9 @@ def nearestMultiple(n: float, unit: float) -> Tuple[float, float, float]: return matchHigh, round(matchHigh - n, 7), round(n - matchHigh, 7) +_DOT_LOOKUP = (1, 1.5, 1.75, 1.875, 1.9375, + 1.96875, 1.984375, 1.9921875, 1.99609375) + def dotMultiplier(dots: int) -> float: ''' dotMultiplier(dots) returns how long to multiply the note @@ -580,6 +587,9 @@ def dotMultiplier(dots: int) -> float: >>> common.dotMultiplier(0) 1.0 ''' + if dots < 9: + return _DOT_LOOKUP[dots] + return ((2 ** (dots + 1.0)) - 1.0) / (2 ** dots) @@ -608,10 +618,7 @@ def decimalToTuplet(decNum: float) -> Tuple[int, int]: >>> common.decimalToTuplet(-.02) Traceback (most recent call last): ZeroDivisionError: number must be greater than zero - - TODO: replace with fractions... ''' - def findSimpleFraction(inner_working): 'Utility function.' for index in range(1, 1000): @@ -833,7 +840,7 @@ def lcm(filterList: Iterable[int]) -> int: ''' def _lcm(a, b): - '''find lowest common multiple of a, b''' + '''find the least common multiple of a, b''' # // forces integer style division (no remainder) return abs(a * b) // euclidGCD(a, b) diff --git a/music21/common/objects.py b/music21/common/objects.py index 30207c283f..87de228385 100644 --- a/music21/common/objects.py +++ b/music21/common/objects.py @@ -15,7 +15,6 @@ 'RelativeCounter', 'SlottedObjectMixin', 'EqualSlottedObjectMixin', - 'Iterator', 'Timer', ] diff --git a/music21/common/parallel.py b/music21/common/parallel.py index 6a3503b429..c91c4171e2 100644 --- a/music21/common/parallel.py +++ b/music21/common/parallel.py @@ -55,7 +55,7 @@ def runParallel(iterable, parallelFunction, *, ... c = corpus.parse(fn) # this is the slow call that is good to parallelize ... return len(c.recurse().notes) >>> #_DOCS_SHOW outputs = common.runParallel(files, countNotes) - >>> outputs = common.runNonParallel(files, countNotes) #_DOCS_HIDE cant pickle doctest funcs. + >>> outputs = common.runNonParallel(files, countNotes) #_DOCS_HIDE cannot pickle doctest funcs. >>> outputs [165, 50, 131] diff --git a/music21/common/pathTools.py b/music21/common/pathTools.py index 7ccbb944e9..4b6a223b81 100644 --- a/music21/common/pathTools.py +++ b/music21/common/pathTools.py @@ -117,7 +117,7 @@ def getCorpusContentDirs() -> List[str]: def getRootFilePath() -> pathlib.Path: ''' - Return the root directory for music21 -- outside of the music21 namespace + Return the root directory for music21 -- outside the music21 namespace which has directories such as "dist", "documentation", "music21" >>> fp = common.getRootFilePath() @@ -147,9 +147,9 @@ def relativepath(path: str, start: Optional[str] = None) -> str: def cleanpath(path: Union[str, pathlib.Path], *, returnPathlib=None) -> Union[str, pathlib.Path]: ''' Normalizes the path by expanding ~user on Unix, ${var} environmental vars - (is this a good idea?), expanding %name% on Windows, normalizing path names (Windows - turns backslashes to forward slashes, and finally if that file is not an absolute path, - turns it from a relative path to an absolute path. + (is this a good idea?), expanding %name% on Windows, normalizing path names + (Windows turns backslashes to forward slashes), and finally if that file + is not an absolute path, turns it from a relative path to an absolute path. v5 -- returnPathlib -- None (default) does not convert. False, returns a string, True, returns a pathlib.Path. diff --git a/music21/common/stringTools.py b/music21/common/stringTools.py index e0e0b106e9..5815de2b0a 100644 --- a/music21/common/stringTools.py +++ b/music21/common/stringTools.py @@ -81,7 +81,7 @@ def getNumFromStr(usrStr: str, numbers: str = '0123456789') -> Tuple[str, str]: found.append(char) else: remain.append(char) - # returns numbers, and then characters + # returns numbers and then characters return ''.join(found), ''.join(remain) diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py index 7633e9c4a5..d8905c96bd 100644 --- a/music21/converter/__init__.py +++ b/music21/converter/__init__.py @@ -22,7 +22,7 @@ is supported, a :class:`~music21.stream.Score` will be returned. -This is the most general public interface for all formats. Programmers +This is the most general, public interface for all formats. Programmers adding their own formats to the system should provide an interface here to their own parsers (such as humdrum, musicxml, etc.) @@ -95,7 +95,7 @@ class ConverterFileException(exceptions21.Music21Exception): class ArchiveManager: r''' Before opening a file path, this class can check if this is an - archived file collection, such as a .zip or or .mxl file. This will return the + archived file collection, such as a .zip or .mxl file. This will return the data from the archive. >>> fnCorpus = corpus.getWork('bwv66.6', fileExtensions=('.xml',)) @@ -413,7 +413,7 @@ def unregisterSubconverter(removeSubconverter): >>> mxlConverter in c.subconvertersList() False - if there is no such subConverter registered and it is not a default subconverter, + If there is no such subConverter registered, and it is not a default subconverter, then a converter.ConverterException is raised: >>> class ConverterSonix(converter.subConverters.SubConverter): @@ -480,7 +480,7 @@ def _getDownloadFp(self, directory, ext, url): # pylint: disable=redefined-builtin # noinspection PyShadowingBuiltins def parseFileNoPickle(self, fp, number=None, - format=None, forceSource=False, **keywords): # @ReservedAssignment + format=None, forceSource=False, **keywords): ''' Given a file path, parse and store a music21 Stream. @@ -584,7 +584,7 @@ def parseFile(self, fp, number=None, self.stream.fileFormat = useFormat def parseData(self, dataStr, number=None, - format=None, forceSource=False, **keywords): # @ReservedAssignment + format=None, forceSource=False, **keywords): ''' Given raw data, determine format and parse into a music21 Stream. ''' @@ -635,29 +635,36 @@ def parseData(self, dataStr, number=None, self.subConverter.keywords = keywords self.subConverter.parseData(dataStr, number=number) - def parseURL(self, url, *, format=None, number=None, - forceSource=False, **keywords): # @ReservedAssignment - '''Given a url, download and parse the file + def parseURL(self, + url, + *, + format=None, + number=None, + forceSource=False, + **keywords): + ''' + Given a url, download and parse the file into a music21 Stream stored in the `stream` property of the converter object. Note that this checks the user Environment `autoDownload` setting before downloading. - Use `forceSource=True` to download every time rather than read from a cached file. + Use `forceSource=True` to download every time rather than + re-reading from a cached file. - >>> jeanieLightBrownURL = ('https://github.com/cuthbertLab/music21/raw/master' + - ... '/music21/corpus/leadSheet/fosterBrownHair.mxl') + >>> joplinURL = ('https://github.com/cuthbertLab/music21/raw/master' + + ... '/music21/corpus/joplin/maple_leaf_rag.mxl') >>> c = converter.Converter() - >>> #_DOCS_SHOW c.parseURL(jeanieLightBrownURL) - >>> #_DOCS_SHOW jeanieStream = c.stream + >>> #_DOCS_SHOW c.parseURL(joplinURL) + >>> #_DOCS_SHOW joplinStream = c.stream Changed in v.7 -- made keyword-only and added `forceSource` option. ''' autoDownload = environLocal['autoDownload'] if autoDownload in ('deny', 'ask'): - message = 'Automatic downloading of URLs is presently set to {!r};' - message += ' configure your Environment "autoDownload" setting to ' + message = 'Automatic downloading of URLs is presently set to {!r}; ' + message += 'configure your Environment "autoDownload" setting to ' message += '"allow" to permit automatic downloading: ' message += "environment.set('autoDownload', 'allow')" message = message.format(autoDownload) @@ -700,6 +707,7 @@ def parseURL(self, url, *, format=None, number=None, self.stream.filePath = fp # These are attributes defined outside of self.stream.fileNumber = number # __init__ and will be moved to self.stream.fileFormat = useFormat # Metadata in v8. + # TODO: v8 -- move to Metadata # -----------------------------------------------------------------------# # Subconverters @@ -1028,7 +1036,7 @@ def stream(self): # pylint: disable=redefined-builtin # noinspection PyShadowingBuiltins -def parseFile(fp, number=None, format=None, forceSource=False, **keywords): # @ReservedAssignment +def parseFile(fp, number=None, format=None, forceSource=False, **keywords): ''' Given a file path, attempt to parse the file into a Stream. ''' @@ -1039,7 +1047,7 @@ def parseFile(fp, number=None, format=None, forceSource=False, **keywords): # @ # pylint: disable=redefined-builtin # noinspection PyShadowingBuiltins -def parseData(dataStr, number=None, format=None, **keywords): # @ReservedAssignment +def parseData(dataStr, number=None, format=None, **keywords): ''' Given musical data represented within a Python string, attempt to parse the data into a Stream. @@ -1051,7 +1059,7 @@ def parseData(dataStr, number=None, format=None, **keywords): # @ReservedAssign # pylint: disable=redefined-builtin # noinspection PyShadowingBuiltins def parseURL(url, *, format=None, number=None, - forceSource=False, **keywords): # @ReservedAssignment + forceSource=False, **keywords): ''' Given a URL, attempt to download and parse the file into a Stream. Note: URL downloading will not happen automatically unless the user has set their @@ -1097,7 +1105,7 @@ def parse(value: Union[bundles.MetadataEntry, bytes, str, pathlib.Path], URL: - >>> #_DOCS_SHOW s = converter.parse('http://midirepository.org/file220/file.mid') + >>> #_DOCS_SHOW s = converter.parse('https://midirepository.org/file220/file.mid') Data is preceded by an identifier such as "tinynotation:" @@ -1178,7 +1186,7 @@ def parse(value: Union[bundles.MetadataEntry, bytes, str, pathlib.Path], forceSource=forceSource, **keywords) elif isinstance(value, pathlib.Path): raise FileNotFoundError(f'Cannot find file in {str(value)}') - elif (isinstance(value, str) and common.findFormatFile(value) is not None): + elif isinstance(value, str) and common.findFormatFile(value) is not None: # assume mistyped file path raise FileNotFoundError(f'Cannot find file in {str(value)}') else: @@ -1967,7 +1975,7 @@ def testParseURL(self): # This file should have been written, above destFp = Converter()._getDownloadFp(e.getRootTempDir(), '.krn', url) - # Hack garbage into it so that we can test whether or not forceSource works + # Hack garbage into it so that we can test whether forceSource works with open(destFp, 'a', encoding='utf-8') as fp: fp.write('all sorts of garbage that Humdrum cannot parse') diff --git a/music21/converter/qmConverter.py b/music21/converter/qmConverter.py index 92bb1350ad..db74532220 100644 --- a/music21/converter/qmConverter.py +++ b/music21/converter/qmConverter.py @@ -41,8 +41,8 @@ def parseData(self, strData, number=None): >>> from music21.converter.qmConverter import QMConverter >>> qmc = QMConverter() >>> qmc.parseData('C D E G C') - >>> s = qmc.stream - >>> s.show('text') + >>> q_stream = qmc.stream + >>> q_stream.show('text') {0.0} {0.0} {0.0} diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index 3b1746e2a8..5f82cd7903 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -16,7 +16,8 @@ parseData method that sets self.stream. ''' # ------------------------------------------------------------------------------ -# Converters are associated classes; they are not subclasses, but most define a parseData() method, +# Converters are associated classes; they are not subclasses, +# but most define a parseData() method, # a parseFile() method, and a .stream attribute or property. import base64 import io @@ -142,7 +143,7 @@ def launch(self, filePath, fmt=None, ''' Opens the appropriate viewer for the file generated by .write() - app is the path to an application to launch. Specify it and/or a launchKey + app is the path to an application to launch. Specify it and/or a launchKey. launchKey is the specific key in .music21rc (such as graphicsPath), etc. to search for the application. If it's not specified then there might be a default one for the converter in self.launchKey. If it can't find it @@ -359,7 +360,7 @@ def show(self, obj, fmt, app=None, subformats=None, **keywords): # pragma: no c helperSubConverter = helperConverter.subConverter # noinspection PyPackageRequirements - from IPython.display import Image, display, HTML # @UnresolvedImport + from IPython.display import Image, display, HTML if helperFormat in ('musicxml', 'xml', 'lilypond', 'lily'): # hack to make musescore excerpts -- fix with a converter class in MusicXML @@ -895,7 +896,7 @@ def parseFile(self, fp: Union[str, pathlib.Path], number=None): archData = arch.getData() c.xmlText = archData c.parseXMLText() - else: # its a file path or a raw musicxml string + else: # it is a file path or a raw musicxml string c.readFile(fp) # movement titles can be stored in more than one place in musicxml @@ -1193,7 +1194,7 @@ def parseFile(self, fp, number=None): otherwise, a :class:`~music21.stream.Score` is returned. If `number` is provided, and this ABC file defines multiple works - with a X: tag, just the specified work will be returned. + with an X: tag, just the specified work will be returned. ''' # environLocal.printDebug(['ConverterABC.parseFile: got number', number]) from music21 import abcFormat @@ -1205,7 +1206,7 @@ def parseFile(self, fp, number=None): af.close() # only create opus if multiple ref numbers - # are defined; if a number is given an opus will no be created + # are defined; if a number is given an opus will not be created if abcHandler.definesReferenceNumbers(): # this creates a Score or Opus object, depending on if a number # is given @@ -1484,7 +1485,7 @@ def testImportMei3(self): ''' When the file uses UTF-16 encoding rather than UTF-8 (which happens if it was exported from - the "sibmei" plug-in for Sibelius. + the "sibmei" plug-in for Sibelius). ''' from unittest import mock # pylint: disable=no-name-in-module with mock.patch('music21.mei.MeiToM21Converter') as mockConv: @@ -1581,7 +1582,7 @@ def testWriteMusicXMLMakeNotation(self): s = s.splitAtDurations(recurse=True)[0] out2 = s.write(makeNotation=False) round_trip_back = converter.parse(out2) - # 4/4 will not be assumed; quarter note will still be split out from 5.0QL + # 4/4 will not be assumed; quarter note will still be split out from 5.0QL, # but it will remain in measure 1 # and there will be no rests in measure 2 self.assertEqual( diff --git a/music21/corpus/__init__.py b/music21/corpus/__init__.py index f850ea5254..5c874e5cec 100644 --- a/music21/corpus/__init__.py +++ b/music21/corpus/__init__.py @@ -289,7 +289,7 @@ def parse(workName, number=None, fileExtensions=None, forceSource=False, - format=None # @ReservedAssignment + format=None ): ''' The most important method call for corpus. @@ -332,7 +332,7 @@ def parse(workName, number=number, fileExtensions=fileExtensions, forceSource=forceSource, - format=format # @ReservedAssignment + format=format ) diff --git a/music21/corpus/chorales.py b/music21/corpus/chorales.py index becd06b4fd..ea0fc41a77 100644 --- a/music21/corpus/chorales.py +++ b/music21/corpus/chorales.py @@ -939,12 +939,15 @@ def prepareList(self): class Iterator: # noinspection SpellCheckingInspection ''' - This is a class for iterating over many Bach Chorales. It is designed to make it easier to use - one of music21's most accessible datasets. It will parse each chorale in the selected + This is a class for iterating over many Bach Chorales. It is designed + to make it easier to use + one of music21's most accessible datasets. It will parse each chorale + in the selected range in a lazy fashion so that a list of chorales need not be parsed up front. To select a range of chorales, first select a .numberingSystem ('riemenschneider', 'bwv', 'kalmus', 'budapest', - 'baerenreiter', or 'title'). Then, set .currentNumber to the lowest number in the range and + 'baerenreiter', or 'title'). Then, set .currentNumber to the lowest + number in the range and .highestNumber to the highest in the range. This can either be done by catalogue number (iterationType = 'number') or by index (iterationType = 'index'). diff --git a/music21/corpus/manager.py b/music21/corpus/manager.py index 778ea33267..fb6131fc77 100644 --- a/music21/corpus/manager.py +++ b/music21/corpus/manager.py @@ -169,7 +169,7 @@ def parse(workName, number=None, fileExtensions=None, forceSource=False, - format=None # @ReservedAssignment + format=None ): filePath = getWork(workName=workName, movementNumber=movementNumber, diff --git a/music21/duration.py b/music21/duration.py index 72f32c9cd3..b28feaf4d2 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -46,15 +46,16 @@ 2 ''' +from collections import namedtuple import contextlib import copy import fractions +from functools import lru_cache import io -import unittest from math import inf, isnan from typing import Union, Tuple, Dict, List, Optional, Iterable, Literal +import unittest -from collections import namedtuple from music21 import prebase @@ -478,8 +479,8 @@ def quarterLengthToTuplet( # try multiples of the tuplet division, from 1 to max - 1 for m in range(1, i): for numberOfDots in POSSIBLE_DOTS_IN_TUPLETS: - tupletMultiplier = fractions.Fraction(common.dotMultiplier(numberOfDots)) - qLenCandidate = qLenBase * m * tupletMultiplier + tupletMultiplier = common.dotMultiplier(numberOfDots) + qLenCandidate = opFrac(qLenBase * m * tupletMultiplier) if qLenCandidate == qLen: tupletDuration = durationTupleFromTypeDots(typeKey, numberOfDots) newTuplet = Tuplet(numberNotesActual=i, @@ -488,8 +489,8 @@ def quarterLengthToTuplet( durationNormal=tupletDuration,) post.append(newTuplet) break - # not looking for these matches will add tuple alternative - # representations; this could be useful + # not looking for these matches will add tuple alternative + # representations; this could be useful if len(post) >= maxToReturn: break if len(post) >= maxToReturn: @@ -891,6 +892,7 @@ def durationTupleFromQuarterLength(ql=1.0) -> DurationTuple: return DurationTuple('inexpressible', 0, ql) +@lru_cache(1024) def durationTupleFromTypeDots(durType='quarter', dots=0): ''' Returns a DurationTuple (which knows its quarterLength) for @@ -2435,7 +2437,7 @@ def _updateQuarterLength(self): # PUBLIC PROPERTIES # @property - def components(self): + def components(self) -> Tuple[DurationTuple, ...]: ''' Returns or sets a tuple of the component DurationTuples of this Duration object @@ -3081,7 +3083,7 @@ def makeTime(self): return self._makeTime @makeTime.setter - def makeTime(self, expr): + def makeTime(self, expr: Literal[True, False, None]): if expr not in (True, False, None): raise ValueError('expr must be True, False, or None') self._makeTime = bool(expr) diff --git a/music21/features/base.py b/music21/features/base.py index c8f5397c7e..9b9f604d82 100644 --- a/music21/features/base.py +++ b/music21/features/base.py @@ -538,7 +538,8 @@ class DataInstance: multiple commonly-used stream representations once, providing rapid processing. ''' # pylint: disable=redefined-builtin - def __init__(self, streamOrPath=None, id=None): # @ReservedAssignment + # noinspection PyShadowingBuiltins + def __init__(self, streamOrPath=None, id=None): if isinstance(streamOrPath, stream.Stream): self.stream = streamOrPath self.streamPath = None @@ -896,7 +897,8 @@ def addMultipleData(self, dataList, classValues, ids=None): self.addData(d, cv, thisId) # pylint: disable=redefined-builtin - def addData(self, dataOrStreamOrPath, classValue=None, id=None): # @ReservedAssignment + # noinspection PyShadowingBuiltins + def addData(self, dataOrStreamOrPath, classValue=None, id=None): ''' Add a Stream, DataInstance, MetadataEntry, or path (Posix or str) to a corpus or local file to this data set. @@ -1069,7 +1071,7 @@ def getString(self, outputFmt='tab'): return outputFormat.getString() # pylint: disable=redefined-builtin - def write(self, fp=None, format=None, includeClassLabel=True): # @ReservedAssignment + def write(self, fp=None, format=None, includeClassLabel=True): ''' Set the output format object. ''' @@ -1644,7 +1646,7 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # def xtestOrangeBayesA(self): # pragma: no cover # '''Using an already created test file with a BayesLearner. # ''' -# import orange # @UnresolvedImport # pylint: disable=import-error +# import orange # pylint: disable=import-error # data = orange.ExampleTable( # '~/music21Ext/mlDataSets/bachMonteverdi-a/bachMonteverdi-a.tab') # classifier = orange.BayesLearner(data) @@ -1656,7 +1658,7 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # def xtestClassifiersA(self): # pragma: no cover # '''Using an already created test file with a BayesLearner. # ''' -# import orange, orngTree # @UnresolvedImport # pylint: disable=import-error +# import orange, orngTree # pylint: disable=import-error # data1 = orange.ExampleTable( # '~/music21Ext/mlDataSets/chinaMitteleuropa-b/chinaMitteleuropa-b1.tab') # @@ -1696,7 +1698,7 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # def xtestClassifiersB(self): # pragma: no cover # '''Using an already created test file with a BayesLearner. # ''' -# import orange, orngTree # @UnresolvedImport # pylint: disable=import-error +# import orange, orngTree # pylint: disable=import-error # data1 = orange.ExampleTable( # '~/music21Ext/mlDataSets/chinaMitteleuropa-b/chinaMitteleuropa-b1.tab') # @@ -1742,7 +1744,7 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # This test shows how to compare four classifiers; replace the file path # with a path to the .tab data file. # ''' -# import orange, orngTree # @UnresolvedImport # pylint: disable=import-error +# import orange, orngTree # pylint: disable=import-error # data = orange.ExampleTable( # '~/music21Ext/mlDataSets/bachMonteverdi-a/bachMonteverdi-a.tab') # @@ -1774,7 +1776,7 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # # # def xtestOrangeClassifierTreeLearner(self): # pragma: no cover -# import orange, orngTree # @UnresolvedImport # pylint: disable=import-error +# import orange, orngTree # pylint: disable=import-error # data = orange.ExampleTable( # '~/music21Ext/mlDataSets/bachMonteverdi-a/bachMonteverdi-a.tab') # diff --git a/music21/lily/lilyObjects.py b/music21/lily/lilyObjects.py index 6a2695dbc7..dbbef51483 100644 --- a/music21/lily/lilyObjects.py +++ b/music21/lily/lilyObjects.py @@ -1284,7 +1284,7 @@ class LyPrefixCompositeMusic(LyObject): | re_rhythmed_music ''' # pylint: disable=redefined-builtin - def __init__(self, type=None, genericPrefixMusicScm=None, # @ReservedAssignment + def __init__(self, type=None, genericPrefixMusicScm=None, simpleString=None, optionalId=None, optionalContextMod=None, music=None, fraction=None, repeatedMusic=None, pitchAlsoInChords1=None, pitchAlsoInChords2=None, diff --git a/music21/lily/translate.py b/music21/lily/translate.py index 02d68275a0..cc2c75e489 100644 --- a/music21/lily/translate.py +++ b/music21/lily/translate.py @@ -851,7 +851,7 @@ def lyPrefixCompositeMusicFromStream( self, streamIn, contextType=None, - type=None, # @ReservedAssignment + type=None, beforeMatter=None ): # noinspection PyShadowingNames @@ -2421,7 +2421,7 @@ def writeLyFile(self, ext='', fp=None): return self.tempName # noinspection PyShadowingBuiltins - def runThroughLily(self, format=None, # @ReservedAssignment + def runThroughLily(self, format=None, backend=None, fileName=None, skipWriting=False): r''' creates a .ly file from self.topLevelObject via .writeLyFile diff --git a/music21/metadata/caching.py b/music21/metadata/caching.py index 269bc7ce09..e32114596e 100644 --- a/music21/metadata/caching.py +++ b/music21/metadata/caching.py @@ -388,7 +388,7 @@ def process_serial(jobs): # ----------------------------------------------------------------------------- -class WorkerProcess(multiprocessing.Process): # @UndefinedVariable pylint: disable=inherit-non-class +class WorkerProcess(multiprocessing.Process): # pylint: disable=inherit-non-class ''' A worker process for use by the multi-threaded metadata-caching job processor. diff --git a/music21/meter/tools.py b/music21/meter/tools.py index 2ea79b17e7..49135e3042 100644 --- a/music21/meter/tools.py +++ b/music21/meter/tools.py @@ -6,7 +6,7 @@ # Authors: Christopher Ariza # Michael Scott Cuthbert # -# Copyright: Copyright © 2009-2012, 2015, 2021 Michael Scott Cuthbert +# Copyright: Copyright © 2009-2022 Michael Scott Cuthbert # and the music21 Project # License: BSD, see license.txt # ----------------------------------------------------------------------------- @@ -219,7 +219,7 @@ def fractionToSlashMixed(fList: NumDenomTuple) -> Tuple[Tuple[str, int], ...]: def fractionSum(numDenomTuple: NumDenomTuple) -> NumDenom: ''' Given a tuple of tuples of numerator and denominator, - find the sum; does NOT reduce to lowest terms. + find the sum; does NOT reduce to its lowest terms. >>> from music21.meter.tools import fractionSum >>> fractionSum(((3, 8), (5, 8), (1, 8))) @@ -234,8 +234,8 @@ def fractionSum(numDenomTuple: NumDenomTuple) -> NumDenom: (0, 1) This method might seem like an easy place to optimize and simplify - by just doing a fractions.Fraction() sum (I tried!), but not reducing to lowest - terms is a feature of this method. 3/8 + 3/8 = 6/8, not 3/4: + by just doing a fractions.Fraction() sum (I tried!), but not reducing to + its lowest terms is a feature of this method. 3/8 + 3/8 = 6/8, not 3/4: >>> fractionSum(((3, 8), (3, 8))) (6, 8) @@ -610,7 +610,7 @@ def divisionOptionsAlgo(n, d) -> MeterOptions: # add src representation opts.append((f'{n}/{d}',)) # additive multiples with the same denominators - # add to opts in-place + # add to "opts" in-place opts.extend(divisionOptionsAdditiveMultiples(n, d)) # additive multiples with smaller denominators # only doing this for numerators of 1 for now diff --git a/music21/midi/__init__.py b/music21/midi/__init__.py index a3ace91c06..046964a0c6 100644 --- a/music21/midi/__init__.py +++ b/music21/midi/__init__.py @@ -463,10 +463,9 @@ class MidiEvent(prebase.ProtoM21Object): ''' # pylint: disable=redefined-builtin - def __init__(self, track: Optional['music21.midi.MidiTrack'] = None, - type=None, # @ReservedAssignment + type=None, time: int = 0, channel: Optional[int] = None): self.track: Optional['music21.midi.MidiTrack'] = track # a MidiTrack object diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 17135045c9..e52ff6a90e 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -2389,11 +2389,10 @@ def getSupports(self): ''' - # pylint: disable=redefined-builtin - def getSupport(attribute, type, value, element): # @ReservedAssignment + def getSupport(attribute, supports_type, value, element): su = Element('supports') su.set('attribute', attribute) - su.set('type', type) + su.set('type', supports_type) su.set('value', value) su.set('element', element) return su diff --git a/music21/spanner.py b/music21/spanner.py index f7a59f297b..db6ceeb05f 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -455,7 +455,7 @@ def addSpannedElements( self.spannerStorage.coreElementsChanged() - def hasSpannedElement(self, spannedElement: Music21Object) -> bool: + def hasSpannedElement(self, spannedElement: base.Music21Object) -> bool: ''' Return True if this Spanner has the spannedElement. @@ -804,9 +804,9 @@ def getBySpannedElement(self, spannedElement): def replaceSpannedElement( self, - old: Music21Object, - new: Music21Object - ) -> List['Spanner']: + old: base.Music21Object, + new: base.Music21Object + ) -> List[Spanner]: # noinspection PyShadowingNames ''' Given a spanner spannedElement (an object), replace all old spannedElements diff --git a/music21/tie.py b/music21/tie.py index 4a5832f046..4be5bf53d2 100644 --- a/music21/tie.py +++ b/music21/tie.py @@ -102,7 +102,7 @@ class Tie(prebase.ProtoM21Object, SlottedObjectMixin): VALID_TIE_TYPES = ('start', 'stop', 'continue', 'let-ring', 'continue-let-ring') # pylint: disable=redefined-builtin - def __init__(self, type='start'): # @ReservedAssignment + def __init__(self, type='start'): # super().__init__() # no need for ProtoM21Object or SlottedObjectMixin if type not in self.VALID_TIE_TYPES: raise TieException( From fdec50e1030f033957a5f686289940d23ac2ebb9 Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 15:35:23 -1000 Subject: [PATCH 5/8] Lint and mypy --- documentation/nbvalNotebook.py | 2 +- music21/alpha/analysis/fixer.py | 6 +- music21/braille/lookup.py | 87 ++++++++++++++------------- music21/chord/__init__.py | 15 ++--- music21/chord/tables.py | 24 +++++++- music21/common/types.py | 5 +- music21/duration.py | 71 +++++++++------------- music21/graph/findPlot.py | 19 +++--- music21/midi/translate.py | 68 ++++++++++++--------- music21/musicxml/partStaffExporter.py | 8 ++- music21/musicxml/xmlToM21.py | 2 +- music21/search/segment.py | 6 +- music21/stream/base.py | 7 ++- music21/stream/iterator.py | 3 +- music21/stream/makeNotation.py | 43 ++++++------- music21/stream/streamStatus.py | 6 +- music21/voiceLeading.py | 10 +-- 17 files changed, 208 insertions(+), 174 deletions(-) diff --git a/documentation/nbvalNotebook.py b/documentation/nbvalNotebook.py index f5fa7f0c30..4c6dfc13c0 100644 --- a/documentation/nbvalNotebook.py +++ b/documentation/nbvalNotebook.py @@ -11,7 +11,7 @@ # noinspection PyPackageRequirements import pytest # pylint: disable=unused-import,import-error # noinspection PyPackageRequirements -import nbval ## pylint: disable=unused-import,import-error +import nbval # pylint: disable=unused-import,import-error from music21 import environment from music21 import common diff --git a/music21/alpha/analysis/fixer.py b/music21/alpha/analysis/fixer.py index 4f10fde366..6c508b53a2 100644 --- a/music21/alpha/analysis/fixer.py +++ b/music21/alpha/analysis/fixer.py @@ -63,7 +63,7 @@ def fix(self): for (midiRef, omrRef, op) in self.changes: if self.checkIfNoteInstance(midiRef, omrRef) is False: continue - # if the are the same, don't bother to try changing it + # if they are the same, don't bother to try changing it # 3 is the number of noChange Ops if isinstance(op, aligner.ChangeOps) and op == aligner.ChangeOps.NoChange: continue @@ -246,7 +246,7 @@ def fix(self): # if they're not notes, don't bother with rest if self.checkIfNoteInstance(midiRef, omrRef) is False: continue - # if the are the same, don't bother to try changing it + # if they are the same, don't bother to try changing it # 3 is the number of noChange Ops if isinstance(op, aligner.ChangeOps) and op == aligner.ChangeOps.NoChange: continue @@ -394,7 +394,7 @@ def addOrnament(self, def fix(self: _T, *, show=False, inPlace=True) -> Optional[_T]: ''' - Corrects missed ornaments in omr stream according to mid stream + Corrects missed ornaments in omrStream according to midiStream :param show: Whether to show results :param inPlace: Whether to make changes to own omr stream or return a new OrnamentFixer with changes diff --git a/music21/braille/lookup.py b/music21/braille/lookup.py index 784efbf00d..1f3210a75b 100644 --- a/music21/braille/lookup.py +++ b/music21/braille/lookup.py @@ -24,7 +24,7 @@ New International Manual of Braille Music Notation (by Bettye Krolick), which we will cite as "Krolick" or "krolick". ''' - +from typing import Dict, Union import itertools _DOC_IGNORE_MODULE_OR_PACKAGE = True @@ -280,48 +280,49 @@ def makePitchNameToNotes(): 'decresc.': _B[345] + _B[145] + _B[15] + _B[14] + _B[1235] + _B[3], 'decr.': _B[345] + _B[145] + _B[15] + _B[14] + _B[1235] + _B[3]} -alphabet = {'a': _B[1], - 'b': _B[12], - 'c': _B[14], - 'd': _B[145], - 'e': _B[15], - 'f': _B[124], - 'g': _B[1245], - 'h': _B[125], - 'i': _B[24], - 'j': _B[245], - 'k': _B[13], - 'l': _B[123], - 'm': _B[134], - 'n': _B[1345], - 'o': _B[135], - 'p': _B[1234], - 'q': _B[12345], - 'r': _B[1235], - 's': _B[234], - 't': _B[2345], - 'u': _B[136], - 'v': _B[1236], - 'w': _B[2456], - 'x': _B[1346], - 'y': _B[13456], - 'z': _B[1356], - ' ': _B[0], - '!': _B[235], - "'": _B[3], - ',': _B[2], - '-': _B[356], - '.': _B[256], - '/': _B[34], - ':': _B[25], - '?': _B[236], - '(': _B[2356], - ')': _B[2356], - '^': _B[4], # substitute for accent mark - '[': _B[6] + _B[2356], - ']': _B[2356] + _B[3], - '*': _B[35] + _B[35], - } +alphabet: Dict[Union[str, int], str] = { + 'a': _B[1], + 'b': _B[12], + 'c': _B[14], + 'd': _B[145], + 'e': _B[15], + 'f': _B[124], + 'g': _B[1245], + 'h': _B[125], + 'i': _B[24], + 'j': _B[245], + 'k': _B[13], + 'l': _B[123], + 'm': _B[134], + 'n': _B[1345], + 'o': _B[135], + 'p': _B[1234], + 'q': _B[12345], + 'r': _B[1235], + 's': _B[234], + 't': _B[2345], + 'u': _B[136], + 'v': _B[1236], + 'w': _B[2456], + 'x': _B[1346], + 'y': _B[13456], + 'z': _B[1356], + ' ': _B[0], + '!': _B[235], + "'": _B[3], + ',': _B[2], + '-': _B[356], + '.': _B[256], + '/': _B[34], + ':': _B[25], + '?': _B[236], + '(': _B[2356], + ')': _B[2356], + '^': _B[4], # substitute for accent mark + '[': _B[6] + _B[2356], + ']': _B[2356] + _B[3], + '*': _B[35] + _B[35], +} alphabet.update(numbersUpper) chordSymbols = { diff --git a/music21/chord/__init__.py b/music21/chord/__init__.py index b5dabfbbc5..c929ee69d3 100644 --- a/music21/chord/__init__.py +++ b/music21/chord/__init__.py @@ -4469,7 +4469,8 @@ def transpose(self, value, *, inPlace=False): # PUBLIC PROPERTIES # - @property + # see https://github.com/python/mypy/issues/1362 + @property # type: ignore @cacheMethod def chordTablesAddress(self): ''' @@ -4503,7 +4504,7 @@ def chordTablesAddress(self): return tables.ChordTableAddress(0, 0, 0, 0) - @property + @property # type: ignore @cacheMethod def commonName(self): ''' @@ -4797,7 +4798,7 @@ def duration(self, durationObj): # need to permit Duration object assignment here raise ChordException(f'this must be a Duration object, not {durationObj}') - @property + @property # type: ignore @cacheMethod def fifth(self) -> Optional[pitch.Pitch]: ''' @@ -5110,7 +5111,7 @@ def notes(self, newNotes): self._notes.clear() self.add(newNotes, runSort=False) - @property + @property # type: ignore @cacheMethod def normalOrder(self): ''' @@ -5504,7 +5505,7 @@ def primeFormString(self) -> str: return Chord.formatVectorString(self.primeForm) - @property + @property # type: ignore @cacheMethod def quality(self): ''' @@ -5690,7 +5691,7 @@ def scaleDegrees(self): degrees.append(tupleKey) return degrees - @property + @property # type: ignore @cacheMethod def seventh(self): ''' @@ -5717,7 +5718,7 @@ def seventh(self): except ChordException: return None - @property + @property # type: ignore @cacheMethod def third(self) -> Optional[pitch.Pitch]: ''' diff --git a/music21/chord/tables.py b/music21/chord/tables.py index 3d6f50e799..d4047d609f 100644 --- a/music21/chord/tables.py +++ b/music21/chord/tables.py @@ -53,6 +53,7 @@ class ChordTablesException(exceptions21.Music21Exception): # is symmetrical under inversion. t1 = ((0,), (0,0,0,0,0,0), (1,1,1,1,11,11,11,11), 0) # 1-1 monad = (None, t1) +del t1 t1 = ((0,1), (1,0,0,0,0,0), (1,1,0,0,9,9,8,8), 0) # 2-1 t2 = ((0,2), (0,1,0,0,0,0), (1,1,1,1,9,9,9,9), 0) # 2-2 @@ -61,6 +62,7 @@ class ChordTablesException(exceptions21.Music21Exception): t5 = ((0,5), (0,0,0,0,1,0), (1,1,0,0,9,9,8,8), 0) # 2-5 t6 = ((0,6), (0,0,0,0,0,1), (2,2,2,2,10,10,10), 0) # 2-6 diad = (None, t1, t2, t3, t4, t5, t6) +del t1, t2, t3, t4, t5, t6 t1 = ((0,1,2), (2,1,0,0,0,0), (1,1,0,0,7,7,4,4), 0) # 3-1 t2 = ((0,1,3), (1,1,1,0,0,0), (1,0,0,0,5,6,5,5), 0) # 3-2 @@ -75,6 +77,7 @@ class ChordTablesException(exceptions21.Music21Exception): t11 = ((0,3,7), (0,0,1,1,1,0), (1,0,0,0,5,6,5,5), 0) # 3-11 t12 = ((0,4,8), (0,0,0,3,0,0), (3,3,3,3,9,9,9,9), 0) # 3-12 trichord = (None, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) +del t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 t1 = ((0,1,2,3), (3,2,1,0,0,0), (1,1,0,0,5,5,1,1), 0) # 4-1 t2 = ((0,1,2,4), (2,2,1,1,0,0), (1,0,0,0,3,4,1,1), 0) # 4-2 @@ -110,7 +113,9 @@ class ChordTablesException(exceptions21.Music21Exception): t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, t25, t26, t27, t28, t29 ) - +del t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 +del t13, t14, t15, t16, t17, t18, t19, t20, t21, t22 +del t23, t24, t25, t26, t27, t28, t29 t1 = ((0,1,2,3,4), (4,3,2,1,0,0), (1,1,0,0,3,3,0,0), 0) # 5-1 t2 = ((0,1,2,3,5), (3,3,2,1,1,0), (1,0,0,0,1,2,1,1), 0) # 5-2 @@ -156,6 +161,10 @@ class ChordTablesException(exceptions21.Music21Exception): t20, t21, t22, t23, t24, t25, t26, t27, t28, t29, t30, t31, t32, t33, t34, t35, t36, t37, t38 ) +del t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 +del t13, t14, t15, t16, t17, t18, t19, t20, t21, t22 +del t23, t24, t25, t26, t27, t28, t29 +del t30, t31, t32, t33, t34, t35, t36, t37, t38 t1 = ((0,1,2,3,4,5), (5,4,3,2,1,0), (1,1,0,0,1,1,0,0), 0) # 6-1 A t2 = ((0,1,2,3,4,6), (4,4,3,2,1,1), (1,0,0,0,0,1,0,0), 0) # 6-2 @@ -215,6 +224,10 @@ class ChordTablesException(exceptions21.Music21Exception): t40, t41, t42, t43, t44, t45, t46, t47, t48, t49, t50, ) +del t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 +del t13, t14, t15, t16, t17, t18, t19, t20, t21, t22 +del t23, t24, t25, t26, t27, t28, t29 +del t30, t31, t32, t33, t34, t35, t36, t37, t38 del t39, t40, t41, t42, t43, t44, t45, t46, t47, t48, t49, t50 t1 = ((0,1,2,3,4,5, 6), (6,5,4,3,2,1), (1,1,0,0,0,0,0,0), 0) # 7-1 @@ -261,6 +274,9 @@ class ChordTablesException(exceptions21.Music21Exception): t20, t21, t22, t23, t24, t25, t26, t27, t28, t29, t30, t31, t32, t33, t34, t35, t36, t37, t38 ) +del t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 +del t13, t14, t15, t16, t17, t18, t19, t20, t21, t22 +del t23, t24, t25, t26, t27, t28, t29 del t30, t31, t32, t33, t34, t35, t36, t37, t38 t1 = ((0,1,2,3,4,5,6, 7), (7,6,5,4,4,2), (1,1,0,0,0,0,0,0), 0 ) # 8-1 @@ -297,6 +313,7 @@ class ChordTablesException(exceptions21.Music21Exception): t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, t25, t26, t27, t28, t29 ) +del t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 del t13, t14, t15, t16, t17, t18, t19 del t20, t21, t22, t23, t24, t25, t26, t27, t28, t29 @@ -313,7 +330,7 @@ class ChordTablesException(exceptions21.Music21Exception): t11 = ((0,1,2,3,5,6,7,9,10), (6,6,7,7,7,3), (1,0,0,0,0,0,0,0), 0) # 9-11 t12 = ((0,1,2,4,5,6,8,9,10), (6,6,6,9,6,3), (3,3,3,3,0,0,0,0), 0) # 9-12 nonachord = (None, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) -del t7, t8, t9, t10, t11, t12 +del t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 t1 = ((0,1,2,3,4,5,6,7,8, 9), (9,8,8,8,8,4), (1,1,0,0,0,0,0,0), 0) # 10-1 t2 = ((0,1,2,3,4,5,6,7,8,10), (8,9,8,8,8,4), (1,1,1,1,0,0,0,0), 0) # 10-2 @@ -322,10 +339,11 @@ class ChordTablesException(exceptions21.Music21Exception): t5 = ((0,1,2,3,4,5,7,8,9,10), (8,8,8,8,9,4), (1,1,0,0,0,0,0,0), 0) # 10-5 t6 = ((0,1,2,3,4,6,7,8,9,10), (8,8,8,8,8,5), (2,2,2,2,0,0,0,0), 0) # 10-6 decachord = (None, t1, t2, t3, t4, t5, t6) -del t2, t3, t4, t5, t6 +del t1, t2, t3, t4, t5, t6 t1 = ((0,1,2,3,4,5,6,7,8,9,10), (10,10,10,10,10,5), (1,1,1,1,0,0,0,0), 0) # 11-1 undecachord = (None, t1) +del t1 t1 = ((0,1,2,3,4,5,6,7,8,9,10,11), (12,12,12,12,12,6), (12,12,12,12,0,0,0,0), 0) # 12-1 dodecachord = (None, t1) diff --git a/music21/common/types.py b/music21/common/types.py index 8ffcb566fe..e8e8e1dcf5 100644 --- a/music21/common/types.py +++ b/music21/common/types.py @@ -9,10 +9,13 @@ # License: BSD, see license.txt # ------------------------------------------------------------------------------ from fractions import Fraction -from typing import Union +from typing import Union, TypeVar from music21.common.enums import OffsetSpecial OffsetQL = Union[float, Fraction] OffsetQLSpecial = Union[float, Fraction, OffsetSpecial] OffsetQLIn = Union[int, float, Fraction] + +StreamType = TypeVar('StreamType', bound='music21.stream.Stream') +M21ObjType = TypeVar('M21ObjType', bound='music21.base.Music21Object') diff --git a/music21/duration.py b/music21/duration.py index b28feaf4d2..9e24b482f6 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -814,48 +814,37 @@ def convertTypeToNumber(dType: str) -> float: # ----------------------------------------------------------------------------------- -DurationTuple = namedtuple('DurationTuple', 'type dots quarterLength') +class DurationTuple(namedtuple('DurationTuple', 'type dots quarterLength')): + def augmentOrDiminish(self, amountToScale): + return durationTupleFromQuarterLength(self.quarterLength * amountToScale) + @property + def ordinal(self): + ''' + Converts type to an ordinal number where maxima = 1 and 1024th = 14; + whole = 4 and quarter = 6. Based on duration.ordinalTypeFromNum -def _augmentOrDiminishTuple(self, amountToScale): - return durationTupleFromQuarterLength(self.quarterLength * amountToScale) - - -DurationTuple.augmentOrDiminish = _augmentOrDiminishTuple # type: ignore[attr-defined] - - -del _augmentOrDiminishTuple - - -def _durationTupleOrdinal(self): - ''' - Converts type to an ordinal number where maxima = 1 and 1024th = 14; - whole = 4 and quarter = 6. Based on duration.ordinalTypeFromNum - - >>> a = duration.DurationTuple('whole', 0, 4.0) - >>> a.ordinal - 4 - - >>> b = duration.DurationTuple('maxima', 0, 32.0) - >>> b.ordinal - 1 - - >>> c = duration.DurationTuple('1024th', 0, 1/256) - >>> c.ordinal - 14 - ''' - ordinalFound = None - for i in range(len(ordinalTypeFromNum)): - if self.type == ordinalTypeFromNum[i]: - ordinalFound = i - break - if ordinalFound is None: - raise DurationException( - f'Could not determine durationNumber from {ordinalFound}') - return ordinalFound + >>> a = duration.DurationTuple('whole', 0, 4.0) + >>> a.ordinal + 4 + >>> b = duration.DurationTuple('maxima', 0, 32.0) + >>> b.ordinal + 1 -DurationTuple.ordinal = property(_durationTupleOrdinal) # type: ignore[attr-defined] + >>> c = duration.DurationTuple('1024th', 0, 1/256) + >>> c.ordinal + 14 + ''' + ordinalFound = None + for i in range(len(ordinalTypeFromNum)): + if self.type == ordinalTypeFromNum[i]: + ordinalFound = i + break + if ordinalFound is None: + raise DurationException( + f'Could not determine durationNumber from {ordinalFound}') + return ordinalFound _durationTupleCacheTypeDots: Dict[Tuple[str, int], DurationTuple] = {} @@ -2234,7 +2223,7 @@ def getGraceDuration( if c_type == 'zero': c_type = 'eighth' newComponents.append(DurationTuple(c.type, c.dots, 0.0)) - gd.components = newComponents # set new components + gd.components = tuple(newComponents) # set new components gd.linked = False gd.type = new_type gd.quarterLength = 0.0 @@ -2977,7 +2966,7 @@ def type(self, value: str): if self.linked is True: nt = durationTupleFromTypeDots(value, self.dots) - self.components = [nt] + self.components = (nt,) self._quarterLengthNeedsUpdating = True self.expressionIsInferred = False self.informClient() @@ -3833,7 +3822,7 @@ def testExceptions(self): # ------------------------------------------------------------------------------- # define presented order in documentation -_DOC_ORDER = [Duration, Tuplet, convertQuarterLengthToType, TupletFixer] +_DOC_ORDER: List[type] = [Duration, Tuplet, convertQuarterLengthToType, TupletFixer] if __name__ == '__main__': diff --git a/music21/graph/findPlot.py b/music21/graph/findPlot.py index 9288b37265..8c78c0893e 100644 --- a/music21/graph/findPlot.py +++ b/music21/graph/findPlot.py @@ -15,7 +15,7 @@ import collections import types import unittest - +from typing import List from music21.graph import axis from music21.graph import plot @@ -32,14 +32,15 @@ # all formats need to be here, and first for each row must match a graphType. -FORMAT_SYNONYMS = [('horizontalbar', 'bar', 'horizontal', 'pianoroll', 'piano'), - ('histogram', 'histo', 'count'), - ('scatter', 'point'), - ('scatterweighted', 'weightedscatter', 'weighted'), - ('3dbars', '3d'), - ('colorgrid', 'grid', 'window', 'windowed'), - ('horizontalbarweighted', 'barweighted', 'weightedbar') - ] # type: List[Tuple[str]] +FORMAT_SYNONYMS: List[Tuple[str, ...]] = [ + ('horizontalbar', 'bar', 'horizontal', 'pianoroll', 'piano'), + ('histogram', 'histo', 'count'), + ('scatter', 'point'), + ('scatterweighted', 'weightedscatter', 'weighted'), + ('3dbars', '3d'), + ('colorgrid', 'grid', 'window', 'windowed'), + ('horizontalbarweighted', 'barweighted', 'weightedbar') +] # define co format strings FORMATS = [syn[0] for syn in FORMAT_SYNONYMS] diff --git a/music21/midi/translate.py b/music21/midi/translate.py index 8eff8658dd..3041a42fc6 100644 --- a/music21/midi/translate.py +++ b/music21/midi/translate.py @@ -16,7 +16,7 @@ import unittest import math import copy -from typing import Optional, List, Tuple, Dict, Union, Any +from typing import Optional, List, Tuple, Dict, Union, Any, TypeVar, Type import warnings from music21 import chord @@ -40,6 +40,8 @@ PERCUSSION_MAPPER = PercussionMapper() +NotRestType = TypeVar('NotRestType', bound=note.NotRest) + # ------------------------------------------------------------------------------ class TranslateException(exceptions21.Music21Exception): pass @@ -208,7 +210,7 @@ def getStartEvents(mt=None, channel=1, instrumentObj=None): # additional allocation of instruments may happen elsewhere # this may lead to two program changes happening at time zero - # however, this assures that the program change happens before the + # however, this assures that the program change happens before # the clearing of the pitch bend data if instrumentObj is not None and instrumentObj.midiProgram is not None: sub = instrumentToMidiEvents(instrumentObj, includeDeltaTime=True, @@ -273,15 +275,15 @@ def music21ObjectToMidiFile( # ------------------------------------------------------------------------------ # Notes -def _constructOrUpdateNotRest( +def _constructOrUpdateNotRestSubclass( eOn: 'music21.midi.MidiEvent', tOn: int, tOff: int, ticksPerQuarter: int, *, - inputM21: Optional[note.NotRest] = None, - preferredClass: type = note.Note, -) -> note.NotRest: + inputM21: Optional[NotRestType] = None, + preferredClass: Type[NotRestType] = note.Note, +) -> NotRestType: ''' Construct (or edit the duration of) a NotRest subclass, usually a note.Note (or a chord.Chord if provided to `preferredClass`). @@ -321,8 +323,8 @@ def _constructOrUpdateNotRest( def midiEventsToNote( eventList, ticksPerQuarter=None, - inputM21: Optional[note.NotRest] = None, -) -> Optional[note.NotRest]: + inputM21: Optional[NotRestType] = None, +) -> Optional[NotRestType]: # noinspection PyShadowingNames ''' Convert from a list of midi.DeltaTime and midi.MidiEvent objects to a music21 Note. @@ -425,8 +427,14 @@ def midiEventsToNote( else: raise TranslateException(f'cannot handle MIDI event list in the form: {eventList!r}') - nr = _constructOrUpdateNotRest( - eOn, tOn, tOff, ticksPerQuarter, inputM21=inputM21, preferredClass=note.Note) + nr = _constructOrUpdateNotRestSubclass( + eOn, + tOn, + tOff, + ticksPerQuarter, + inputM21=inputM21, + preferredClass=note.Note + ) if isinstance(nr, note.Note): nr.pitch.midi = eOn.pitch @@ -622,7 +630,7 @@ def midiEventsToChord( firstOn: Optional['music21.midi.MidiEvent'] = None any_channel_10 = False # this is a format provided by the Stream conversion of - # midi events; it pre groups events for a chord together in nested pairs + # midi events; it pre-groups events for a chord together in nested pairs # of abs start time and the event object if isinstance(eventList, list) and eventList and isinstance(eventList[0], tuple): # pairs of pairs @@ -668,8 +676,14 @@ def midiEventsToChord( preferredClass = percussion.PercussionChord else: preferredClass = chord.Chord - c: chord.ChordBase = _constructOrUpdateNotRest( - firstOn, tOn, tOff, ticksPerQuarter, inputM21=inputM21, preferredClass=preferredClass) + c: chord.ChordBase = _constructOrUpdateNotRestSubclass( + firstOn, + tOn, + tOff, + ticksPerQuarter, + inputM21=inputM21, + preferredClass=preferredClass + ) if isinstance(c, percussion.PercussionChord): # Construct note.Unpitched objects @@ -848,12 +862,12 @@ def midiEventsToInstrument(eventList): The percussion map will be used if the channel is 10: >>> me.channel = 10 - >>> i = midi.translate.midiEventsToInstrument(me) - >>> i + >>> instrumentObj = midi.translate.midiEventsToInstrument(me) + >>> instrumentObj - >>> i.midiChannel # 0-indexed in music21 + >>> instrumentObj.midiChannel # 0-indexed in music21 9 - >>> i.midiProgram # 0-indexed in music21 + >>> instrumentObj.midiProgram # 0-indexed in music21 53 ''' from music21 import midi as midiModule @@ -1135,7 +1149,7 @@ def midiEventsToTempo(eventList): return mm -def tempoToMidiEvents(tempoIndication, includeDeltaTime=True): +def tempoToMidiEvents(tempoIndication: 'music21.tempo.TempoIndication', includeDeltaTime=True): # noinspection PyShadowingNames r''' Given any TempoIndication, convert it to list of :class:`~music21.midi.MidiEvent` @@ -1182,7 +1196,7 @@ def tempoToMidiEvents(tempoIndication, includeDeltaTime=True): True ''' from music21 import midi as midiModule - if tempoIndication.number is None: + if not hasattr(tempoIndication, 'number') or tempoIndication.number is None: return mt = None # use a midi track set to None eventList = [] @@ -1258,7 +1272,7 @@ def getPacketFromMidiEvent( # allocate channel later # post['channel'] = None if midiEvent.type != midiModule.ChannelVoiceMessages.NOTE_OFF and obj is not None: - # store duration so as to calculate when the + # store duration to calculate when the # channel/pitch bend can be freed post['duration'] = durationToMidiTicks(obj.duration) # note offs will have the same object ref, and seem like the have a @@ -1337,7 +1351,7 @@ def streamToPackets( Then, packets to events is called. ''' from music21 import midi as midiModule - # store all events by offset by offset without delta times + # store all events by offset without delta times # as (absTime, event) packetsByOffset = [] lastInstrument = None @@ -1380,7 +1394,7 @@ def streamToPackets( lastInstrument=lastInstrument, ) elementPackets.append(p) - # if its a note_off, use the duration to shift offset + # if it is a note_off, use the duration to shift offset # midi events have already been created; else: p = getPacketFromMidiEvent( @@ -1506,7 +1520,7 @@ def assignPacketsToChannels( # only add if unique if usedChannel not in channelExclude: channelExclude.append(usedChannel) - # or if this event has shift, then we can exclude + # or if this event has a shift, then we can exclude # the channel already used without a shift elif centShift: if usedChannel not in channelExclude: @@ -1595,7 +1609,7 @@ def assignPacketsToChannels( # environLocal.printDebug(['foundChannels', foundChannels]) # environLocal.printDebug(['usedTracks', usedTracks]) - # post processing of entire packet collection + # post-processing of entire packet collection # for all used channels, create a zero pitch bend at time zero # for ch in foundChannels: # for each track, places a pitch bend in its initChannel @@ -1976,7 +1990,7 @@ def midiTrackToStream( # collect notes with similar start times into chords # create a composite list of both notes and chords # composite = [] - chordSub = None + chordSub: Optional[List['music21.midi.MidiEvent']] = None i = 0 iGathered = [] # store a list of indexes of gathered values put into chords voicesRequired = False @@ -2034,7 +2048,7 @@ def midiTrackToStream( # if we can add more elements to this chord group else: # no more matches; assuming chordSub tones are contiguous break - # this comparison must be outside of j loop, as the case where we + # this comparison must be outside the j loop, as the case where we # have the last note in a list of notes and the j loop does not # execute; chordSub will be None if chordSub is not None: @@ -2871,7 +2885,7 @@ def midiFileToStream( if 'quantizePost' in keywords: quantizePost = keywords.pop('quantizePost') - # create a stream for each tracks + # create a stream for each track # may need to check if tracks actually have event data midiTracksToStreams(mf.tracks, ticksPerQuarter=mf.ticksPerQuarterNote, diff --git a/music21/musicxml/partStaffExporter.py b/music21/musicxml/partStaffExporter.py index d85ffb81c8..25b5a6c60c 100644 --- a/music21/musicxml/partStaffExporter.py +++ b/music21/musicxml/partStaffExporter.py @@ -19,6 +19,7 @@ from xml.etree.ElementTree import Element, SubElement, Comment from music21.common.misc import flattenList +from music21.common.types import M21ObjType from music21.key import KeySignature from music21.layout import StaffGroup from music21.meter import TimeSignature @@ -516,13 +517,14 @@ def setEarliestAttributesAndClefsPartStaff(self, group: StaffGroup): ''' - def isMultiAttribute(m21Class, comparison: str = '__eq__') -> bool: + def isMultiAttribute(m21Class: M21ObjType, + comparison: str = '__eq__') -> bool: ''' Return True if any first instance of m21Class in any subsequent staff in this StaffGroup does not compare to the first instance of that class in the earliest staff where found (not necessarily the first) using `comparison`. ''' - initialM21Instance: Optional[m21Class] = None + initialM21Instance: Optional[M21ObjType] = None # noinspection PyShadowingNames for ps in group: # ps okay to reuse. if initialM21Instance is None: @@ -548,7 +550,7 @@ def isMultiAttribute(m21Class, comparison: str = '__eq__') -> bool: # Initial PartStaff in group: find earliest mxAttributes, set clef #1 and if initialPartStaffRoot is None: initialPartStaffRoot = self.getRootForPartStaff(ps) - mxAttributes: Element = initialPartStaffRoot.find('measure/attributes') + mxAttributes = initialPartStaffRoot.find('measure/attributes') clef1: Optional[Element] = mxAttributes.find('clef') if clef1 is not None: clef1.set('number', '1') diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py index 5a182cdbb0..6d6d2578b5 100644 --- a/music21/musicxml/xmlToM21.py +++ b/music21/musicxml/xmlToM21.py @@ -4260,7 +4260,7 @@ def xmlToTuplets(self, mxNote): remainingTupletAmountToAccountFor = t.tupletMultiplier() timeModTup = t - returnTuplets = [None] * 8 # type: List[Optional['music21.duration.Tuplet']] + returnTuplets: List[Optional[duration.Tuplet]] = [None] * 8 removeFromActiveTuplets = set() # a set of tuplets to set to stop... diff --git a/music21/search/segment.py b/music21/search/segment.py index 86e93a2f68..ab0808bf71 100644 --- a/music21/search/segment.py +++ b/music21/search/segment.py @@ -33,6 +33,7 @@ from collections import OrderedDict from functools import partial +from typing import List from music21 import common from music21 import converter @@ -172,6 +173,7 @@ def indexScoreFilePaths(scoreFilePaths, *args, runMulticore=True, **keywords): + # noinspection PyShadowingNames ''' Returns a dictionary of the lists from indexScoreParts for each score in scoreFilePaths @@ -290,6 +292,7 @@ def getDifflibOrPyLev( smObject = difflib.SequenceMatcher(junk, '', seq2) else: try: + # noinspection PyPackageRequirements from Levenshtein import StringMatcher as pyLevenshtein smObject = pyLevenshtein.StringMatcher(junk, '', seq2) except ImportError: @@ -304,6 +307,7 @@ def scoreSimilarity( includeReverse=False, forceDifflib=False, ): + # noinspection PyShadowingNames r''' Find the level of similarity between each pair of segments in a scoreDict. @@ -400,7 +404,7 @@ def doOneSegment(thisSegment): # ------------------------------------------------------------------------------ # define presented order in documentation -_DOC_ORDER = [] +_DOC_ORDER: List[type] = [] if __name__ == '__main__': diff --git a/music21/stream/base.py b/music21/stream/base.py index dbcbedac21..f4019b5821 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -86,7 +86,7 @@ class StreamDeprecationWarning(UserWarning): # ----------------------------------------------------------------------------- # Metaclass -_OffsetMap = collections.namedtuple('OffsetMap', ['element', 'offset', 'endTime', 'voiceIndex']) +OffsetMap = collections.namedtuple('OffsetMap', ['element', 'offset', 'endTime', 'voiceIndex']) # ----------------------------------------------------------------------------- @@ -6105,7 +6105,7 @@ def offsetMap(self, srcObj=None): endTime = opFrac(offset + dur) # NOTE: used to make a copy.copy of elements here; # this is not necessary b/c making deepcopy of entire Stream - thisOffsetMap = _OffsetMap(e, offset, endTime, voiceIndex) + thisOffsetMap = OffsetMap(e, offset, endTime, voiceIndex) # environLocal.printDebug(['offsetMap: thisOffsetMap', thisOffsetMap]) offsetMap.append(thisOffsetMap) # offsetMap.append((offset, offset + dur, e, voiceIndex)) @@ -14026,7 +14026,8 @@ def testCopyAndDeepcopy(self): # ----------------------------------------------------------------------------- # define presented order in documentation -_DOC_ORDER = [Stream, Measure, Part, Score, Opus, Voice] +_DOC_ORDER = [Stream, Measure, Part, Score, Opus, Voice, + SpannerStorage, VariantStorage, OffsetMap] if __name__ == '__main__': diff --git a/music21/stream/iterator.py b/music21/stream/iterator.py index f1c455cdda..9a9687b790 100644 --- a/music21/stream/iterator.py +++ b/music21/stream/iterator.py @@ -160,11 +160,10 @@ def __init__(self, self.filters: List[FilterType] = filterList self._len = None self._matchingElements = None - # keep track of where we are in the parse. # esp important for recursive streams... if activeInformation is not None: - self.activeInformation = activeInformation + self.activeInformation: ActiveInformation = activeInformation else: self.activeInformation: ActiveInformation = {} self.updateActiveInformation() diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py index 05c176605c..beab4bc6f4 100644 --- a/music21/stream/makeNotation.py +++ b/music21/stream/makeNotation.py @@ -3,13 +3,13 @@ # Name: makeNotation.py # Purpose: functionality for manipulating streams # -# Authors: Michael Scott Cuthbert +# Authors: Michael Scott Asato Cuthbert # Christopher Ariza # Jacob Walls # Evan Lynch # -# Copyright: Copyright © 2008-2021 Michael Scott Cuthbert and the music21 -# Project +# Copyright: Copyright © 2008-2022 Michael Scott Asato Cuthbert +# and the music21 Project # License: BSD, see license.txt # ----------------------------------------------------------------------------- @@ -30,7 +30,7 @@ from music21 import pitch from music21.common.numberTools import opFrac - +from music21.common.types import StreamType from music21.exceptions21 import StreamException environLocal = environment.Environment(__file__) @@ -40,12 +40,12 @@ def makeBeams( - s: 'music21.stream.Stream', + s: StreamType, *, inPlace=False, setStemDirections=True, failOnNoTimeSignature=False, -): +) -> Optional[StreamType]: # noinspection PyShadowingNames ''' Return a new Measure, or Stream of Measures, with beams applied to all @@ -122,7 +122,7 @@ def makeBeams( if not inPlace: # make a copy returnObj: stream.Stream = s.coreCopyAsDerivation('makeBeams') else: - returnObj: stream.Stream = s + returnObj = s # if s.isClass(Measure): mColl: List[stream.Measure] @@ -221,7 +221,7 @@ def makeBeams( def makeMeasures( - s, + s: StreamType, *, meterStream=None, refStreamOrTimeRange=None, @@ -230,7 +230,7 @@ def makeMeasures( finalBarline='final', bestClef=False, inPlace=False, -): +) -> Optional[StreamType]: ''' Takes a stream and places all of its elements into measures (:class:`~music21.stream.Measure` objects) @@ -702,14 +702,14 @@ def makeMeasures( def makeRests( - s, + s: StreamType, *, refStreamOrTimeRange=None, fillGaps=False, timeRangeFromBarDuration=False, inPlace=False, hideRests=False, -): +) -> Optional[StreamType]: ''' Given a Stream with an offset not equal to zero, fill with one Rest preceding this offset. @@ -972,13 +972,13 @@ def oHighTargetForMeasure( return returnObj def makeTies( - s, + s: StreamType, *, meterStream=None, inPlace=False, displayTiedAccidentals=False, classFilterList=(note.GeneralNote,), -): +) -> Optional[StreamType]: # noinspection PyShadowingNames ''' Given a stream containing measures, examine each element in the @@ -1347,7 +1347,7 @@ def makeTies( return None -def makeTupletBrackets(s: 'music21.stream.Stream', *, inPlace=False): +def makeTupletBrackets(s: StreamType, *, inPlace=False) -> Optional[StreamType]: # noinspection PyShadowingNames ''' Given a flat Stream of mixed durations, designates the first and last tuplet of any group @@ -1470,7 +1470,7 @@ def makeTupletBrackets(s: 'music21.stream.Stream', *, inPlace=False): return returnObj -def realizeOrnaments(s: 'music21.stream.Stream'): +def realizeOrnaments(s: StreamType) -> StreamType: ''' Realize all ornaments on a stream @@ -1548,7 +1548,8 @@ def realizeElementExpressions(innerElement): return newStream -def moveNotesToVoices(source: 'music21.stream.Stream', classFilterList=('GeneralNote',)): +def moveNotesToVoices(source: StreamType, + classFilterList=('GeneralNote',)) -> None: ''' Move notes into voices. Happens inplace always. Returns None ''' @@ -1564,7 +1565,7 @@ def moveNotesToVoices(source: 'music21.stream.Stream', classFilterList=('General source.insert(0, dst) -def getTiePitchSet(prior: 'music21.note.NotRest'): +def getTiePitchSet(prior: 'music21.note.NotRest') -> Optional[Set[str]]: # noinspection PyShadowingNames ''' helper method for makeAccidentals to get the tie pitch set (or None) @@ -1622,7 +1623,7 @@ def getTiePitchSet(prior: 'music21.note.NotRest'): return tiePitchSet def makeAccidentalsInMeasureStream( - s: 'music21.stream.Stream', + s: StreamType, *, pitchPast: Optional[List[pitch.Pitch]] = None, pitchPastMeasure: Optional[List[pitch.Pitch]] = None, @@ -1633,7 +1634,7 @@ def makeAccidentalsInMeasureStream( overrideStatus: bool = False, cautionaryNotImmediateRepeat: bool = True, tiePitchSet: Optional[Set[str]] = None -): +) -> None: ''' Makes accidentals in place on a stream consisting of only Measures. Helper for Stream.makeNotation and Part.makeAccidentals. @@ -1710,7 +1711,7 @@ def makeAccidentalsInMeasureStream( ) def iterateBeamGroups( - s: 'music21.stream.Stream', + s: StreamType, skipNoBeams=True, recurse=True ) -> Generator[List[note.NotRest], None, None]: @@ -1781,7 +1782,7 @@ def iterateBeamGroups( def setStemDirectionForBeamGroups( - s: 'music21.stream.Stream', + s: StreamType, *, setNewStems=True, overrideConsistentStemDirections=False, diff --git a/music21/stream/streamStatus.py b/music21/stream/streamStatus.py index 7f72ef698a..b01e52ff2f 100644 --- a/music21/stream/streamStatus.py +++ b/music21/stream/streamStatus.py @@ -150,11 +150,11 @@ def haveTupletBracketsBeenMade(self): >>> s.append(note.Note()) >>> s.streamStatus.haveTupletBracketsBeenMade() is None True - >>> n = note.Note(quarterLength=1/3) - >>> s.append(n) + >>> nTuplet = note.Note(quarterLength=1/3) + >>> s.append(nTuplet) >>> s.streamStatus.haveTupletBracketsBeenMade() False - >>> n.duration.tuplets[0].type = 'start' + >>> nTuplet.duration.tuplets[0].type = 'start' >>> s.streamStatus.haveTupletBracketsBeenMade() True diff --git a/music21/voiceLeading.py b/music21/voiceLeading.py index 7627c9edc1..3732e0c200 100644 --- a/music21/voiceLeading.py +++ b/music21/voiceLeading.py @@ -55,7 +55,7 @@ # create a module level shared cache for intervals of P1, P5, P8 # to be populated the first time a VLQ object is created -intervalCache = [] # type: List[interval.Interval] +intervalCache: List[interval.Interval] = [] class MotionType(str, enum.Enum): @@ -1665,8 +1665,8 @@ def offset(self, leftAlign=True): returns the overall offset of the Verticality. Typically, this would just be the offset of each object in the Verticality, and each object would have the same offset. - However, if the duration of one object in the slice is different than the duration - of another, + However, if the duration of one object in the slice is different from + the duration of another, and that other starts after the first, but the first is still sounding, then the offsets would be different. In this case, specify leftAlign=True to return the lowest valued-offset @@ -2106,7 +2106,7 @@ def couldBePassingTone(self) -> bool: are moving in the same direction. Returns True if the tone is identified as either a chromatic passing tone or a diatonic passing tone. Only major and minor diatonic passing tones are recognized (not - pentatonic or scales beyond twelve-notes). Does NOT check if tone is non harmonic + pentatonic or scales beyond twelve-notes). Does NOT check if tone is non-harmonic. Accepts pitch or note objects; method is dependent on octave information @@ -2212,7 +2212,7 @@ def couldBeChromaticPassingTone(self): def couldBeNeighborTone(self): ''' checks if noteToAnalyze could be a neighbor tone, either a diatonic neighbor tone - or a chromatic neighbor tone. Does NOT check if tone is non harmonic + or a chromatic neighbor tone. Does NOT check if tone is non-harmonic. >>> voiceLeading.ThreeNoteLinearSegment('E3', 'F3', 'E3').couldBeNeighborTone() True From 27556cc23e9e7e55b33ff009ee7a938ef0e2c500 Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 15:37:25 -1000 Subject: [PATCH 6/8] missing imports --- music21/common/types.py | 5 ++++- music21/graph/findPlot.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/music21/common/types.py b/music21/common/types.py index e8e8e1dcf5..dd75a597c5 100644 --- a/music21/common/types.py +++ b/music21/common/types.py @@ -9,10 +9,13 @@ # License: BSD, see license.txt # ------------------------------------------------------------------------------ from fractions import Fraction -from typing import Union, TypeVar +from typing import Union, TypeVar, TYPE_CHECKING from music21.common.enums import OffsetSpecial +if TYPE_CHECKING: + import music21 + OffsetQL = Union[float, Fraction] OffsetQLSpecial = Union[float, Fraction, OffsetSpecial] OffsetQLIn = Union[int, float, Fraction] diff --git a/music21/graph/findPlot.py b/music21/graph/findPlot.py index 8c78c0893e..82052f6d91 100644 --- a/music21/graph/findPlot.py +++ b/music21/graph/findPlot.py @@ -15,7 +15,7 @@ import collections import types import unittest -from typing import List +from typing import List, Tuple from music21.graph import axis from music21.graph import plot From 5e4ff2db83cceb3170146f5a557d074588149199 Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 15:46:58 -1000 Subject: [PATCH 7/8] lint and mypy again; back off opFrac cache some weird things going on with someone manipulating the fraction internally -- must figure that out before going on. --- music21/analysis/reduction.py | 1 - music21/common/numberTools.py | 5 ++--- music21/common/objects.py | 1 - music21/duration.py | 5 +++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index 1b93afd6f3..9cc7add387 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -22,7 +22,6 @@ from music21 import exceptions21 from music21 import chord -from music21 import clef from music21 import common from music21 import expressions from music21 import instrument diff --git a/music21/common/numberTools.py b/music21/common/numberTools.py index 90b111a654..727be23a0d 100644 --- a/music21/common/numberTools.py +++ b/music21/common/numberTools.py @@ -147,7 +147,7 @@ def numToIntOrFloat(value: Union[int, float]) -> Union[int, float]: DENOM_LIMIT = defaults.limitOffsetDenominator - +@lru_cache(1024) def _preFracLimitDenominator(n: int, d: int) -> Tuple[int, int]: # noinspection PyShadowingNames ''' @@ -229,7 +229,6 @@ def _preFracLimitDenominator(n: int, d: int) -> Tuple[int, int]: # no type checking due to accessing protected attributes (for speed) @no_type_check -@lru_cache(1024) # speeds up x2! but all fractions will be the same object... def opFrac(num: Union[OffsetQLIn, None]) -> Union[OffsetQL, None]: ''' opFrac -> optionally convert a number to a fraction or back. @@ -565,7 +564,7 @@ def nearestMultiple(n: float, unit: float) -> Tuple[float, float, float]: return matchHigh, round(matchHigh - n, 7), round(n - matchHigh, 7) -_DOT_LOOKUP = (1, 1.5, 1.75, 1.875, 1.9375, +_DOT_LOOKUP = (1.0, 1.5, 1.75, 1.875, 1.9375, 1.96875, 1.984375, 1.9921875, 1.99609375) def dotMultiplier(dots: int) -> float: diff --git a/music21/common/objects.py b/music21/common/objects.py index 87de228385..1da1f26c98 100644 --- a/music21/common/objects.py +++ b/music21/common/objects.py @@ -22,7 +22,6 @@ import time from typing import Tuple import weakref -from music21.common.decorators import deprecated class RelativeCounter(collections.Counter): diff --git a/music21/duration.py b/music21/duration.py index 9e24b482f6..17d0622d4e 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -53,7 +53,8 @@ from functools import lru_cache import io from math import inf, isnan -from typing import Union, Tuple, Dict, List, Optional, Iterable, Literal +from typing import (Union, Tuple, Dict, List, Optional, + Iterable, Literal, Type, Callable) import unittest @@ -3822,7 +3823,7 @@ def testExceptions(self): # ------------------------------------------------------------------------------- # define presented order in documentation -_DOC_ORDER: List[type] = [Duration, Tuplet, convertQuarterLengthToType, TupletFixer] +_DOC_ORDER: List[Union[Type, Callable]] = [Duration, Tuplet, convertQuarterLengthToType, TupletFixer] if __name__ == '__main__': From 633f7e342d8289b419f595f9a2ed2b8b7e16dbf8 Mon Sep 17 00:00:00 2001 From: Myke Cuthbert Date: Tue, 19 Apr 2022 15:56:44 -1000 Subject: [PATCH 8/8] last? lint --- music21/duration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/music21/duration.py b/music21/duration.py index 17d0622d4e..b6a5d45600 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -3823,7 +3823,9 @@ def testExceptions(self): # ------------------------------------------------------------------------------- # define presented order in documentation -_DOC_ORDER: List[Union[Type, Callable]] = [Duration, Tuplet, convertQuarterLengthToType, TupletFixer] +_DOC_ORDER: List[Union[Type, Callable]] = [ + Duration, Tuplet, GraceDuration, convertQuarterLengthToType, TupletFixer, +] if __name__ == '__main__':