Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion music21/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
'''
from __future__ import annotations

__version__ = '10.2.0b1'
__version__ = '10.2.0b2'

def get_version_tuple(vv):
v = vv.split('.')
Expand Down
11 changes: 6 additions & 5 deletions music21/abcFormat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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())
Expand All @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions music21/alpha/analysis/ornamentRecognizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__':
Expand Down
14 changes: 7 additions & 7 deletions music21/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<class 'music21.base.Music21Object'>

>>> music21.VERSION_STR
'10.2.0b1'
'10.2.0b2'

Alternatively, after doing a complete import, these classes are available
under the module "base":
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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])
Expand All @@ -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

Expand Down
36 changes: 31 additions & 5 deletions music21/duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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] = {}

Expand Down Expand Up @@ -1889,16 +1911,15 @@ 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
multiplying the current quarterLength of the
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
Expand Down Expand Up @@ -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')
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions music21/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion music21/freezeThaw.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
8 changes: 4 additions & 4 deletions music21/humdrum/spineParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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('.')

Expand Down
16 changes: 8 additions & 8 deletions music21/lily/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -2164,7 +2164,7 @@ def lyPrefixCompositeMusicFromVariant(self,

lpOssiaMusicVariant = self.lyOssiaMusicFromVariant(variantObject)

replacedElementsLength = variantObject.replacementDuration
replacedElementsLength = variantObject.replacementQuarterLength
variantLength = variantObject.containedHighestTime - spacerDur

self.variantMode = True
Expand Down
12 changes: 6 additions & 6 deletions music21/meter/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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):
'''
Expand Down
Loading
Loading