From 6ac44dd7bb39b3a5657aea6a056a89d76b65e46d Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 24 May 2026 15:04:25 -1000 Subject: [PATCH] Find other missing fraction conversions (opFrac) @jacobtylerwalls PR #1900 showed a missing opFrac. With AI-Assistance decided to find and fix other missing cases. Also found a confusing naming `Variant. replacementDuration` which is actually a quarterLength -- renamed that but left the older replacementDuration alias in place. Will deprecate in v11 and remove in v12. AI-Assisted (Claude) --- music21/_version.py | 2 +- music21/abcFormat/__init__.py | 11 +- music21/alpha/analysis/ornamentRecognizer.py | 7 +- music21/base.py | 14 +- music21/duration.py | 36 +++- music21/expressions.py | 6 +- music21/freezeThaw.py | 2 +- music21/humdrum/spineParser.py | 8 +- music21/lily/translate.py | 16 +- music21/meter/base.py | 12 +- music21/meter/core.py | 17 +- music21/musedata/__init__.py | 25 +-- music21/musicxml/xmlToM21.py | 24 +-- music21/spanner.py | 10 +- music21/stream/base.py | 26 +-- music21/stream/core.py | 6 +- music21/stream/makeNotation.py | 5 +- music21/variant.py | 169 ++++++++++++------- 18 files changed, 237 insertions(+), 159 deletions(-) diff --git a/music21/_version.py b/music21/_version.py index 68bc2eaf2..6ec488d63 100644 --- a/music21/_version.py +++ b/music21/_version.py @@ -50,7 +50,7 @@ ''' from __future__ import annotations -__version__ = '10.2.0b1' +__version__ = '10.2.0b2' def get_version_tuple(vv): v = vv.split('.') diff --git a/music21/abcFormat/__init__.py b/music21/abcFormat/__init__.py index 3b9a68364..21712668f 100644 --- a/music21/abcFormat/__init__.py +++ b/music21/abcFormat/__init__.py @@ -85,6 +85,7 @@ import unittest from music21 import common +from music21.common.types import OffsetQL from music21 import defaults from music21 import environment from music21 import exceptions21 @@ -1403,7 +1404,7 @@ def __init__(self, src='', carriedAccidental: str = ''): # Pitch and duration attributes for m21 conversion # they are set via parse() based on other contextual information. self.pitchName: str|None = None # if None, a rest or chord - self.quarterLength: float = 0.0 + self.quarterLength: OffsetQL = 0.0 @staticmethod def _splitChordSymbols(strSrc: str) -> tuple[list[str], str]: @@ -1597,7 +1598,7 @@ def getPitchName( def getQuarterLength(self, strSrc: str, - forceDefaultQuarterLength: float|None = None) -> float: + forceDefaultQuarterLength: float|None = None) -> OffsetQL: ''' Called with parse(), after context processing, to calculate duration @@ -1666,7 +1667,7 @@ def getQuarterLength(self, # get default if numStr == '': - ql = activeDefaultQuarterLength + ql: OffsetQL = activeDefaultQuarterLength # if only, shorthand for /2 elif numStr == '/': ql = activeDefaultQuarterLength * 0.5 @@ -1676,7 +1677,7 @@ def getQuarterLength(self, ql = activeDefaultQuarterLength * 0.125 # if a half fraction elif numStr.startswith('/'): - ql = activeDefaultQuarterLength / int(numStr.split('/')[1]) + ql = common.opFrac(activeDefaultQuarterLength / int(numStr.split('/')[1])) # uncommon usage: 3/ short for 3/2 elif numStr.endswith('/'): n = int(numStr.split('/', maxsplit=1)[0].strip()) @@ -1695,7 +1696,7 @@ def getQuarterLength(self, assert dStr is not None n = int(nStr.strip()) d = int(dStr.strip()) - ql = activeDefaultQuarterLength * n / d + ql = common.opFrac(activeDefaultQuarterLength * n / d) # not a fraction; a multiplier else: ql = activeDefaultQuarterLength * int(numStr) diff --git a/music21/alpha/analysis/ornamentRecognizer.py b/music21/alpha/analysis/ornamentRecognizer.py index ac4e0762f..029f35b96 100644 --- a/music21/alpha/analysis/ornamentRecognizer.py +++ b/music21/alpha/analysis/ornamentRecognizer.py @@ -646,8 +646,11 @@ def testRecognizeTrill(self): self.assertFalse(trill, cond.name) -def calculateTrillNoteDuration(numTrillNotes, totalDuration): - return totalDuration.quarterLength / numTrillNotes +def calculateTrillNoteDuration( + numTrillNotes: int, + totalDuration: duration.Duration +) -> OffsetQL: + return opFrac(totalDuration.quarterLength / numTrillNotes) if __name__ == '__main__': diff --git a/music21/base.py b/music21/base.py index 591ce975b..17276a8a0 100644 --- a/music21/base.py +++ b/music21/base.py @@ -26,7 +26,7 @@ >>> music21.VERSION_STR -'10.2.0b1' +'10.2.0b2' Alternatively, after doing a complete import, these classes are available under the module "base": @@ -3605,7 +3605,7 @@ def measureNumber(self) -> int|None: mNumber = m.number # type: ignore return mNumber - def _getMeasureOffset(self, includeMeasurePadding=True) -> float|fractions.Fraction: + def _getMeasureOffset(self, includeMeasurePadding: bool = True) -> OffsetQL: # noinspection PyShadowingNames ''' Try to obtain the nearest Measure that contains this object, @@ -3637,11 +3637,11 @@ def _getMeasureOffset(self, includeMeasurePadding=True) -> float|fractions.Fract # TODO: v8 -- expose as public. activeS = self.activeSite - if activeS is not None and activeS.isMeasure: + if activeS is not None and isinstance(activeS, stream.Measure): # environLocal.printDebug(['found activeSite as Measure, using for offset']) offsetLocal = activeS.elementOffset(self) - if includeMeasurePadding: - offsetLocal += activeS.paddingLeft + if includeMeasurePadding and activeS.paddingLeft: + offsetLocal = opFrac(offsetLocal + activeS.paddingLeft) else: # environLocal.printDebug(['did not find activeSite as Measure, # doing context search', 'self.activeSite', self.activeSite]) @@ -3652,8 +3652,8 @@ def _getMeasureOffset(self, includeMeasurePadding=True) -> float|fractions.Fract # environLocal.printDebug(['using found Measure for offset access']) try: offsetLocal = m.elementOffset(self) - if includeMeasurePadding: - offsetLocal += m.paddingLeft + if includeMeasurePadding and m.paddingLeft: + offsetLocal = opFrac(offsetLocal + m.paddingLeft) except SitesException: offsetLocal = self.offset diff --git a/music21/duration.py b/music21/duration.py index 0acafae94..9a96724ec 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -792,6 +792,28 @@ def convertTypeToNumber(dType: str) -> float: # ----------------------------------------------------------------------------------- class DurationTuple(t.NamedTuple): + ''' + A DurationTuple is the atomic, un-tupleted building block of a + :class:`~music21.duration.Duration`. It bundles a note `type` + (e.g. ``'quarter'``, ``'eighth'``) with a + number of `dots` and the `quarterLength` that those produce: + + >>> dt = duration.DurationTuple('quarter', 1, 1.5) + >>> dt.type + 'quarter' + >>> dt.dots + 1 + >>> dt.quarterLength + 1.5 + + Most Durations have one DurationTuple stored in `.components`. Tuplet notes + have a DurationTuple and a Tuplet object. A complex Duration (like + a quarter tied to an sixteenth) can have tow or more DurationTuples. + + A DurationTuple's `quarterLength` is also stored alongside `type` and `dots` + for quick computation. For `inexpressible` DurationTuples, `quarterLength` + stores the ground-truth. + ''' type: str dots: int quarterLength: OffsetQL @@ -823,7 +845,7 @@ def ordinal(self): raise DurationException( f'Could not determine durationNumber from {self.type}') - +# Caches for DurationTuple lookups _durationTupleCacheTypeDots: dict[tuple[str, int], DurationTuple] = {} _durationTupleCacheQuarterLength: dict[OffsetQL, DurationTuple] = {} @@ -1889,7 +1911,7 @@ def appendTuplet(self, newTuplet: Tuplet) -> None: newTuplet.frozen = True self.tuplets = self._tuplets + (newTuplet,) - def augmentOrDiminish(self, amountToScale, retainComponents=False): + def augmentOrDiminish(self, amountToScale: OffsetQLIn, retainComponents=False) -> t.Self: ''' Given a number greater than zero, creates a new Duration object after @@ -1897,8 +1919,7 @@ def augmentOrDiminish(self, amountToScale, retainComponents=False): duration by the number and resets the components for the duration (by default). - Returns a new duration that has - the new length. + Returns a new duration that has the new length. >>> aDur = duration.Duration() >>> aDur.quarterLength = 1.5 # dotted quarter @@ -1978,6 +1999,9 @@ def augmentOrDiminish(self, amountToScale, retainComponents=False): >>> fDur.augmentOrDiminish(-1) Traceback (most recent call last): ValueError: amountToScale must be greater than zero + + Note: for unlinked durations w/ tuplet scaling, amountToScale must already + by a Fraction. ''' if not amountToScale > 0: raise ValueError('amountToScale must be greater than zero') @@ -1993,6 +2017,7 @@ def augmentOrDiminish(self, amountToScale, retainComponents=False): self._quarterLengthNeedsUpdating = True else: post.tuplets = () + # post.quarterLength will run opFrac unless it is unlinked. post.quarterLength = self.quarterLength * amountToScale return post @@ -2098,7 +2123,8 @@ def componentStartTime(self, componentIndex: int) -> float: For a valid component index value, this returns the quarter note offset at which that component would start. - This does not handle fractional arguments. + This method does not handle fractional arguments, since components are stored + untupleted. >>> components = [] >>> qdt = duration.DurationTuple('quarter', 0, 1.0) diff --git a/music21/expressions.py b/music21/expressions.py index 2646b511d..bd10bebcc 100644 --- a/music21/expressions.py +++ b/music21/expressions.py @@ -2244,8 +2244,8 @@ def realize( keySig: key.KeySignature|None = None, inPlace: bool = False ) -> tuple[list[note.Note|note.Unpitched], - note.Note|note.Unpitched|None, - list[note.Note|note.Unpitched]]: + note.Note|note.Unpitched|None, + list[note.Note|note.Unpitched]]: # noinspection PyShadowingNames ''' realize a turn. @@ -2371,7 +2371,7 @@ def realize( if turnDuration < 4 * self.quarterLength: if not self.autoScale: raise ExpressionException('The note is not long enough to realize a turn') - useQL = opFrac(turnDuration / 4) + useQL = turnDuration / 4 elif turnDuration > 4 * self.quarterLength: # in this case, we keep the first 3 turn notes as self.quarterLength, and # extend the 4th turn note to finish up the turnDuration diff --git a/music21/freezeThaw.py b/music21/freezeThaw.py index 6f5217e2d..2f0d4f3c5 100644 --- a/music21/freezeThaw.py +++ b/music21/freezeThaw.py @@ -1154,7 +1154,7 @@ def testFreezeThawVariant(self): stream2.append(m) # c.show('t') variant.addVariant(c.parts[0], 6.0, stream2, - variantName='rhythmic_switch', replacementDuration=3.0) + variantName='rhythmic_switch', replacementQuarterLength=3.0) # test Variant is in stream unused_v1 = c.parts.first().getElementsByClass(variant.Variant).first() diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index 26ce03f7b..b731a129e 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -1289,8 +1289,8 @@ def moveElementsIntoMeasures(self, streamIn: stream.Stream) -> stream.Stream: m1TimeSignature = m1.timeSignature if m1TimeSignature is not None: if m1.duration.quarterLength < m1TimeSignature.barDuration.quarterLength: - m1.paddingLeft = (m1TimeSignature.barDuration.quarterLength - - m1.duration.quarterLength) + m1.paddingLeft = opFrac(m1TimeSignature.barDuration.quarterLength + - m1.duration.quarterLength) return streamOut @@ -2409,8 +2409,8 @@ def hdStringToNote(contents: str) -> note.GeneralNote: foundNumber = re.search(r'(\d+)', contents) if foundRational: durationFirst = int(foundRational.group(1)) - durationSecond = float(foundRational.group(2)) - thisObject.duration.quarterLength = 4 * durationSecond / durationFirst + durationSecond = int(foundRational.group(2)) + thisObject.duration.quarterLength = opFrac(4 * durationSecond / durationFirst) if '.' in contents: thisObject.duration.dots = contents.count('.') diff --git a/music21/lily/translate.py b/music21/lily/translate.py index ec06edd07..ec3300c29 100644 --- a/music21/lily/translate.py +++ b/music21/lily/translate.py @@ -1877,13 +1877,13 @@ def lyPrefixCompositeMusicFromRelatedVariants(self, >>> v1 = variant.Variant() >>> for el in s1: ... v1.append(el) - >>> v1.replacementDuration = 4.0 + >>> v1.replacementQuarterLength = 4.0 >>> v2 = variant.Variant() >>> sp2 = note.Rest() >>> sp2.style.hideObjectOnPrint = True >>> sp2.duration.quarterLength = 4.0 - >>> v2.replacementDuration = 4.0 + >>> v2.replacementQuarterLength = 4.0 >>> v2.append(sp2) >>> for el in s2: ... v2.append(el) @@ -1892,7 +1892,7 @@ def lyPrefixCompositeMusicFromRelatedVariants(self, >>> sp3 = note.Rest() >>> sp3.style.hideObjectOnPrint = True >>> sp3.duration.quarterLength = 8.0 - >>> v3.replacementDuration = 4.0 + >>> v3.replacementQuarterLength = 4.0 >>> v3.append(sp3) >>> for el in s3: ... v3.append(el) @@ -1901,7 +1901,7 @@ def lyPrefixCompositeMusicFromRelatedVariants(self, >>> sp4 = note.Rest() >>> sp4.style.hideObjectOnPrint = True >>> sp4.duration.quarterLength = 16.0 - >>> v4.replacementDuration = 4.0 + >>> v4.replacementQuarterLength = 4.0 >>> v4.append(sp4) >>> for el in s4: ... v4.append(el) @@ -2015,7 +2015,7 @@ def findOffsetOfFirstNonSpacerElement(inputStream): raise LilyTranslateException('Should not have overlapping variants.') spacerDuration = firstOffset - highestOffsetSoFar - highestOffsetSoFar = v.replacementDuration + firstOffset + highestOffsetSoFar = v.replacementQuarterLength + firstOffset # make spacer with spacerDuration and append if spacerDuration > 0.0: @@ -2034,9 +2034,9 @@ def findOffsetOfFirstNonSpacerElement(inputStream): endOffset = v.containedHighestTime vStripped = variant.Variant(v._stream.getElementsByOffset(firstOffset, offsetEnd=endOffset)) - vStripped.replacementDuration = v.replacementDuration + vStripped.replacementQuarterLength = v.replacementQuarterLength - replacedElementsLength = vStripped.replacementDuration + replacedElementsLength = vStripped.replacementQuarterLength variantLength = vStripped.containedHighestTime - firstOffset if variantLength != replacedElementsLength: @@ -2164,7 +2164,7 @@ def lyPrefixCompositeMusicFromVariant(self, lpOssiaMusicVariant = self.lyOssiaMusicFromVariant(variantObject) - replacedElementsLength = variantObject.replacementDuration + replacedElementsLength = variantObject.replacementQuarterLength variantLength = variantObject.containedHighestTime - spacerDur self.variantMode = True diff --git a/music21/meter/base.py b/music21/meter/base.py index 8a3a303c7..356cca9c9 100644 --- a/music21/meter/base.py +++ b/music21/meter/base.py @@ -1304,7 +1304,7 @@ def _setDefaultAccentWeights(self, depth: int = 3) -> None: divStep = self.barDuration.quarterLength / accentCount weightInts = [0] * accentCount # weights as integer/depth counts for i in range(accentCount): - ql = i * divStep + ql = opFrac(i * divStep) weightInts[i] = ms.offsetToDepth(ql, align='quantize', index=i) maxInt = max(weightInts) @@ -1729,7 +1729,7 @@ def averageBeatStrength(self, streamIn, notesOnly=True): totalWeight += elWeight return totalWeight / totalObjects - def getMeasureOffsetOrMeterModulusOffset(self, el): + def getMeasureOffsetOrMeterModulusOffset(self, el: base.Music21Object) -> OffsetQL: ''' Return the measure offset based on a Measure, if it exists, otherwise based on meter modulus of the TimeSignature. @@ -1766,12 +1766,12 @@ def getMeasureOffsetOrMeterModulusOffset(self, el): ''' mOffset = el._getMeasureOffset() # TODO(msc): expose this method and remove private tsMeasureOffset = self._getMeasureOffset(includeMeasurePadding=False) - if (mOffset + tsMeasureOffset) < self.barDuration.quarterLength: + if opFrac(mOffset + tsMeasureOffset) < self.barDuration.quarterLength: return mOffset else: # must get offset relative to not just start of Stream, but the last # time signature - post = ((mOffset - tsMeasureOffset) % self.barDuration.quarterLength) + post = opFrac((mOffset - tsMeasureOffset) % self.barDuration.quarterLength) # environLocal.printDebug(['result', post]) return post @@ -2018,7 +2018,7 @@ def getBeatProgress(self, qLenPos): >>> a = meter.TimeSignature('3/4', 3) >>> a.getBeatProgress(0) - (1, 0) + (1, 0.0) >>> a.getBeatProgress(0.75) (1, 0.75) >>> a.getBeatProgress(1.0) @@ -2035,7 +2035,7 @@ def getBeatProgress(self, qLenPos): ''' beatIndex = self.beatSequence.offsetToIndex(qLenPos) start, unused_end = self.beatSequence.offsetToSpan(qLenPos) - return beatIndex + 1, qLenPos - start + return beatIndex + 1, opFrac(qLenPos - start) def getBeatProportion(self, qLenPos): ''' diff --git a/music21/meter/core.py b/music21/meter/core.py index 1627452c1..206c7d2c7 100644 --- a/music21/meter/core.py +++ b/music21/meter/core.py @@ -18,14 +18,15 @@ import copy import typing as t -from music21 import prebase +from music21 import common from music21.common.numberTools import opFrac from music21.common.objects import SlottedObjectMixin -from music21 import common +from music21.common.types import OffsetQLIn from music21.duration import Duration, DurationException from music21 import environment from music21.exceptions21 import MeterException from music21.meter import tools +from music21 import prebase environLocal = environment.Environment('meter.core') @@ -1915,7 +1916,7 @@ def offsetToAddress(self, qLenPos, includeCoincidentBoundaries=False): i = None for i in range(len(self)): start = qPos - end = qPos + self[i].duration.quarterLength + end = opFrac(qPos + self[i].duration.quarterLength) # if adjoining ends are permitted, first match is found if includeCoincidentBoundaries: if start <= qLenPos <= end: @@ -1925,13 +1926,13 @@ def offsetToAddress(self, qLenPos, includeCoincidentBoundaries=False): if start <= qLenPos < end: match.append(i) break - qPos += self[i].duration.quarterLength + qPos = opFrac(qPos + self[i].duration.quarterLength) if i is not None and isinstance(self[i], MeterSequence): # recurse # qLenPosition needs to be relative to this subdivision # start is our current position that this subdivision # starts at - qLenPosShift = qLenPos - start + qLenPosShift = opFrac(qLenPos - start) match += self[i].offsetToAddress(qLenPosShift, includeCoincidentBoundaries) @@ -2014,9 +2015,10 @@ def offsetToWeight(self, qLenPos): iMatch = self.offsetToIndex(qLenPos) return opFrac(self[iMatch].weight) - def offsetToDepth(self, qLenPos, align='quantize', index: int|None = None): + def offsetToDepth(self, qLenPos: OffsetQLIn, align: str = 'quantize', index: int|None = None): ''' - Given a qLenPos, return the maximum available depth at this position. + Given a qLenPos, return the maximum available depth at this position. Align can be + start, quantize, or end. >>> b = meter.MeterSequence('4/4', 4) >>> b[1] = b[1].subdivide(2) @@ -2040,6 +2042,7 @@ def offsetToDepth(self, qLenPos, align='quantize', index: int|None = None): * Changed in v7: `index` can be provided, if known, for a long `MeterSequence` to improve performance. ''' + # TODO: Change the aligns to be StrEnums. qLenPos = opFrac(qLenPos) if qLenPos >= self.duration.quarterLength or qLenPos < 0: raise MeterException(f'cannot access from qLenPos {qLenPos}') diff --git a/music21/musedata/__init__.py b/music21/musedata/__init__.py index 0211863ed..7de8afd3b 100644 --- a/music21/musedata/__init__.py +++ b/music21/musedata/__init__.py @@ -54,6 +54,7 @@ from music21 import common from music21 import prebase +from music21.common.types import OffsetQL environLocal = environment.Environment('musedata') @@ -295,7 +296,7 @@ def getPitchObject(self): # environLocal.printDebug(['p', p]) return p - def getQuarterLength(self, divisionsPerQuarterNote=None): + def getQuarterLength(self, divisionsPerQuarterNote: int|None = None) -> OffsetQL: ''' Gets the quarterLength of the note given the prevailing divisionsPerQuarterNote @@ -340,7 +341,7 @@ def getQuarterLength(self, divisionsPerQuarterNote=None): else: raise MuseDataException('cannot access parent container of this record ' + 'to obtain divisions per quarter') - return divisions / dpq + return common.opFrac(divisions / dpq) def getDots(self): if self.stage == 1: @@ -353,16 +354,16 @@ def getDots(self): return 2 return 0 -# def getType(self): -# # TODO: column 17 self.src[16] defines the graphic note type -# # this may or may not align with derived quarter length -# if self.stage == 1: -# return None -# else: -# if len(self.src) == 0: -# return None -# data = self.src[16] -# return data + # def getType(self): + # # TODO: column 17 self.src[16] defines the graphic note type + # # this may or may not align with derived quarter length + # if self.stage == 1: + # return None + # else: + # if len(self.src) == 0: + # return None + # data = self.src[16] + # return data def getLyrics(self): ''' diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py index d64eb3fdd..0f71957d5 100644 --- a/music21/musicxml/xmlToM21.py +++ b/music21/musicxml/xmlToM21.py @@ -2607,7 +2607,8 @@ def addToStaffReference(self, mxObjectOrNumber, m21Object): def insertCoreAndRef(self, offset, mxObjectOrNumber, m21Object): ''' - runs addToStaffReference and then insertCore. + runs addToStaffReference and then insertCore (which will do opFracs, so no need to do + so before here) >>> from xml.etree.ElementTree import fromstring as EL >>> mxNote = EL('1') @@ -2665,13 +2666,13 @@ def xmlBackup(self, mxObj: ET.Element): >>> MP = musicxml.xmlToM21.MeasureParser() >>> MP.divisions = 100 - >>> MP.offsetMeasureNote = 1.9979 + >>> MP.offsetMeasureNote = 1.875 >>> from xml.etree.ElementTree import fromstring as EL >>> mxBackup = EL('100') >>> MP.xmlBackup(mxBackup) >>> MP.offsetMeasureNote - 0.9979 + 0.875 >>> MP.xmlBackup(mxBackup) >>> MP.offsetMeasureNote @@ -2679,8 +2680,8 @@ def xmlBackup(self, mxObj: ET.Element): ''' mxDuration = mxObj.find('duration') if durationText := strippedText(mxDuration): - change = opFrac(float(durationText) / self.divisions) - self.offsetMeasureNote -= change + self.offsetMeasureNote = opFrac(self.offsetMeasureNote + - float(durationText) / self.divisions) # check for negative offsets produced by # musicxml durations with float rounding issues # https://github.com/cuthbertLab/music21/issues/971 @@ -2710,7 +2711,7 @@ def xmlForward(self, mxObj: ET.Element): self.lastForwardTagCreatedByFinale = r # Allow overfilled measures for now -- TODO(someday): warn? - self.offsetMeasureNote += change + self.offsetMeasureNote = opFrac(self.offsetMeasureNote + change) def xmlPrint(self, mxPrint: ET.Element): ''' @@ -2869,7 +2870,7 @@ def xmlToNote(self, mxNote: ET.Element) -> None: self.nLast = c # update # only increment Chords after completion - self.offsetMeasureNote += offsetIncrement + self.offsetMeasureNote = opFrac(self.offsetMeasureNote + offsetIncrement) self.lastForwardTagCreatedByFinale = None def xmlToChord(self, mxNoteList: list[ET.Element]) -> chord.ChordBase: @@ -5720,7 +5721,7 @@ def xmlToTempoIndication(self, mxMetronome, mxWords=None): self.setPosition(mxMetronome, mm) return mm - def xmlToOffset(self, mxObj): + def xmlToOffset(self, mxObj: ET.Element) -> float: ''' Finds an inside the mxObj and returns it as a music21 offset (in quarterLengths) @@ -5732,17 +5733,16 @@ def xmlToOffset(self, mxObj): >>> MP.xmlToOffset(off) 2.5 - Returns a float, not fraction. + Returns a float, not fraction, since the inserts will later convert to a Fraction + if need be. >>> MP.divisions = 30 >>> off = EL(r'10') >>> MP.xmlToOffset(off) 0.33333... - ''' - try: - offset = float(mxObj.find('offset').text.strip()) + offset = float(mxObj.find('offset').text.strip()) # type: ignore except (ValueError, AttributeError): return 0.0 return offset / self.divisions diff --git a/music21/spanner.py b/music21/spanner.py index 297137e2b..98944c55c 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -27,6 +27,7 @@ from music21 import base from music21 import common +from music21.common.numberTools import opFrac from music21.common.types import OffsetQL from music21 import defaults from music21 import environment @@ -687,9 +688,8 @@ def fill( endOffsetInHierarchy: OffsetQL if endElement is not None: try: - endOffsetInHierarchy = ( - endElement.getOffsetInHierarchy(searchStream) + endElement.quarterLength - ) + endOffsetInHierarchy = opFrac(endElement.getOffsetInHierarchy(searchStream) + + endElement.quarterLength) except sites.SitesException: # print('end element not in searchStream') self.addSpannedElements(endElement) @@ -699,9 +699,7 @@ def fill( endElement.activeSite = savedEndElementActiveSite return else: - endOffsetInHierarchy = ( - startOffsetInHierarchy + startElement.quarterLength - ) + endOffsetInHierarchy = opFrac(startOffsetInHierarchy + startElement.quarterLength) matchIterator = (searchStream .recurse() diff --git a/music21/stream/base.py b/music21/stream/base.py index db852fc73..9e192264a 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -11822,7 +11822,7 @@ def activateVariants(self, group=None, *, matchBySpan=True, inPlace=False): replace elements defined in the Variant with those in the calling Stream. Elements replaced will be gathered into a new Variant given the group 'default'. If a variant is activated with - .replacementDuration different from its length, the appropriate elements + .replacementQuarterLength different from its length, the appropriate elements in the stream will have their offsets shifted, and measure numbering will be fixed. If matchBySpan is True, variants with lengthType 'replacement' will replace all the elements in the @@ -11851,7 +11851,7 @@ def activateVariants(self, group=None, *, matchBySpan=True, inPlace=False): ... v1measure.insert(e.offset, e) >>> v2 = variant.Variant() - >>> v2.replacementDuration = 4.0 + >>> v2.replacementQuarterLength = 4.0 >>> v2measure1 = stream.Measure() >>> v2measure2 = stream.Measure() >>> v2.insert(0.0, v2measure1) @@ -11862,7 +11862,7 @@ def activateVariants(self, group=None, *, matchBySpan=True, inPlace=False): ... v2measure2.insert(e.offset, e) >>> v3 = variant.Variant() - >>> v3.replacementDuration = 4.0 + >>> v3.replacementQuarterLength = 4.0 >>> v1.groups = ['docVariants'] >>> v2.groups = ['docVariants'] >>> v3.groups = ['docVariants'] @@ -12078,8 +12078,8 @@ def activateVariants(self, group=None, *, matchBySpan=True, inPlace=False): insertionRegionsForExpansion = [] # For saving the insertion regions # go through all elongation variants to find the insertion regions. for v in elongationVariants: - lengthDifference = v.replacementDuration - v.containedHighestTime - insertionStart = v.getOffsetBySite(returnObj) + v.replacementDuration + lengthDifference = v.replacementQuarterLength - v.containedHighestTime + insertionStart = v.getOffsetBySite(returnObj) + v.replacementQuarterLength # Saves the information for each gap to be expanded insertionRegionsForExpansion.append((insertionStart, -1 * lengthDifference, [v])) @@ -12126,7 +12126,7 @@ def _insertReplacementVariant(self, v, matchBySpan=True): ... m.append(n) ... v.append(m) >>> v.groups = ['paris'] - >>> v.replacementDuration = 8.0 + >>> v.replacementQuarterLength = 8.0 >>> s = stream.Stream() >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] @@ -12286,7 +12286,7 @@ def _insertDeletionVariant(self, v, matchBySpan=True): ... m.append(n) ... v.append(m) >>> v.groups = ['paris'] - >>> v.replacementDuration = 12.0 + >>> v.replacementQuarterLength = 12.0 >>> s = stream.Stream() >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] @@ -12336,11 +12336,11 @@ def _insertDeletionVariant(self, v, matchBySpan=True): deletedMeasures = deque() # For keeping track of what measure numbers are deleted # length of the deleted region - lengthDifference = v.replacementDuration - v.containedHighestTime + lengthDifference = v.replacementQuarterLength - v.containedHighestTime removed = variant.Variant() # what group should this have? removed.groups = ['default'] # for now, default - removed.replacementDuration = v.containedHighestTime + removed.replacementQuarterLength = v.containedHighestTime vStart = self.elementOffset(v) deletionStart = vStart + v.containedHighestTime @@ -12411,7 +12411,7 @@ def _insertInsertionVariant(self, v, matchBySpan=True): ... m.append(n) ... v.append(m) >>> v.groups = ['paris'] - >>> v.replacementDuration = 4.0 + >>> v.replacementQuarterLength = 4.0 >>> s = stream.Stream() >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] @@ -12474,7 +12474,7 @@ def _insertInsertionVariant(self, v, matchBySpan=True): deletedMeasures = deque() removed = variant.Variant() # what group should this have? removed.groups = ['default'] # for now, default - removed.replacementDuration = v.containedHighestTime + removed.replacementQuarterLength = v.containedHighestTime vStart = self.elementOffset(v) # First deal with the elements in the overlapping section (limit by class) @@ -12854,8 +12854,8 @@ def showVariantAsOssialikePart(self, containedPart, variantGroups, *, inPlace=Fa ... v2measure.insert(e.offset, e) >>> v3 = variant.Variant() - >>> v2.replacementDuration = 4.0 - >>> v3.replacementDuration = 4.0 + >>> v2.replacementQuarterLength = 4.0 + >>> v3.replacementQuarterLength = 4.0 >>> v1.groups = ['variant1'] >>> v2.groups = ['variant2'] >>> v3.groups = ['variant3'] diff --git a/music21/stream/core.py b/music21/stream/core.py index 33748bd64..703347c48 100644 --- a/music21/stream/core.py +++ b/music21/stream/core.py @@ -93,6 +93,8 @@ def coreInsert( Returns boolean if the Stream (assuming it was sorted before) is still guaranteed to be sorted. (False doesn't mean that it's not sorted, just that we can't guarantee it.) If you don't care and plan to sort the stream later, then use `ignoreSort=True`. + + No need to opFrac before getting here -- the inner routine will do it for us. ''' # environLocal.printDebug(['coreInsert', 'self', self, # 'offset', offset, 'element', element]) @@ -120,7 +122,7 @@ def coreInsert( self.coreSetElementOffset( element, - float(offset), # why is this not opFrac? + offset, # coreSetElementOffset will opFrac for us addElement=True, setActiveSite=setActiveSite ) @@ -159,7 +161,7 @@ def coreAppend( # Make this faster # self._elementTree.insert(self.highestTime, element) # does not change sorted state - self._setHighestTime(ht + element.duration.quarterLength) # type: ignore + self._setHighestTime(opFrac(ht + element.duration.quarterLength)) # type: ignore # -------------------------------------------------------------------------- # adding and editing Elements and Streams -- all need to call coreElementsChanged # most will set isSorted to False diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py index cbd2b5d7a..b6bb43c6e 100644 --- a/music21/stream/makeNotation.py +++ b/music21/stream/makeNotation.py @@ -877,6 +877,7 @@ def oHighTargetForMeasure( if m is not None: post -= m.paddingLeft post -= m.paddingRight + post = opFrac(post) return max(post, 0.0) oLowTarget: OffsetQL = 0.0 @@ -895,9 +896,9 @@ def oHighTargetForMeasure( elif returnObj.hasMeasures(): # This could be optimized to save some context searches, # but at the cost of readability. - oHighTarget = sum( + oHighTarget = opFrac(sum( m.barDuration.quarterLength for m in returnObj.getElementsByClass(stream.Measure) - ) + )) # If the above search didn't run or still yielded 0.0, use refStreamOrTimeRange if oHighTarget == 0.0: diff --git a/music21/variant.py b/music21/variant.py index 1ac59377a..cea8d3ffc 100644 --- a/music21/variant.py +++ b/music21/variant.py @@ -106,7 +106,7 @@ def __init__( self._stream = stream.VariantStorage(givenElements=givenElements, givenElementsBehavior=givenElementsBehavior) - self._replacementDuration = None + self._replacementQuarterLength = None if name is not None: self.groups.append(name) @@ -271,22 +271,47 @@ def containedSite(self): ''' return self._stream - def _getReplacementDuration(self): - if self._replacementDuration is None: + def _getReplacementQuarterLength(self): + if self._replacementQuarterLength is None: return self._stream.duration.quarterLength else: - return self._replacementDuration + return self._replacementQuarterLength + + def _setReplacementQuarterLength(self, value): + self._replacementQuarterLength = value + + replacementQuarterLength = property( + _getReplacementQuarterLength, + _setReplacementQuarterLength, + doc=''' + Set or Return the quarterLength in the main stream which this variant + object replaces in the variant version of the stream. If replacementQuarterLength is + not set, it is assumed to be the same length as the variant. If it is set to 0, + the variant should be interpreted as an insertion. Setting replacementQuarterLength + to None will return the value to the default which is the length of the variant + itself. + + * New in v10.3: renamed from ``replacementDuration``. + ''') + + def _getReplacementDuration(self): + return self.replacementQuarterLength def _setReplacementDuration(self, value): - self._replacementDuration = value - - replacementDuration = property(_getReplacementDuration, _setReplacementDuration, doc=''' - Set or Return the quarterLength duration in the main stream which this variant - object replaces in the variant version of the stream. If replacementDuration is - not set, it is assumed to be the same length as the variant. If, it is set to 0, - the variant should be interpreted as an insertion. Setting replacementDuration - to None will return the value to the default which is the duration of the variant - itself. + self.replacementQuarterLength = value + + replacementDuration = property( + _getReplacementDuration, + _setReplacementDuration, + doc=''' + Synonym for :attr:`replacementQuarterLength`. + + .. note:: + + ``replacementDuration`` is pending deprecation as of v10.3 (music21 + avoids referring to an offset/quarterLength value as a "Duration"). It will be + fully deprecated in v11 and removed in v12. Use + :attr:`replacementQuarterLength` instead. ''') @property @@ -296,7 +321,7 @@ def lengthType(self): if the variant is longer than the region it replaces, and 'replacement' if it is the same length. ''' - lengthDifference = self.replacementDuration - self.containedHighestTime + lengthDifference = self.replacementQuarterLength - self.containedHighestTime if lengthDifference > 0.0: return 'deletion' elif lengthDifference < 0.0: @@ -310,7 +335,7 @@ def replacedElements(self, contextStream=None, classList=None, ''' Returns a Stream containing the elements which this variant replaces in a given context stream. - This Stream will have length self.replacementDuration. + This Stream will have length self.replacementQuarterLength. In regions that are strictly replaced, only elements that share a class with an element in the variant @@ -339,8 +364,8 @@ def replacedElements(self, contextStream=None, classList=None, ... v2measure2.insert(e.offset, e) >>> v3 = variant.Variant() - >>> v2.replacementDuration = 4.0 - >>> v3.replacementDuration = 4.0 + >>> v2.replacementQuarterLength = 4.0 + >>> v3.replacementQuarterLength = 4.0 >>> s.insert(4.0, v1) # replacement variant >>> s.insert(12.0, v2) # insertion variant (2 bars replace 1 bar) @@ -387,7 +412,7 @@ def replacedElements(self, contextStream=None, classList=None, ... m.append(n) ... v.append(m) >>> v.groups = ['paris'] - >>> v.replacementDuration = 4.0 + >>> v.replacementQuarterLength = 4.0 >>> s = stream.Stream() >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] @@ -439,7 +464,7 @@ def replacedElements(self, contextStream=None, classList=None, if self.lengthType in ('replacement', 'elongation'): - vEnd = vStart + self.replacementDuration + spacerDuration + vEnd = vStart + self.replacementQuarterLength + spacerDuration classes = [] for e in self.elements: classes.append(e.classes[0]) @@ -453,7 +478,7 @@ def replacedElements(self, contextStream=None, classList=None, elif self.lengthType == 'deletion': vMiddle = vStart + self.containedHighestTime - vEnd = vStart + self.replacementDuration + vEnd = vStart + self.replacementQuarterLength classes = [] # collect all classes found in this variant for e in self.elements: classes.append(e.classes[0]) @@ -507,7 +532,7 @@ def removeReplacedElementsFromStream(self, referenceStream=None, classList=None) ... m.append(n) ... v.append(m) >>> v.groups = ['paris'] - >>> v.replacementDuration = 4.0 + >>> v.replacementQuarterLength = 4.0 >>> s = stream.Stream() >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] @@ -859,9 +884,9 @@ def mergeVariantMeasureStreams(streamX, streamY, variantName='variant', *, inPla {3.0} {16.0} - >>> mergedStream[variant.Variant][0].replacementDuration + >>> mergedStream[variant.Variant][0].replacementQuarterLength 4.0 - >>> mergedStream[variant.Variant][1].replacementDuration + >>> mergedStream[variant.Variant][1].replacementQuarterLength 0.0 >>> parisStream = mergedStream.activateVariants('paris', inPlace=False) @@ -904,11 +929,11 @@ def mergeVariantMeasureStreams(streamX, streamY, variantName='variant', *, inPla {2.0} {3.0} - >>> parisStream[variant.Variant][0].replacementDuration + >>> parisStream[variant.Variant][0].replacementQuarterLength 0.0 - >>> parisStream[variant.Variant][1].replacementDuration + >>> parisStream[variant.Variant][1].replacementQuarterLength 4.0 - >>> parisStream[variant.Variant][2].replacementDuration + >>> parisStream[variant.Variant][2].replacementQuarterLength 8.0 ''' if inPlace is True: @@ -929,26 +954,26 @@ def mergeVariantMeasureStreams(streamX, streamY, variantName='variant', *, inPla startOffset = returnObj.measure(xRegionStartMeasure + 1).getOffsetBySite(returnObj) yRegion = None - replacementDuration = 0.0 + replacementQuarterLength = 0.0 if regionType == 'equal': # yRegion = streamY.measures(yRegionStartMeasure + 1, yRegionEndMeasure) continue # Do nothing elif regionType == 'replace': xRegion = returnObj.measures(xRegionStartMeasure + 1, xRegionEndMeasure) - replacementDuration = xRegion.duration.quarterLength + replacementQuarterLength = xRegion.duration.quarterLength yRegion = streamY.measures(yRegionStartMeasure + 1, yRegionEndMeasure) elif regionType == 'delete': xRegion = returnObj.measures(xRegionStartMeasure + 1, xRegionEndMeasure) - replacementDuration = xRegion.duration.quarterLength + replacementQuarterLength = xRegion.duration.quarterLength yRegion = None elif regionType == 'insert': yRegion = streamY.measures(yRegionStartMeasure + 1, yRegionEndMeasure) - replacementDuration = 0.0 + replacementQuarterLength = 0.0 else: raise VariantException(f'Unknown regionType {regionType!r}') addVariant(returnObj, startOffset, yRegion, - variantName=variantName, replacementDuration=replacementDuration) + variantName=variantName, replacementQuarterLength=replacementQuarterLength) if inPlace is True: return @@ -1339,7 +1364,7 @@ def mergePartAsOssia(mainPart, ossiaPart, ossiaName, ossiaMeasure, variantName=ossiaName, variantGroups=None, - replacementDuration=None + replacementQuarterLength=None ) else: for ossiaMeasure in ossiaPart.getElementsByClass(stream.Measure): @@ -1356,7 +1381,8 @@ def mergePartAsOssia(mainPart, ossiaPart, ossiaName, ) else: addVariant(returnObj, ossiaOffset, ossiaMeasure, - variantName=ossiaName, variantGroups=None, replacementDuration=None) + variantName=ossiaName, variantGroups=None, + replacementQuarterLength=None) if inPlace is True: return @@ -1372,19 +1398,26 @@ def addVariant( sVariant: stream.Stream|Variant, variantName=None, variantGroups=None, - replacementDuration=None + replacementQuarterLength=None, + *, + replacementDuration=None, ): # noinspection PyShadowingNames ''' Takes a stream, the location of the variant to be added to that stream (startOffset), the content of the - variant to be added (sVariant), and the duration of the section of the stream which the variant - replaces (replacementDuration). + variant to be added (sVariant), and the quarterLength of the section of the stream which + the variant replaces (replacementQuarterLength). - If replacementDuration is 0, + If replacementQuarterLength is 0, this is an insertion. If sVariant is None, this is a deletion. + * Changed in v10.3: the ``replacementDuration`` keyword was renamed to + ``replacementQuarterLength``. The old name is still accepted as a + keyword-only argument but is pending deprecation: it will warn in v11 + and be removed in v12. + >>> data1M1 = [('a', 'quarter'), ('b', 'eighth'), ('c', 'eighth'), ... ('a', 'quarter'), ('a', 'quarter')] @@ -1412,7 +1445,7 @@ def addVariant( ... m.append(n) >>> stream2.append(m) >>> variant.addVariant(stream1, 4.0, stream2, - ... variantName='rhythmic_switch', replacementDuration=4.0) + ... variantName='rhythmic_switch', replacementQuarterLength=4.0) >>> stream1.show('text') {0.0} {0.0} @@ -1439,7 +1472,7 @@ def addVariant( >>> variant1.repeatAppend(note.Note('f'), 3) >>> startOffset = 3.0 >>> variant.addVariant(stream1, startOffset, variant1, - ... variantName='paris', replacementDuration=3.0) + ... variantName='paris', replacementQuarterLength=3.0) >>> stream1.show('text') {0.0} {1.0} @@ -1449,6 +1482,15 @@ def addVariant( {4.0} {5.0} ''' + if replacementDuration is not None: + # 'replacementDuration' was renamed to 'replacementQuarterLength' in v10.3. + # Pending deprecation: still accepted in v10, will warn in v11, be removed in v12. + if replacementQuarterLength is not None: + raise TypeError( + "addVariant() received both 'replacementQuarterLength' and its former " + + "name 'replacementDuration'; pass only 'replacementQuarterLength'.") + replacementQuarterLength = replacementDuration + tempVariant = Variant() if variantGroups is not None: @@ -1456,7 +1498,7 @@ def addVariant( if variantName is not None: tempVariant.groups.append(variantName) - tempVariant.replacementDuration = replacementDuration + tempVariant.replacementQuarterLength = replacementQuarterLength if sVariant is None: # deletion pass @@ -1511,7 +1553,7 @@ def refineVariant(s, sVariant, *, inPlace=False): ... m.append(n) ... v.append(m) >>> v.groups = ['paris'] - >>> v.replacementDuration = 8.0 + >>> v.replacementQuarterLength = 8.0 >>> s = stream.Stream() >>> streamDataM1 = [('a', 'quarter'), ('b', 'quarter'), ('a', 'quarter'), ('g', 'quarter')] @@ -1572,7 +1614,7 @@ def refineVariant(s, sVariant, *, inPlace=False): # useful parameters from variant and its location variantGroups = sVariant.groups - replacementDuration = sVariant.replacementDuration + replacementDuration = sVariant.replacementQuarterLength startOffset = sVariant.getOffsetBySite(s) # endOffset = replacementDuration + startOffset @@ -1631,7 +1673,7 @@ def refineVariant(s, sVariant, *, inPlace=False): startOffset, variantSubRegion, variantGroups=variantGroups, - replacementDuration=replacementDuration + replacementQuarterLength=replacementDuration ) # The original variant object has been replaced by more refined @@ -1712,7 +1754,7 @@ def _mergeVariantMeasureStreamsCarefully(streamX, streamY, variantName, *, inPla startOffset, variantSubRegion, variantGroups=[variantName], - replacementDuration=replacementDuration + replacementQuarterLength=replacementDuration ) if not inPlace: @@ -2181,7 +2223,7 @@ def makeAllVariantsReplacements(streamWithVariants, >>> returnStream = variant.makeAllVariantsReplacements(newStream, recurse=False) >>> for v in returnStream.parts[0][variant.Variant]: - ... (v.offset, v.lengthType, v.replacementDuration) + ... (v.offset, v.lengthType, v.replacementQuarterLength) (4.0, 'replacement', 4.0) (16.0, 'elongation', 0.0) (20.0, 'deletion', 4.0) @@ -2189,14 +2231,14 @@ def makeAllVariantsReplacements(streamWithVariants, >>> returnStream = variant.makeAllVariantsReplacements( ... newStream, variantNames=['france'], recurse=True) >>> for v in returnStream.parts[0][variant.Variant]: - ... (v.offset, v.lengthType, v.replacementDuration) + ... (v.offset, v.lengthType, v.replacementQuarterLength) (4.0, 'replacement', 4.0) (16.0, 'elongation', 0.0) (20.0, 'deletion', 4.0) >>> variant.makeAllVariantsReplacements(newStream, recurse=True, inPlace=True) >>> for v in newStream.parts[0][variant.Variant]: - ... (v.offset, v.lengthType, v.replacementDuration, v.containedHighestTime) + ... (v.offset, v.lengthType, v.replacementQuarterLength, v.containedHighestTime) (4.0, 'replacement', 4.0, 4.0) (12.0, 'elongation', 4.0, 12.0) (20.0, 'deletion', 8.0, 4.0) @@ -2255,7 +2297,7 @@ def _doVariantFixingOnStream(s, variantNames=None): ... >>> for v in s[variant.Variant]: - ... (v.offset, v.lengthType, v.replacementDuration) + ... (v.offset, v.lengthType, v.replacementQuarterLength) (0.0, 'elongation', 4.0) (4.0, 'replacement', 4.0) (12.0, 'elongation', 4.0) @@ -2269,18 +2311,18 @@ def _doVariantFixingOnStream(s, variantNames=None): >>> v1Stream = converter.parse('tinyNotation: 4/4 a4 a a a ', makeNotation=False) >>> # initial insertion deletion >>> v1 = variant.Variant(v1Stream.notes) - >>> v1.replacementDuration = 0.0 + >>> v1.replacementQuarterLength = 0.0 >>> v1.groups = ['london'] >>> s.insert(0.0, v1) >>> v2 = variant.Variant() - >>> v2.replacementDuration = 4.0 + >>> v2.replacementQuarterLength = 4.0 >>> v2.groups = ['london'] >>> s.insert(4.0, v2) >>> variant._doVariantFixingOnStream(s, 'london') >>> for v in s[variant.Variant]: - ... (v.offset, v.lengthType, v.replacementDuration, v.containedHighestTime) + ... (v.offset, v.lengthType, v.replacementQuarterLength, v.containedHighestTime) (0.0, 'elongation', 1.0, 5.0) (4.0, 'deletion', 5.0, 1.0) ''' @@ -2293,7 +2335,7 @@ def _doVariantFixingOnStream(s, variantNames=None): else: continue # huh???? lengthType = v.lengthType - replacementDuration = v.replacementDuration + replacementDuration = v.replacementQuarterLength highestTime = v.containedHighestTime if lengthType == 'elongation' and replacementDuration == 0.0: @@ -2306,7 +2348,7 @@ def _doVariantFixingOnStream(s, variantNames=None): if v.getOffsetBySite(s) == 0.0: isInitial = True isFinal = False - elif v.getOffsetBySite(s) + v.replacementDuration == s.duration.quarterLength: + elif v.getOffsetBySite(s) + v.replacementQuarterLength == s.duration.quarterLength: isInitial = False isFinal = True else: @@ -2345,8 +2387,9 @@ def _doVariantFixingOnStream(s, variantNames=None): s.insert(newVariantOffset, v) # Give it a new replacementDuration including the added element - oldReplacementDuration = v.replacementDuration - v.replacementDuration = oldReplacementDuration + targetElement.duration.quarterLength + oldReplacementQuarterLength = v.replacementQuarterLength + v.replacementQuarterLength = common.opFrac( + oldReplacementQuarterLength + targetElement.duration.quarterLength) def _getNextElements(s, v, numberOfElements=1): @@ -2375,12 +2418,12 @@ def _getNextElements(s, v, numberOfElements=1): >>> v1Stream = converter.parse('tinyNotation: 4/4 a4 a a a ', makeNotation=False) >>> # initial insertion >>> v1 = variant.Variant(v1Stream.notes) - >>> v1.replacementDuration = 0.0 + >>> v1.replacementQuarterLength = 0.0 >>> v1.groups = ['london'] >>> s.insert(0.0, v1) >>> v2 = variant.Variant() - >>> v2.replacementDuration = 4.0 + >>> v2.replacementQuarterLength = 4.0 >>> v2.groups = ['london'] >>> s.insert(4.0, v2) >>> for v in s[variant.Variant]: @@ -2413,7 +2456,7 @@ def _getNextElements(s, v, numberOfElements=1): returnElement = potentialTargets.first() else: - replacementDuration = v.replacementDuration + replacementDuration = v.replacementQuarterLength variantOffset = v.getOffsetBySite(s) potentialTargets = s.getElementsByOffset(variantOffset + replacementDuration, offsetEnd=s.highestTime, @@ -2454,12 +2497,12 @@ def _getPreviousElement(s, v): >>> v1Stream = converter.parse('tinyNotation: 4/4 f4 f f f ', makeNotation=False) >>> # insertion final deletion >>> v1 = variant.Variant(v1Stream.notes) - >>> v1.replacementDuration = 0.0 + >>> v1.replacementQuarterLength = 0.0 >>> v1.groups = ['london'] >>> s.insert(4.0, v1) >>> v2 = variant.Variant() - >>> v2.replacementDuration = 4.0 + >>> v2.replacementQuarterLength = 4.0 >>> v2.groups = ['london'] >>> s.insert(8.0, v2) >>> for v in s[variant.Variant]: @@ -2507,7 +2550,7 @@ def makeVariantBlocks(s): for v in variantsToBeDone: startOffset = s.elementOffset(v) - endOffset = v.replacementDuration + startOffset + endOffset = v.replacementQuarterLength + startOffset conflictingVariants = s.getElementsByOffset(offsetStart=startOffset, offsetEnd=endOffset, includeEndBoundary=False, @@ -2515,7 +2558,7 @@ def makeVariantBlocks(s): mustBeginInSpan=True, classList=[variant.Variant]) for cV in conflictingVariants: - oldReplacementDuration = cV.replacementDuration + oldReplacementDuration = cV.replacementQuarterLength if s.elementOffset(cV) == startOffset: continue # do nothing else: @@ -2528,7 +2571,7 @@ def makeVariantBlocks(s): cV._stream.coreSetElementOffset(el, oldOffset + shiftOffset) cV.coreElementsChanged() cV.insert(0.0, r) - cV.replacementDuration = oldReplacementDuration + cV.replacementQuarterLength = oldReplacementDuration s.remove(cV) s.insert(startOffset, cV) variantsToBeDone.append(cV)