diff --git a/music21/analysis/patel.py b/music21/analysis/patel.py index 6652e73513..bd400b8a65 100644 --- a/music21/analysis/patel.py +++ b/music21/analysis/patel.py @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ from __future__ import annotations -import math +from statistics import mean, stdev import unittest def nPVI(streamForAnalysis): @@ -98,15 +98,7 @@ def melodicIntervalVariability(streamForAnalysis, **skipKeywords): + 'a std-deviation of intervals (and thus a MIV)') # summation = 0 semitoneList = [myInt.chromatic.undirected for myInt in intervalStream] - mean = 0 - std = 0 - for a in semitoneList: - mean = mean + a - mean = mean / totalElements - for a in semitoneList: - std = std + (a - mean) ** 2 - std = math.sqrt(std / (totalElements - 1)) - return 100 * (std / mean) + return 100 * (stdev(semitoneList) / mean(semitoneList)) class Test(unittest.TestCase): diff --git a/music21/base.py b/music21/base.py index c2a6e2b37a..e1a01c3fb7 100644 --- a/music21/base.py +++ b/music21/base.py @@ -257,11 +257,9 @@ def __eq__(self, other: object): if len(self) != len(other): return False - sls = sorted(self) - slo = sorted(other) - for x in range(len(sls)): - if sls[x].lower() != slo[x].lower(): + for eSelf, eOther in zip(sorted(self), sorted(other)): + if eSelf.lower() != eOther.lower(): return False return True @@ -3267,9 +3265,7 @@ def splitAtQuarterLength( elif addTies and isinstance(e, chord.Chord) and isinstance(eRemain, chord.Chord): # the last isinstance is redundant, but MyPy needs it. - for i in range(len(e.notes)): - component = e.notes[i] - remainComponent = eRemain.notes[i] + for component, remainComponent in zip(e.notes, eRemain.notes): forceEndTieType = 'stop' if component.tie is not None: # the last tie of what was formally a start should @@ -3352,8 +3348,7 @@ def splitByQuarterLengths( eList = [] spannerList = [] # this does not fully work with trills over multiple splits yet. eRemain = copy.deepcopy(self) - for qlIndex in range(len(quarterLengthList) - 1): - qlSplit = quarterLengthList[qlIndex] + for qlSplit in quarterLengthList[:-1]: st = eRemain.splitAtQuarterLength(qlSplit, addTies=addTies, displayTiedAccidentals=displayTiedAccidentals) diff --git a/music21/beam.py b/music21/beam.py index 185998381d..c5da526cad 100644 --- a/music21/beam.py +++ b/music21/beam.py @@ -334,10 +334,8 @@ def removeSandwichedUnbeamables(beamsList: list[Beams | None]): beamNext = beamsList[i + 1] else: beamNext = None - if beamLast is None and beamNext is None: beamsList[i] = None - beamLast = beamsList[i] return beamsList @@ -350,9 +348,7 @@ def mergeConnectingPartialBeams(beamsList): 16ths are not beamed by default. ''' # sanitize two partials in a row: - for i in range(len(beamsList) - 1): - bThis = beamsList[i] - bNext = beamsList[i + 1] + for i, (bThis, bNext) in enumerate(zip(beamsList[:-1], beamsList[1:])): if not bThis or not bNext: continue @@ -387,9 +383,7 @@ def mergeConnectingPartialBeams(beamsList): nextBeam.direction = None # now fix partial-lefts that follow stops: - for i in range(1, len(beamsList)): - bThis = beamsList[i] - bPrev = beamsList[i - 1] + for bThis, bPrev in zip(beamsList[1:], beamsList[:-1]): if not bThis or not bPrev: continue @@ -416,17 +410,17 @@ def mergeConnectingPartialBeams(beamsList): return beamsList @staticmethod - def sanitizePartialBeams(beamsList): + def sanitizePartialBeams(beamsList: list[Beams | None]) -> list[Beams | None]: ''' It is possible at a late stage to have beams that only consist of partials or beams with a 'start' followed by 'partial/left' or possibly 'stop' followed by 'partial/right'; beams entirely consisting of partials are removed and the direction of irrational partials is fixed. ''' - for i in range(len(beamsList)): - if beamsList[i] is None: + for i, beamsObj in enumerate(beamsList): + if beamsObj is None: continue - allTypes = beamsList[i].getTypes() + allTypes = beamsObj.getTypes() # clear elements that have partial beams with no full beams: if 'start' not in allTypes and 'stop' not in allTypes and 'continue' not in allTypes: # nothing but partials @@ -436,7 +430,8 @@ def sanitizePartialBeams(beamsList): # follow a stop hasStart = False hasStop = False - for b in beamsList[i].beamsList: + b: Beam + for b in beamsObj.beamsList: if b.type == 'start': hasStart = True continue @@ -583,9 +578,9 @@ def getByNumber(self, number): ''' if number not in self.getNumbers(): raise IndexError(f'beam number {number} cannot be accessed') - for i in range(len(self)): - if self.beamsList[i].number == number: - return self.beamsList[i] + for beam in self.beamsList: + if beam.number == number: + return beam def getNumbers(self): ''' @@ -695,10 +690,10 @@ def setByNumber(self, number, type, direction=None): # type is okay @ReservedAs raise BeamException(f'beam type cannot be {type}') if number not in self.getNumbers(): raise IndexError(f'beam number {number} cannot be accessed') - for i in range(len(self)): - if self.beamsList[i].number == number: - self.beamsList[i].type = type - self.beamsList[i].direction = direction + for beam in self.beamsList: + if beam.number == number: + beam.type = type + beam.direction = direction # ----------------------------------------------------------------------------- diff --git a/music21/duration.py b/music21/duration.py index a406872917..16e37ab8b2 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -862,15 +862,11 @@ def ordinal(self): >>> c.ordinal 14 ''' - ordinalFound = None - for i in range(len(ordinalTypeFromNum)): - if self.type == ordinalTypeFromNum[i]: - ordinalFound = i - break - if ordinalFound is None: + try: + return ordinalTypeFromNum.index(self.type) + except ValueError: raise DurationException( - f'Could not determine durationNumber from {ordinalFound}') - return ordinalFound + f'Could not determine durationNumber from {self.type}') _durationTupleCacheTypeDots: dict[tuple[str, int], DurationTuple] = {} @@ -2135,18 +2131,14 @@ def componentIndexAtQtrPosition(self, quarterPosition): return self.components[-1] currentPosition = 0.0 - indexFound = None - for i in range(len(self.components)): - currentPosition = opFrac(currentPosition + self.components[i].quarterLength) + for i, component in enumerate(self.components): + currentPosition = opFrac(currentPosition + component.quarterLength) if currentPosition > quarterPosition: - indexFound = i - break - if indexFound is None: - raise DurationException( - 'Could not match quarter length within an index.') - return indexFound + return i + raise DurationException( + 'Could not match quarterLength within an index.') - def componentStartTime(self, componentIndex): + def componentStartTime(self, componentIndex: int) -> float: ''' For a valid component index value, this returns the quarter note offset at which that component would start. @@ -2172,15 +2164,13 @@ def componentStartTime(self, componentIndex): IndexError: invalid component index value 3 submitted; value must be an integer between 0 and 2 ''' - if componentIndex not in range(len(self.components)): + if not (0 <= componentIndex < len(self.components)): raise IndexError( f'invalid component index value {componentIndex} ' + f'submitted; value must be an integer between 0 and {len(self.components) - 1}') + components = self.components[:componentIndex] + return float(sum([c.quarterLength for c in components])) - currentPosition = 0.0 - for i in range(componentIndex): - currentPosition += self.components[i].quarterLength - return currentPosition def consolidate(self): ''' @@ -2624,10 +2614,10 @@ def dotGroups(self, value: tuple[int, ...]): if not isinstance(value, tuple): raise TypeError('only tuple dotGroups values can be used with this method.') # removes dots from all components... - components = list(self._components) - for i in range(len(self._components)): - components[i] = durationTupleFromTypeDots(self._components[i].type, 0) - self._components = tuple(components) + componentsGenerator = ( + durationTupleFromTypeDots(component.type, 0) for component in self._components + ) + self._components = tuple(componentsGenerator) self._dotGroups = value self._quarterLengthNeedsUpdating = True diff --git a/music21/interval.py b/music21/interval.py index 7457276e33..e01185b892 100644 --- a/music21/interval.py +++ b/music21/interval.py @@ -2560,23 +2560,24 @@ def _stringToDiatonicChromatic( dirScale = -1 else: dirScale = 1 + value_lower = value.lower() - if 'descending' in value.lower(): + if 'descending' in value_lower: value = re.sub(r'descending\s*', '', value, flags=re.RegexFlag.IGNORECASE) dirScale = -1 - elif 'ascending' in value.lower(): + elif 'ascending' in value_lower: value = re.sub(r'ascending\\s*', '', value, flags=re.RegexFlag.IGNORECASE) # permit whole and half abbreviations - if value.lower() in ('w', 'whole', 'tone'): + if value_lower in ('w', 'whole', 'tone'): value = 'M2' inferred = True - elif value.lower() in ('h', 'half', 'semitone'): + elif value_lower in ('h', 'half', 'semitone'): value = 'm2' inferred = True for i, ordinal in enumerate(common.musicOrdinals): - if ordinal.lower() in value.lower(): + if ordinal.lower() in value_lower: value = re.sub(fr'\s*{ordinal}\s*', str(i), value, diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index 53baf2b0e4..142b6d9ad9 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -1138,7 +1138,7 @@ def search( # environLocal.printDebug(['comparing fields:', f, field]) # look for partial match in all fields - if field.lower() in uniqueName.lower(): + if field in uniqueName.lower(): valueFieldPairs.append((value, uniqueName)) match = True break @@ -1149,21 +1149,20 @@ def search( uniqueName, None ) - if not workId: + if workId is None: # there is no associated grandfathered workId, don't search it continue # look for partial match in all fields - if field.lower() in workId.lower(): + if field in workId.lower(): valueFieldPairs.append((value, workId)) match = True break else: # get all fields for uniqueName, value in self.all(skipContributors=True): - if not self._isStandardUniqueName(uniqueName): - # custom metadata, don't search it - continue - valueFieldPairs.append((value, uniqueName)) + # only search standard metadata + if self._isStandardUniqueName(uniqueName): + valueFieldPairs.append((value, uniqueName)) # now get all (or field-matched) contributor names, using contrib.role # as field name, so clients can search by custom contributor role. @@ -1970,13 +1969,7 @@ def _isStandardUniqueName(self, uniqueName: str) -> bool: False ''' - prop: PropertyDescription | None = ( - properties.UNIQUE_NAME_TO_PROPERTY_DESCRIPTION.get(uniqueName, None) - ) - if prop is None: - return False - - return True + return uniqueName in properties.UNIQUE_NAME_TO_PROPERTY_DESCRIPTION @staticmethod def _isStandardNamespaceName(namespaceName: str) -> bool: diff --git a/music21/metadata/caching.py b/music21/metadata/caching.py index b73814da1a..fd42f0bd8b 100644 --- a/music21/metadata/caching.py +++ b/music21/metadata/caching.py @@ -356,12 +356,11 @@ def process_parallel(jobs, processCount=None): # end generator @staticmethod - def process_serial(jobs): + def process_serial(jobs: list[MetadataCachingJob]): ''' Process jobs serially. ''' remainingJobs = len(jobs) - results = [] for job in jobs: results, errors = job.run() remainingJobs -= 1 diff --git a/music21/metadata/primitives.py b/music21/metadata/primitives.py index cbdeae90df..3e20dde4a3 100644 --- a/music21/metadata/primitives.py +++ b/music21/metadata/primitives.py @@ -862,10 +862,10 @@ def __init__(self, def __str__(self): if isinstance(self._data, bytes): return self._data.decode('UTF-8') - elif not isinstance(self._data, str): - return str(self._data) - else: + elif isinstance(self._data, str): return self._data + else: + return str(self._data) def _reprInternal(self): return str(self) diff --git a/music21/meter/tools.py b/music21/meter/tools.py index 2c61faf9ea..5c9b1f0933 100644 --- a/music21/meter/tools.py +++ b/music21/meter/tools.py @@ -16,7 +16,6 @@ from functools import lru_cache import math import re -import typing as t from music21 import common from music21.common.enums import MeterDivision @@ -134,7 +133,6 @@ def slashMixedToFraction(valueSrc: str) -> tuple[NumDenomTuple, bool]: * Changed in v7: new location and returns a tuple as first value. ''' pre: list[NumDenom | tuple[int, None]] = [] - post: list[NumDenom] = [] summedNumerator = False value = valueSrc.strip() # rem whitespace value = value.split('+') @@ -149,6 +147,7 @@ def slashMixedToFraction(valueSrc: str) -> tuple[NumDenomTuple, bool]: else: # its just a numerator try: pre.append((int(part), None)) + summedNumerator = True except ValueError: raise Music21Exception( 'Cannot parse this file -- this error often comes ' @@ -157,27 +156,19 @@ def slashMixedToFraction(valueSrc: str) -> tuple[NumDenomTuple, bool]: + 'Clear your temp directory of .p and .p.gz files and try again...; ' + f'Time Signature: {valueSrc} ') + post: list[NumDenom] = [] # when encountering a missing denominator, find the first defined # and apply to all previous - for i in range(len(pre)): - if pre[i][1] is not None: # there is a denominator - intNum = pre[i][0] # this is all for type checking - intDenom = pre[i][1] - if t.TYPE_CHECKING: - assert isinstance(intNum, int) and isinstance(intDenom, int) - post.append((intNum, intDenom)) - else: # search ahead for next defined denominator - summedNumerator = True - match: int | None = None - for j in range(i, len(pre)): # this O(n^2) operation is easily simplified to O(n) - if pre[j][1] is not None: - match = pre[j][1] + for i, (intNum, intDenom) in enumerate(pre): + if intDenom is None: # search for next denominator + # this O(n^2) operation is easily simplified to O(n) + for (_, nextDenom) in pre[i + 1:]: + if nextDenom is not None: + intDenom = nextDenom break - if match is None: + else: raise MeterException(f'cannot match denominator to numerator in: {valueSrc}') - - preBothAreInts = (pre[i][0], match) - post.append(preBothAreInts) + post.append((intNum, intDenom)) return tuple(post), summedNumerator @@ -195,26 +186,14 @@ def fractionToSlashMixed(fList: NumDenomTuple) -> tuple[tuple[str, int], ...]: * Changed in v7: new location and returns a tuple. ''' pre: list[tuple[list[int], int]] = [] - for i in range(len(fList)): - n: int - d: int - n, d = fList[i] + for n, d in fList: # look at previous fraction and determine if denominator is the same + if pre and pre[-1][1] == d: + pre[-1][0].append(n) - match = None - search = list(range(len(pre))) - search.reverse() # go backwards - for j in search: - if pre[j][1] == d: - match = j # index to add numerator - break - else: - break # if not found in one less - - if match is None: + else: + # if not found in one less pre.append(([n], d)) - else: # append numerator - pre[match][0].append(n) # create string representation post: list[tuple[str, int]] = [] @@ -263,21 +242,16 @@ def fractionSum(numDenomTuple: NumDenomTuple) -> NumDenom: if len(dListUnique) == 1: n = sum(nList) - d = next(iter(dListUnique)) + d = dList[0] # Does not reduce to lowest terms... return (n, d) else: # there might be a better way to do this - d = 1 - d = math.lcm(*dListUnique) + dRed = math.lcm(*dListUnique) # after finding d, multiply each numerator - nShift = [] - for i in range(len(nList)): - nSrc = nList[i] - dSrc = dList[i] - scalar = d // dSrc - nShift.append(nSrc * scalar) - return (sum(nShift), d) - + nRed = 0 + for nSrc, dSrc in zip(nList, dList): + nRed += nSrc * (dRed // dSrc) + return (nRed, dRed) @lru_cache(512) @@ -329,12 +303,10 @@ def divisionOptionsFractionsUpward(n, d) -> tuple[str, ...]: if d < validDenominators[-1]: nMod = n * 2 dMod = d * 2 - while True: - if dMod > validDenominators[-1]: - break + while dMod <= validDenominators[-1]: opts.append(f'{nMod}/{dMod}') - dMod = dMod * 2 - nMod = nMod * 2 + dMod *= 2 + nMod *= 2 return tuple(opts) @@ -353,9 +325,7 @@ def divisionOptionsFractionsDownward(n, d) -> tuple[str, ...]: if d > validDenominators[0] and n % 2 == 0: nMod = n // 2 dMod = d // 2 - while True: - if dMod < validDenominators[0]: - break + while dMod >= validDenominators[0]: opts.append(f'{nMod}/{dMod}') if nMod % 2 != 0: # no longer even break @@ -375,13 +345,8 @@ def divisionOptionsAdditiveMultiplesDownward(n, d) -> MeterOptions: if d < validDenominators[-1] and n == 1: i = 2 dMod = d * 2 - while True: - if dMod > validDenominators[-1]: - break - seq = [] - for j in range(i): - seq.append(f'{n}/{dMod}') - opts.append(tuple(seq)) + while dMod <= validDenominators[-1]: + opts.append(tuple([f'{n}/{dMod}'] * i)) dMod = dMod * 2 i *= 2 return tuple(opts) @@ -401,13 +366,8 @@ def divisionOptionsAdditiveMultiples(n, d) -> MeterOptions: div = 2 i = div nMod = n // div - while True: - if nMod <= 1: - break - seq = [] - for j in range(i): - seq.append(f'{nMod}/{d}') - seq = tuple(seq) + while nMod > 1: + seq = tuple([f'{nMod}/{d}'] * i) if seq not in opts: # may be cases defined elsewhere opts.append(seq) nMod = nMod // div @@ -430,13 +390,8 @@ def divisionOptionsAdditiveMultiplesEvenDivision(n, d): if n % 2 == 0 and d // 2 >= 1: nMod = n // 2 dMod = d // 2 - while True: - if dMod < 1 or nMod <= 1: - break - seq = [] - for j in range(int(nMod)): - seq.append(f'{1}/{dMod}') - opts.append(tuple(seq)) + while dMod >= 1 and nMod > 1: + opts.append(tuple([f'{1}/{dMod}'] * int(nMod))) if nMod % 2 != 0: # if no longer even must stop break dMod = dMod // 2 diff --git a/music21/midi/__init__.py b/music21/midi/__init__.py index 18e3f61fe4..b8be2983ba 100644 --- a/music21/midi/__init__.py +++ b/music21/midi/__init__.py @@ -220,11 +220,11 @@ def getNumbersAsList(midiBytes): [0, 0, 0, 3] ''' post = [] - for i in range(len(midiBytes)): - if common.isNum(midiBytes[i]): - post.append(midiBytes[i]) + for midiByte in midiBytes: + if common.isNum(midiByte): + post.append(midiByte) else: - post.append(ord(midiBytes[i])) + post.append(ord(midiByte)) return post diff --git a/music21/midi/realtime.py b/music21/midi/realtime.py index 31bb955ad6..45e9cc80cb 100644 --- a/music21/midi/realtime.py +++ b/music21/midi/realtime.py @@ -244,8 +244,8 @@ def x_testPlayOneMeasureAtATime(self): measures.append(b.measure(i)) sp = StreamPlayer(b) - for i in range(len(measures)): - sp.streamIn = measures[i] + for measure in measures: + sp.streamIn = measure sp.play() def x_testPlayRealTime(self): diff --git a/music21/midi/translate.py b/music21/midi/translate.py index 3620ac5c98..65f559d343 100644 --- a/music21/midi/translate.py +++ b/music21/midi/translate.py @@ -1359,10 +1359,10 @@ def streamToPackets( # strip delta times elementPackets = [] firstNotePlayed = False - for i in range(len(midiEventList)): + for midiEvent in midiEventList: # store offset, midi event, object # add channel and pitch change also - midiEvent = midiEventList[i] + if (midiEvent.type == midiModule.ChannelVoiceMessages.NOTE_ON and firstNotePlayed is False): firstNotePlayed = True diff --git a/music21/musedata/__init__.py b/music21/musedata/__init__.py index 0033e2a495..d825b93029 100644 --- a/music21/musedata/__init__.py +++ b/music21/musedata/__init__.py @@ -789,8 +789,8 @@ def _determineStage(self): attributes record starting with a $; if not found, it is stage 1. if it is found it is stage 2. ''' - for i in range(len(self.src)): - if self.src[i].startswith('$'): + for attribute in self.src: + if attribute.startswith('$'): return 2 return 1 diff --git a/music21/musedata/translate.py b/music21/musedata/translate.py index 58b40d8aa3..4d8697322d 100644 --- a/music21/musedata/translate.py +++ b/music21/musedata/translate.py @@ -115,13 +115,13 @@ def _musedataRecordListToNoteOrChord(records, previousElement=None): # get accents and expressions; assumes all on first # returns an empty list of None - dynamicObjs = [] # stored in stream, not Note for a in records[0].getArticulationObjects(): post.articulations.append(a) for e in records[0].getExpressionObjects(): post.expressions.append(e) + dynamicObjs = [] # stored in stream, not Note for d in records[0].getDynamicObjects(): dynamicObjs.append(d) @@ -230,8 +230,7 @@ def musedataPartToStreamPart(museDataPart, inputM21=None): pendingRecords = [] # get notes in each record - for i in range(len(mdrObjs)): - mdr = mdrObjs[i] + for mdr in mdrObjs: # environLocal.printDebug(['processing:', mdr.src]) if mdr.isBack(): diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index e85c9e4236..6c0dfffcc9 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -4845,7 +4845,6 @@ def noteToNotations( # apply all expressions apart from arpeggios only to the first note of a chord. for expObj in chordOrNote.expressions: - mxExpression = None if isinstance(expObj, expressions.ArpeggioMark): mxExpression = self.arpeggioMarkToMxExpression( expObj, chordOrNote, noteIndexInChord @@ -4879,10 +4878,10 @@ def noteToNotations( # only to first note of chord applicableArticulations: list[articulations.Articulation] = [] fingeringNumber = 0 - for a in chordOrNote.articulations: - if isinstance(a, articulations.Fingering): + for aObj in chordOrNote.articulations: + if isinstance(aObj, articulations.Fingering): if fingeringNumber == noteIndexInChord: - applicableArticulations.append(a) + applicableArticulations.append(aObj) fingeringNumber += 1 elif isSingleNoteOrFirstInChord: # Ignore hammer-on/pull-off: @@ -4891,23 +4890,23 @@ def noteToNotations( # array, and the musicxml importer doesn't put them here, # but it's a potential point of user confusion, so we guard # against it here to avoid writing out superfluous - if not isinstance(a, (articulations.HammerOn, articulations.PullOff)): - applicableArticulations.append(a) + if not isinstance(aObj, (articulations.HammerOn, articulations.PullOff)): + applicableArticulations.append(aObj) - for artObj in applicableArticulations: - if isinstance(artObj, articulations.Pizzicato): + for aObj in applicableArticulations: + if isinstance(aObj, articulations.Pizzicato): continue - if isinstance(artObj, articulations.StringIndication) and artObj.number < 1: + if isinstance(aObj, articulations.StringIndication) and aObj.number < 1: continue - if isinstance(artObj, articulations.TechnicalIndication): + if isinstance(aObj, articulations.TechnicalIndication): if mxTechnicalMark is None: mxTechnicalMark = Element('technical') - mxTechnicalMark.append(self.articulationToXmlTechnical(artObj)) + mxTechnicalMark.append(self.articulationToXmlTechnical(aObj)) else: if mxArticulations is None: mxArticulations = Element('articulations') - mxArticulations.append(self.articulationToXmlArticulation(artObj)) + mxArticulations.append(self.articulationToXmlArticulation(aObj)) # TODO: attrGroup: print-object (for individual notations) # TODO: editorial (hard! -- requires parsing again in order...) diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py index 857fd25ad7..9d11e33c7e 100644 --- a/music21/musicxml/xmlToM21.py +++ b/music21/musicxml/xmlToM21.py @@ -5634,7 +5634,10 @@ def handleTimeSignature(self, mxTime): if ts is not None: self.insertCoreAndRef(self.offsetMeasureNote, mxTime, ts) - def xmlToTimeSignature(self, mxTime): + def xmlToTimeSignature( + self, + mxTime: ET.Element + ) -> meter.TimeSignature | meter.SenzaMisuraTimeSignature: # noinspection PyShadowingNames ''' Returns a TimeSignature or SenzaMisuraTimeSignature (for senza-misura) @@ -5693,16 +5696,14 @@ def xmlToTimeSignature(self, mxTime): denominators = [] for beatOrType in mxTime: if beatOrType.tag == 'beats': - numerators.append(beatOrType.text.strip()) # may be 3+2 + numerators.append(strippedText(beatOrType)) # may be 3+2 elif beatOrType.tag == 'beat-type': - denominators.append(beatOrType.text.strip()) + denominators.append(strippedText(beatOrType)) elif beatOrType.tag == 'interchangeable': break # interchangeable comes after all beat/beat-type sequences # convert into a string - msg = [] - for i in range(len(numerators)): - msg.append(f'{numerators[i]}/{denominators[i]}') + msg = [f'{num}/{denom}' for num, denom in zip(numerators, denominators)] # warnings.warn(f"loading meter string: {'+'.join(msg)}", MusicXMLWarning) if len(msg) == 1: # normal @@ -5723,16 +5724,16 @@ def xmlToTimeSignature(self, mxTime): # attr: symbol symbol = mxTime.get('symbol') - if symbol: - if symbol in ('common', 'cut', 'single-number', 'normal'): - ts.symbol = symbol - elif symbol == 'note': - ts.symbolizeDenominator = True - elif symbol == 'dotted-note': - pass - # TODO: support, but not as musicxml style -- reduces by 1/3 the numerator... - # this should be done by changing the displaySequence directly. - + if symbol is None: + pass + elif symbol in ('common', 'cut', 'single-number', 'normal'): + ts.symbol = symbol + elif symbol == 'note': + ts.symbolizeDenominator = True + elif symbol == 'dotted-note': + pass + # TODO: support, but not as musicxml style -- reduces by 1/3 the numerator... + # this should be done by changing the displaySequence directly. return ts def handleClef(self, mxClef): @@ -5945,49 +5946,45 @@ def nonTraditionalKeySignature(self, mxKey): >>> MP.nonTraditionalKeySignature(mxKey) ''' - allChildren = list(mxKey) + children = list(mxKey) lastTag = None - allSteps = [] - allAlters = [] - allAccidentals = [] + steps = [] + alters = [] + accidentals = [] - for c in allChildren: + for c in children: tag = c.tag if lastTag == 'key-alter' and tag == 'key-step': - allAccidentals.append(None) - + accidentals.append(None) if tag == 'key-step': - allSteps.append(c.text) + steps.append(c.text) elif tag == 'key-alter': - allAlters.append(float(c.text)) + alters.append(float(c.text)) elif tag == 'key-accidental': - allAccidentals.append(c.text) + accidentals.append(c.text) lastTag = tag - if len(allAccidentals) < len(allAlters): - allAccidentals.append(None) - if len(allSteps) != len(allAlters): + if len(accidentals) < len(alters): + accidentals.append(None) + if len(steps) != len(alters): raise MusicXMLImportException( 'For non traditional signatures each step must have an alter') ks = key.KeySignature(sharps=None) alteredPitches = [] - for i in range(len(allSteps)): - thisStep = allSteps[i] - thisAlter = allAlters[i] - thisAccidental = allAccidentals[i] - p = pitch.Pitch(thisStep) - if thisAccidental is not None: - if thisAccidental in self.mxAccidentalNameToM21: - accidentalName = self.mxAccidentalNameToM21[thisAccidental] + for step, alter, accidental in zip(steps, alters, accidentals): + p = pitch.Pitch(step) + if accidental is not None: + if accidental in self.mxAccidentalNameToM21: + accidentalName = self.mxAccidentalNameToM21[accidental] else: - accidentalName = thisAccidental + accidentalName = accidental p.accidental = pitch.Accidental(accidentalName) - p.accidental.alter = thisAlter + p.accidental.alter = alter else: - p.accidental = pitch.Accidental(thisAlter) + p.accidental = pitch.Accidental(alter) alteredPitches.append(p) diff --git a/music21/noteworthy/translate.py b/music21/noteworthy/translate.py index c4f761d13f..c412e5999a 100644 --- a/music21/noteworthy/translate.py +++ b/music21/noteworthy/translate.py @@ -733,8 +733,6 @@ def createKey(self, attributes): Adds a new key signature to the given measure. Returns the number of sharps (negative for flats) - - >>> measureIn = stream.Measure() >>> measureIn.append(note.Rest(quarterLength=3.0)) @@ -747,13 +745,13 @@ def createKey(self, attributes): {0.0} {3.0} ''' - ke = attributes['Signature'] + currentSharps = 0 - for a in range(len(ke)): - if ke[a] == '#': - currentSharps = currentSharps + 1 - if ke[a] == 'b': - currentSharps = currentSharps - 1 + for attribute in attributes['Signature']: + if attribute == '#': + currentSharps += 1 + elif attribute == 'b': + currentSharps -= 1 currentKey = key.KeySignature(currentSharps) self.currentMeasure.append(currentKey) self.currentKey = currentKey diff --git a/music21/pitch.py b/music21/pitch.py index f7bbcd57a3..e28fb8fd90 100644 --- a/music21/pitch.py +++ b/music21/pitch.py @@ -573,25 +573,26 @@ def _dissonanceScore(pitches, smallPythagoreanRatio=True, accidentalPenalty=True accidentals = [abs(p.alter) for p in pitches] score_accidentals = sum(a if a > 1 else 0 for a in accidentals) / len(pitches) + if smallPythagoreanRatio or triadAward: + try: + intervals = [interval.Interval(noteStart=p1, noteEnd=p2) + for p1, p2 in itertools.combinations(pitches, 2)] + except interval.IntervalException: + return math.inf if smallPythagoreanRatio: # score_ratio = Pythagorean ratio complexity per pitch - for p1, p2 in itertools.combinations(pitches, 2): + for this_interval in intervals: # does not accept weird intervals, e.g. with semitones - try: - this_interval = interval.Interval(noteStart=p1, noteEnd=p2) - ratio = interval.intervalToPythagoreanRatio(this_interval) - penalty = (math.log(ratio.numerator * ratio.denominator / ratio) - / 26.366694928034633) # d2 is 1.0 - score_ratio += penalty - except interval.IntervalException: - return math.inf + ratio = interval.intervalToPythagoreanRatio(this_interval) + # d2 is 1.0 + penalty = math.log(ratio.numerator * ratio.denominator / ratio) * 0.03792663444 + score_ratio += penalty - score_ratio = score_ratio / len(pitches) + score_ratio /= len(pitches) if triadAward: # score_triad = number of thirds per pitch (avoid double-base-thirds) - for p1, p2 in itertools.combinations(pitches, 2): - this_interval = interval.Interval(noteStart=p1, noteEnd=p2) + for this_interval in intervals: simple_directed = this_interval.generic.simpleDirected interval_semitones = this_interval.chromatic.semitones % 12 if simple_directed == 3 and interval_semitones in (3, 4): @@ -5084,9 +5085,8 @@ def set_displayStatus(newDisplayStatus: bool): # first search if the last pitch in our measure # with the same step and at this octave contradicts this pitch. # if so, then no matter what we need an accidental. - for i in range(len(pitchPast) - 1, -1, -1): + for thisPPast in reversed(pitchPast): # check previous in measure. - thisPPast = pitchPast[i] if thisPPast.step == self.step and thisPPast.octave == self.octave: # conflicting alters, need accidental and return if thisPPast.name != self.name: diff --git a/music21/repeat.py b/music21/repeat.py index f6f85754c4..18d3e5ee07 100644 --- a/music21/repeat.py +++ b/music21/repeat.py @@ -1210,7 +1210,7 @@ def _repeatBracketsAreCoherent(self): return False return True - def _hasRepeat(self, streamObj): + def _hasRepeat(self, streamObj: stream.Stream) -> bool: ''' Return True if this Stream of Measures has a repeat pair still to process. @@ -2141,8 +2141,8 @@ def getMeasureSimilarityList(self): mLists = [p.getElementsByClass(stream.Measure) for p in s.parts] # Check for unequal lengths - for i in range(len(mLists) - 1): - if len(mLists[i]) != len(mLists[i + 1]): + for mThis, mNext in zip(mLists, mLists[1:]): + if len(mThis) != len(mNext): raise UnequalPartsLengthException( 'Parts must each have the same number of measures.') @@ -2164,11 +2164,9 @@ def getMeasureSimilarityList(self): tempDict = {} # maps the measure-hashes to the lowest examined measure number with that hash. - res = [] # initialize res - for i in range(len(mLists)): - res.append([]) + res = [[] for _ in range(len(mLists))] for i in range(len(mLists) - 1, -1, -1): # mHash is the concatenation of the measure i for each part. @@ -2179,10 +2177,8 @@ def getMeasureSimilarityList(self): res[i].append(tempDict[mHash]) res[i].extend(res[tempDict[mHash]]) - # tempDict now stores the earliest known measure with mHash. - tempDict[mHash] = i - else: - tempDict[mHash] = i + # tempDict now stores the earliest known measure with mHash. + tempDict[mHash] = i self._mList = res return res @@ -2615,4 +2611,3 @@ def getSimilarMeasureGroups(self, threshold=1): if __name__ == '__main__': import music21 music21.mainTest() - diff --git a/music21/roman.py b/music21/roman.py index c6e79bb485..ecb8ba47c6 100644 --- a/music21/roman.py +++ b/music21/roman.py @@ -2701,11 +2701,9 @@ def shouldSkipThisChordStep(chordStep: int) -> bool: chordStepsToExamine = (3, 5, 7) # newPitches = [] - for i in range(len(correctSemitones)): # 3, 5, possibly 7 - thisChordStep = chordStepsToExamine[i] + for thisChordStep, thisCorrect in zip(chordStepsToExamine, correctSemitones): if shouldSkipThisChordStep(thisChordStep): continue - thisCorrect = correctSemitones[i] thisSemis = self.semitonesFromChordStep(thisChordStep) if thisSemis is None: # no chord step continue diff --git a/music21/scale/__init__.py b/music21/scale/__init__.py index 7fc926a454..3f393155e3 100644 --- a/music21/scale/__init__.py +++ b/music21/scale/__init__.py @@ -326,8 +326,8 @@ def buildNetworkFromPitches(self, pitchList): if not common.isListLike(pitchList) or not pitchList: raise ScaleException(f'Cannot build a network from this pitch list: {pitchList}') intervalList = [] - for i in range(len(pitchList) - 1): - intervalList.append(interval.Interval(pitchList[i], pitchList[i + 1])) + for currentPitch, nextPitch in zip(pitchList, pitchList[1:]): + intervalList.append(interval.Interval(currentPitch, nextPitch)) if pitchList[-1].name == pitchList[0].name: # the completion of the scale has been given. # print('hi %s ' % pitchList) # this scale is only octave duplicating if the top note is exactly diff --git a/music21/scale/intervalNetwork.py b/music21/scale/intervalNetwork.py index 1b5161dc91..81d84b08e4 100644 --- a/music21/scale/intervalNetwork.py +++ b/music21/scale/intervalNetwork.py @@ -2435,29 +2435,26 @@ def getRelativeNodeId( minPitch = pitchTargetObj.transpose(-12, inPlace=False) maxPitch = pitchTargetObj.transpose(12, inPlace=False) - realizedPitch, realizedNode = self.realize(pitchReference, - nodeObj, - minPitch=minPitch, - maxPitch=maxPitch, - direction=direction, - alteredDegrees=alteredDegrees) + realizedPitches, realizedNodes = self.realize(pitchReference, + nodeObj, + minPitch=minPitch, + maxPitch=maxPitch, + direction=direction, + alteredDegrees=alteredDegrees) # environLocal.printDebug(['getRelativeNodeId()', 'nodeObj', nodeObj, # 'realizedPitch', realizedPitch, 'realizedNode', realizedNode]) post = [] # collect more than one - for i in range(len(realizedPitch)): + for realizedPitch, realizedNode in zip(realizedPitches, realizedNodes): # environLocal.printDebug(['getRelativeNodeId', 'comparing', # realizedPitch[i], realizedNode[i]]) # comparison of attributes, not object - match = False if (getattr(pitchTargetObj, comparisonAttribute) - == getattr(realizedPitch[i], comparisonAttribute)): - match = True - if match: - if realizedNode[i] not in post: # may be more than one match - post.append(realizedNode[i]) + == getattr(realizedPitch, comparisonAttribute)): + if realizedNode not in post: # may be more than one match + post.append(realizedNode) if saveOctave is None: pitchTargetObj.octave = None @@ -2513,21 +2510,21 @@ def getNeighborNodeIds( minPitch = pitchTargetObj.transpose(-12, inPlace=False) maxPitch = pitchTargetObj.transpose(12, inPlace=False) - realizedPitch, realizedNode = self.realize(pitchReference, - nodeId, - minPitch=minPitch, - maxPitch=maxPitch, - direction=direction, - alteredDegrees=alteredDegrees) + realizedPitches, realizedNodes = self.realize(pitchReference, + nodeId, + minPitch=minPitch, + maxPitch=maxPitch, + direction=direction, + alteredDegrees=alteredDegrees) lowNeighbor = None highNeighbor = None - for i in range(len(realizedPitch)): - if pitchTargetObj.ps < realizedPitch[i].ps: - highNeighbor = realizedNode[i] + for realizedPitch, realizedNode in zip(realizedPitches, realizedNodes): + if pitchTargetObj.ps < realizedPitch.ps: + highNeighbor = realizedNode # low neighbor may be a previously-encountered pitch return lowNeighbor, highNeighbor - lowNeighbor = realizedNode[i] + lowNeighbor = realizedNode if savedOctave is None: pitchTargetObj.octave = savedOctave @@ -2815,7 +2812,7 @@ def filterPitchList( raise ValueError('There must be at least one pitch given.') # automatically derive a min and max from the supplied pitch - sortList = [(pitchList[i].ps, i) for i in range(len(pitchList))] + sortList = [(pitch.ps, i) for i, pitch in enumerate(pitchList)] sortList.sort() minPitch = pitchList[sortList[0][1]] # first index maxPitch = pitchList[sortList[-1][1]] # last index diff --git a/music21/search/base.py b/music21/search/base.py index 81b1dc2106..b420e9b2b4 100644 --- a/music21/search/base.py +++ b/music21/search/base.py @@ -1105,18 +1105,15 @@ def mostCommonMeasureRhythms(streamIn, transposeDiatonic=False): 'rhythmString': rhythmString, } measureNotes = thisMeasure.notes - foundNote = False - for i in range(len(measureNotes)): - if isinstance(measureNotes[i], note.Note): + for measureNote in measureNotes: + if isinstance(measureNote, note.Note): distanceToTranspose = 72 - measureNotes[0].pitch.ps - foundNote = True + thisMeasureCopy = copy.deepcopy(thisMeasure) + for n in thisMeasureCopy.notes: + # TODO: Transpose Diatonic + n.transpose(distanceToTranspose, inPlace=True) + newDict['rhythm'] = thisMeasureCopy break - if foundNote: - thisMeasureCopy = copy.deepcopy(thisMeasure) - for n in thisMeasureCopy.notes: - # TODO: Transpose Diatonic - n.transpose(distanceToTranspose, inPlace=True) - newDict['rhythm'] = thisMeasureCopy else: newDict['rhythm'] = thisMeasure newDict['measures'] = [thisMeasure] diff --git a/music21/search/serial.py b/music21/search/serial.py index c2bc326d31..f5526573a9 100644 --- a/music21/search/serial.py +++ b/music21/search/serial.py @@ -655,7 +655,7 @@ def searchIncludeAllInclude(self, n, partNumber): chordList = self.chordList chordList.append(n) - self.totalLength = self.totalLength + len(n.pitches) + self.totalLength += len(n.pitches) lengthOfActive = self.totalLength numChordsToDelete = 0 diff --git a/music21/serial.py b/music21/serial.py index 77b3df1d3c..536f3dfaa3 100644 --- a/music21/serial.py +++ b/music21/serial.py @@ -404,7 +404,7 @@ def makeTwelveToneRow(self): a.append(n) return a - def isSameRow(self, row): + def isSameRow(self, other): ''' Convenience function describing if two rows are the same. @@ -418,16 +418,13 @@ def isSameRow(self, row): >>> row1.isSameRow(row3) False ''' - if len(row) != len(self): + if len(self) != len(other): return False - else: - tempSame = True - for i in range(len(row)): - if tempSame is True: - if self[i].pitch.pitchClass != row[i].pitch.pitchClass: - tempSame = False - return tempSame + for selfTone, otherTone in zip(self, other): + if selfTone.pitch.pitchClass != otherTone.pitch.pitchClass: + return False + return True def getIntervalsAsString(self): ''' diff --git a/music21/stream/base.py b/music21/stream/base.py index aaa7327549..cb668c3aaf 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -6123,17 +6123,11 @@ def extractContext(self, searchElement, before=4.0, after=4.0, found = None foundOffset = 0 foundEnd = 0 - elements = self.elements - for i in range(len(elements)): - b = elements[i] - if b.id == searchElement.id: - found = i - foundOffset = self.elementOffset(elements[i]) - foundEnd = foundOffset + elements[i].duration.quarterLength - elif b is searchElement: + for i, b in enumerate(self.elements): + if b is searchElement or b.id == searchElement.id: found = i - foundOffset = self.elementOffset(elements[i]) - foundEnd = foundOffset + elements[i].duration.quarterLength + foundOffset = self.elementOffset(b) + foundEnd = foundOffset + b.duration.quarterLength if found is None: raise StreamException('Could not find the element in the stream') @@ -7122,9 +7116,9 @@ def extendDuration(self, objClass, *, inPlace=False): qLenTotal = returnObj.duration.quarterLength elements = list(returnObj.getElementsByClass(objClass)) - for i in range(len(elements) - 1): - span = returnObj.elementOffset(elements[i + 1]) - returnObj.elementOffset(elements[i]) - elements[i].duration.quarterLength = span + for element, nextElement in zip(elements, elements[1:]): + span = returnObj.elementOffset(nextElement) - returnObj.elementOffset(element) + element.duration.quarterLength = span # handle last element if elements: @@ -7386,11 +7380,9 @@ def updateEndMatch(nInner) -> bool: if len(nLast.pitches) != len(nInner.pitches): return False - for pitchIndex in range(len(nLast.pitches)): + for pLast, pInner in zip(nLast.pitches, nInner.pitches): # check to see that each is the same pitch, but # allow for `accidental is None` == `Accidental('natural')` - pLast = nLast.pitches[pitchIndex] - pInner = nInner.pitches[pitchIndex] if pLast.step != pInner.step or not pLast.isEnharmonic(pInner): return False return True @@ -10497,28 +10489,26 @@ def melodicIntervals(self, **skipKeywords): return self.cloneEmpty(derivationMethod='melodicIntervals') returnStream = self.cloneEmpty(derivationMethod='melodicIntervals') - for i in range(len(returnList) - 1): - firstNote = returnList[i] - secondNote = returnList[i + 1] + for thisNote, nextNote in zip(returnList, returnList[1:]): # returnList could contain None to represent a rest - if firstNote is None or secondNote is None: + if thisNote is None or nextNote is None: continue # Protect against empty chords - if not (firstNote.pitches and secondNote.pitches): + if not (thisNote.pitches and nextNote.pitches): continue - if chord.Chord in firstNote.classSet: - noteStart = firstNote.notes[0] + if chord.Chord in thisNote.classSet: + noteStart = thisNote.notes[0] else: - noteStart = firstNote - if chord.Chord in secondNote.classSet: - noteEnd = secondNote.notes[0] + noteStart = thisNote + if chord.Chord in nextNote.classSet: + noteEnd = nextNote.notes[0] else: - noteEnd = secondNote + noteEnd = nextNote # Prefer Note objects over Pitch objects so that noteStart is set correctly returnInterval = interval.Interval(noteStart, noteEnd) - returnInterval.offset = opFrac(firstNote.offset + firstNote.quarterLength) + returnInterval.offset = opFrac(thisNote.offset + thisNote.quarterLength) returnInterval.duration = duration.Duration(opFrac( - secondNote.offset - returnInterval.offset)) + nextNote.offset - returnInterval.offset)) returnStream.insert(returnInterval) return returnStream @@ -10607,10 +10597,9 @@ def _findLayering(self) -> list[list[int]]: # create a list with an entry for each element # in each entry, provide indices of all other elements that overlap - overlapMap: list[list[int]] = [[] for dummy in range(len(durSpanSorted))] + overlapMap: list[list[int]] = [[] for _ in range(len(durSpanSorted))] - for i in range(len(durSpanSortedIndex)): - src = durSpanSortedIndex[i] + for i, src in enumerate(durSpanSortedIndex): for j in range(i + 1, len(durSpanSortedIndex)): dst = durSpanSortedIndex[j] if self._durSpanOverlap(src[1], dst[1]): @@ -10639,12 +10628,10 @@ def _consolidateLayering(self, layeringMap): raise StreamException('layeringMap must be the same length as flatStream') post = {} - for i in range(len(layeringMap)): - indices = layeringMap[i] + for indices, srcElementObj in zip(layeringMap, flatStream): if not indices: continue - srcElementObj = flatStream[i] srcOffset = srcElementObj.offset dstOffset = None # check indices @@ -10657,7 +10644,7 @@ def _consolidateLayering(self, layeringMap): for k in post: # this comparison needs to be based on object id, not # matching equality - if id(elementObj) in [id(e) for e in post[k]]: + if any(True for e in post[k] if e is elementObj): # if elementObj in post[key]: store = False dstOffset = k @@ -10670,14 +10657,12 @@ def _consolidateLayering(self, layeringMap): post[dstOffset].append(elementObj) # check if this object has been stored anywhere yet - store = True for k in post: - if id(srcElementObj) in [id(e) for e in post[k]]: + if any(True for e in post[k] if e is srcElementObj): # if srcElementObj in post[key]: - store = False break - # dst offset may have been set when looking at indices - if store: + else: + # dst offset may have been set when looking at indices if dstOffset is None: dstOffset = srcOffset if dstOffset not in post: diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py index b5e0775f18..1e4c4f631e 100644 --- a/music21/stream/makeNotation.py +++ b/music21/stream/makeNotation.py @@ -1426,14 +1426,9 @@ def makeTupletBrackets(s: StreamType, *, inPlace=False) -> StreamType | None: # have a list of tuplet, Duration pairs completionCount: OffsetQL = 0.0 # qLen currently filled completionTarget: OffsetQL | None = None # qLen necessary to fill tuplet - for i in range(len(tupletMap)): - tupletObj, dur = tupletMap[i] - - if i > 0: - tupletPrevious = tupletMap[i - 1][0] - else: - tupletPrevious = None + tupletPrevious: duration.Tuplet | None = None + for i, (tupletObj, dur) in enumerate(tupletMap): if i < len(tupletMap) - 1: tupletNext = tupletMap[i + 1][0] # if tupletNext != None: @@ -1485,6 +1480,8 @@ def makeTupletBrackets(s: StreamType, *, inPlace=False) -> StreamType | None: # clear any previous type from prior calls tupletObj.type = None + tupletPrevious = tupletObj + returnObj.streamStatus.tuplets = True if not inPlace: @@ -2227,8 +2224,8 @@ def testSetStemDirectionOneGroup(self): def testDirections(group, expected): self.assertEqual(len(group), len(expected)) - for j in range(len(group)): - self.assertEqual(group[j].stemDirection, expected[j]) + for groupNote, expectedStemDirection in zip(group, expected): + self.assertEqual(groupNote.stemDirection, expectedStemDirection) testDirections(a, ['unspecified'] * 4) setStemDirectionOneGroup(a, setNewStems=False) diff --git a/music21/stream/tests.py b/music21/stream/tests.py index 9e7d4f8b2f..4c9bb210a3 100644 --- a/music21/stream/tests.py +++ b/music21/stream/tests.py @@ -2844,11 +2844,10 @@ def testMakeAccidentalsTies(self): allNotes = bm.flatten().notes # 0C# 1B-~ | 2B- 3C#~ 4C# 6B- 7C# 8B-~ 9B-~ 10B- ds = [True, True, False, True, False, True, False, False, False, False] - for i in range(len(allNotes)): - self.assertEqual(allNotes[i].pitch.accidental.displayStatus, - ds[i], - '%s failed, %s != %s' % - (i, allNotes[i].pitch.accidental.displayStatus, ds[i])) + for i, (dsi, thisNote) in enumerate(zip(ds, allNotes)): + self.assertEqual(thisNote.pitch.accidental.displayStatus, + dsi, + f'{i} failed, {thisNote.pitch.accidental.displayStatus} != {dsi}') # add another B-flat just after the tied one... bm = converter.parse( @@ -2858,11 +2857,10 @@ def testMakeAccidentalsTies(self): allNotes = bm.flatten().notes # 0C# 1B-~ | 2B- 3B- 4C#~ 5C# 6B- 7C# 8B-~ 9B-~ | 10B- ds = [True, True, False, True, True, False, False, False, False, False, False] - for i in range(len(allNotes)): - self.assertEqual(allNotes[i].pitch.accidental.displayStatus, - ds[i], - '%s failed, %s != %s' % - (i, allNotes[i].pitch.accidental.displayStatus, ds[i])) + for i, (dsi, thisNote) in enumerate(zip(ds, allNotes)): + self.assertEqual(thisNote.pitch.accidental.displayStatus, + dsi, + f'{i} failed, {thisNote.pitch.accidental.displayStatus} != {dsi}') def testMakeAccidentalsRespectsDisplayType(self): n = note.Note('D#') @@ -4036,10 +4034,10 @@ def testQuantize(self): def procCompare(srcOffset, srcDur, dstOffset, dstDur, divList): s = Stream() - for i in range(len(srcDur)): + for nOffset, nDuration in zip(srcOffset, srcDur): n = note.Note() - n.quarterLength = srcDur[i] - s.insert(srcOffset[i], n) + n.quarterLength = nDuration + s.insert(nOffset, n) # Must be sorted for quantizing to work optimally. s.sort() @@ -4130,17 +4128,11 @@ def testAnalyze(self): interval.Interval(26), interval.Interval(10)] - for i in range(len(sub)): - sTest = sub[i] - post = sTest.analyze('ambitus') - self.assertEqual(str(post), str(matchAmbitus[i])) - # match values for different analysis strings for idStr in ['range', 'ambitus', 'span']: - for i in range(len(sub)): - sTest = sub[i] + for sTest, matchAmbitusTest in zip(sub, matchAmbitus): post = sTest.analyze(idStr) - self.assertEqual(str(post), str(matchAmbitus[i])) + self.assertEqual(str(post), str(matchAmbitusTest)) # only match first two values matchKrumhansl = [(pitch.Pitch('F#'), 'minor'), @@ -4148,33 +4140,24 @@ def testAnalyze(self): (pitch.Pitch('E'), 'major'), (pitch.Pitch('E'), 'major')] - for i in range(len(sub)): - sTest = sub[i] - post = sTest.analyze('KrumhanslSchmuckler') - # returns three values; match 2 - self.assertEqual(post.tonic.name, matchKrumhansl[i][0].name) - self.assertEqual(post.mode, matchKrumhansl[i][1]) - # match values under different strings provided to analyze - for idStr in ['krumhansl']: - for i in range(len(sub)): - sTest = sub[i] + for idStr in ['KrumhanslSchmuckler', 'krumhansl']: + for sTest, sMatch in zip(sub, matchKrumhansl): post = sTest.analyze(idStr) # returns three values; match 2 - self.assertEqual(post.tonic.name, matchKrumhansl[i][0].name) - self.assertEqual(post.mode, matchKrumhansl[i][1]) + self.assertEqual(post.tonic.name, sMatch[0].name) + self.assertEqual(post.mode, sMatch[1]) matchArden = [(pitch.Pitch('F#'), 'minor'), - (pitch.Pitch('C#'), 'minor'), - (pitch.Pitch('F#'), 'minor'), - (pitch.Pitch('E'), 'major')] + (pitch.Pitch('C#'), 'minor'), + (pitch.Pitch('F#'), 'minor'), + (pitch.Pitch('E'), 'major')] for idStr in ['arden']: - for i in range(len(sub)): - sTest = sub[i] + for sTest, sMatch in zip(sub, matchArden): post = sTest.analyze(idStr) # returns three values; match 2 - self.assertEqual(post.tonic.name, matchArden[i][0].name) - self.assertEqual(post.mode, matchArden[i][1]) + self.assertEqual(post.tonic.name, sMatch[0].name) + self.assertEqual(post.mode, sMatch[1]) def testMakeTupletBracketsA(self): ''' @@ -4763,9 +4746,8 @@ def testMakeChordsBuiltA(self): for durCol in [[1, 1, 1], [0.5, 2, 3], [0.25, 0.25, 0.5], [6, 6, 8]]: s = Stream() o = 0 - for i in range(len(pitchCol)): - ql = durCol[i] - for pStr in pitchCol[i]: + for ql, pitches in zip(durCol, pitchCol): + for pStr in pitches: n = note.Note(pStr) n.quarterLength = ql s.insert(o, n) @@ -4779,10 +4761,10 @@ def testMakeChordsBuiltA(self): for sEval in [s, sMod]: self.assertEqual(len(sEval.getElementsByClass(chord.Chord)), 3) # make sure we have all the original pitches - for i in range(len(pitchCol)): + for i, pitchEl in enumerate(pitchCol): match = [p.nameWithOctave for p in sEval.getElementsByClass(chord.Chord)[i].pitches] - self.assertEqual(match, list(pitchCol[i])) + self.assertEqual(match, list(pitchEl)) # print('post chordify') # s.show('t') # sMod.show('t') diff --git a/music21/test/test_pitch.py b/music21/test/test_pitch.py index d006962ade..62d021a882 100644 --- a/music21/test/test_pitch.py +++ b/music21/test/test_pitch.py @@ -103,10 +103,9 @@ def proc(_pList, past): p.updateAccidentalDisplay(pitchPast=past) past.append(p) - def compare(past, _result): + def compare(_past, _result): # environLocal.printDebug(['accidental compare']) - for i in range(len(_result)): - p = past[i] + for i, (p, _resultItem) in enumerate(zip(_past, _result)): if p.accidental is None: pName = None pDisplayStatus = None @@ -114,8 +113,7 @@ def compare(past, _result): pName = p.accidental.name pDisplayStatus = p.accidental.displayStatus - targetName = _result[i][0] - targetDisplayStatus = _result[i][1] + targetName, targetDisplayStatus = _resultItem self.assertEqual(pName, targetName, f'name error for {i}: {pName} instead of desired {targetName}') @@ -209,10 +207,9 @@ def proc(_pList, past, alteredPitches): p.updateAccidentalDisplay(pitchPast=past, alteredPitches=alteredPitches) past.append(p) - def compare(past, _result): + def compare(_past, _result): # environLocal.printDebug(['accidental compare']) - for i in range(len(_result)): - p = past[i] + for i, (p, _resultItem) in enumerate(zip(_past, _result)): if p.accidental is None: pName = None pDisplayStatus = None @@ -220,12 +217,10 @@ def compare(past, _result): pName = p.accidental.name pDisplayStatus = p.accidental.displayStatus - targetName = _result[i][0] - targetDisplayStatus = _result[i][1] + targetName, targetDisplayStatus = _resultItem - # environLocal.printDebug(['accidental test:', p, pName, - # pDisplayStatus, 'target:', targetName, targetDisplayStatus]) - self.assertEqual(pName, targetName) + self.assertEqual(pName, targetName, + f'name error for {i}: {pName} instead of desired {targetName}') self.assertEqual( pDisplayStatus, targetDisplayStatus, @@ -333,8 +328,7 @@ def proc2(_pList, _past): def compare(_past, _result): # environLocal.printDebug(['accidental compare']) - for i in range(len(_result)): - p = _past[i] + for i, (p, _resultItem) in enumerate(zip(_past, _result)): if p.accidental is None: pName = None pDisplayStatus = None @@ -342,10 +336,10 @@ def compare(_past, _result): pName = p.accidental.name pDisplayStatus = p.accidental.displayStatus - targetName = _result[i][0] - targetDisplayStatus = _result[i][1] + targetName, targetDisplayStatus = _resultItem - self.assertEqual(pName, targetName) + self.assertEqual(pName, targetName, + f'name error for {i}: {pName} instead of desired {targetName}') self.assertEqual( pDisplayStatus, targetDisplayStatus, diff --git a/music21/tree/fromStream.py b/music21/tree/fromStream.py index 963d3a552c..21978096fc 100644 --- a/music21/tree/fromStream.py +++ b/music21/tree/fromStream.py @@ -396,9 +396,9 @@ def testFastPopulate(self): sf.isSorted = False sf._cache = {} sfTreeSlow = sf.asTree() - for i in range(len(sf)): - fastI = sfTree[i] - slowI = sfTreeSlow[i] + self.assertEqual(len(sf), len(sfTreeSlow)) + self.assertEqual(len(sf), len(sfTree)) + for fastI, slowI in zip(sfTree, sfTreeSlow): self.assertIs(fastI, slowI) def testAutoSortExample(self): diff --git a/music21/tree/timespanTree.py b/music21/tree/timespanTree.py index 767828663e..5ad4013305 100644 --- a/music21/tree/timespanTree.py +++ b/music21/tree/timespanTree.py @@ -849,9 +849,8 @@ def testTimespanTree(self): max(x.endTime for x in currentTimespansInList)) self.assertEqual(tsTree.lowestPosition(), currentPosition) self.assertEqual(tsTree.endTime, currentEndTime) - # pylint: disable=consider-using-enumerate - for j in range(len(currentTimespansInTree)): - self.assertEqual(currentTimespansInList[j], currentTimespansInTree[j]) + for inList, inTree in zip(currentTimespansInList, currentTimespansInTree): + self.assertEqual(inList, inTree) random.shuffle(tss) while tss: @@ -872,9 +871,8 @@ def testTimespanTree(self): max(x.endTime for x in currentTimespansInList)) self.assertEqual(tsTree.lowestPosition(), currentPosition) self.assertEqual(tsTree.endTime, currentEndTime) - # pylint: disable=consider-using-enumerate - for i in range(len(currentTimespansInTree)): - self.assertEqual(currentTimespansInList[i], currentTimespansInTree[i]) + for inList, inTree in zip(currentTimespansInList, currentTimespansInTree): + self.assertEqual(inList, inTree) # -----------------------------------------------------------------------------