Skip to content

Commit

Permalink
RepeatBracket change defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
mscuthbert committed Oct 5, 2022
1 parent a07c4af commit 3d4b69c
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 104 deletions.
89 changes: 84 additions & 5 deletions music21/common/classTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,36 @@
# ------------------------------------------------------------------------------
from __future__ import annotations

from collections.abc import Iterable, Collection
import contextlib
import numbers
import typing as t

# from music21 import exceptions21
__all__ = [
'isNum', 'isListLike', 'isIterable', 'classToClassStr', 'getClassSet',
'holdsType',
'isNum', 'isInt', 'isListLike', 'isIterable', 'classToClassStr', 'getClassSet',
'tempAttribute', 'saveAttributes',
]


def isNum(usrData: t.Any) -> bool:
_T = t.TypeVar('_T')


def isInt(usrData: t.Any) -> t.TypeGuard[int]:
'''
Check if usrData is an integer and not True or False.
>>> common.isInt(3)
True
>>> common.isInt(False)
False
>>> common.isInt(2.0)
False
'''
return isinstance(usrData, int) and usrData is not True and usrData is not False


def isNum(usrData: t.Any) -> t.TypeGuard[Number]:
'''
check if usrData is a number (float, int, long, Decimal),
return boolean
Expand Down Expand Up @@ -91,10 +110,10 @@ def isListLike(usrData: t.Any) -> bool:
return isinstance(usrData, (list, tuple))


def isIterable(usrData: t.Any) -> bool:
def isIterable(usrData: t.Any) -> t.TypeGuard[Iterable]:
'''
Returns True if is the object can be iter'd over
and is NOT a string
and is NOT a string. Marks it as an Iterable for type checking.
>>> common.isIterable([5, 10])
True
Expand Down Expand Up @@ -126,6 +145,64 @@ def isIterable(usrData: t.Any) -> bool:
return False


def holdsType(usrData: t.Any, checkType: type[_T]) -> TypeGuard[Collection[_T]]:
'''
Returns True if usrData is a Collection of type checkType.
This reads an item from usrData, so don't use it on something
where iterating destroys the type.
>>> y = [1, 2, 3]
>>> common.classTools.holdsType(y, int)
True
>>> common.classTools.holdsType(5, int)
False
>>> common.classTools.holdsType(['hello'], str)
True
Empty iterators hold the type:
>>> common.classTools.holdsType([], float)
True
Note that a mixed collection holds whatever is first
>>> common.classTools.holdsType((4, 'hello'), int)
True
>>> common.classTools.holdsType((4, 'hello'), str)
False
Works on sets with arbitrary order:
>>> common.classTools.holdsType({2, 10}, int)
True
Intelligent collections will not have their position affected.
>>> m = stream.Measure([note.Note('C'), note.Rest()])
>>> common.classTools.holdsType(m, note.GeneralNote)
True
>>> next(iter(m))
<music21.note.Note C>
>>> r = range(1, 100)
>>> common.classTools.holdsType(r, int)
True
>>> next(iter(r))
1
New in v9.
'''
if not isIterable(usrData):
return False
try:
first = next(iter(usrData))
return isinstance(first, checkType)
except StopIteration:
return True



def classToClassStr(classObj: type) -> str:
'''Convert a class object to a class string.
Expand Down Expand Up @@ -157,6 +234,8 @@ def getClassSet(instance, classNameTuple=None):
True
>>> 'object' in cs
True
>>> note.Note in cs
False
To save time (this IS a performance-critical operation), classNameTuple
can be passed a tuple of names such as ('Pitch', 'object') that
Expand Down
2 changes: 1 addition & 1 deletion music21/musicxml/m21ToXml.py
Original file line number Diff line number Diff line change
Expand Up @@ -6431,7 +6431,7 @@ def setBarline(self, barline, position):

if endingType:
mxEnding = Element('ending')
numberList = self.rbSpanners[0].getNumberList()
numberList = self.rbSpanners[0].numberRange
numberStr = str(numberList[0])
# 0 is not a valid "ending-number"
if numberStr == '0':
Expand Down
6 changes: 3 additions & 3 deletions music21/musicxml/test_xmlToM21.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,13 +1256,13 @@ def testMultiDigitEnding(self):
# Measure 3, right barline: <ending number="3" type="stop"/>
score = converter.parse(testPrimitive.multiDigitEnding)
repeatBrackets = score.recurse().getElementsByClass(spanner.RepeatBracket)
self.assertListEqual(repeatBrackets[0].getNumberList(), [1, 2])
self.assertListEqual(repeatBrackets[1].getNumberList(), [3])
self.assertListEqual(repeatBrackets[0].numberRange, [1, 2])
self.assertListEqual(repeatBrackets[1].numberRange, [3])

nonconformingInput = testPrimitive.multiDigitEnding.replace('1,2', 'ad lib.')
score2 = converter.parse(nonconformingInput)
repeatBracket = score2.recurse().getElementsByClass(spanner.RepeatBracket).first()
self.assertListEqual(repeatBracket.getNumberList(), [1])
self.assertListEqual(repeatBracket.numberRange, [1])

def testChordAlteration(self):
from music21 import musicxml
Expand Down
12 changes: 6 additions & 6 deletions music21/repeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def __init__(self, text=None):
]

# ------------------------------
def insertRepeatEnding(s, start, end, endingNumber=1, *, inPlace=False):
def insertRepeatEnding(s, start, end, endingNumber: int = 1, *, inPlace=False):
'''
Designates a range of measures as being repeated endings (i.e. first and second endings)
within a stream s, where s either contains measures,
Expand Down Expand Up @@ -738,7 +738,7 @@ def _setup(self):

# see if there are any repeat brackets
self._repeatBrackets = self._src.flatten().getElementsByClass(
'RepeatBracket'
spanner.RepeatBracket
).stream()

self._srcMeasureCount = len(self._srcMeasureStream)
Expand Down Expand Up @@ -1114,13 +1114,13 @@ def _groupRepeatBracketIndices(self, streamObj):
# environLocal.printDebug(['_groupRepeatBracketIndices', rb])
# match = False
if rb.isFirst(m): # for this rb, is this the first measures
if rb.getNumberList()[0] in foundRBNumbers:
if rb.numberRange[0] in foundRBNumbers:
# we have a new group
groups.append(groupIndices)
foundRBNumbers = []
groupIndices = {'repeatBrackets': [], 'measureIndices': []}
# store rb numbers to monitor when we are in a new group
foundRBNumbers += rb.getNumberList() # concat list
foundRBNumbers += rb.numberRange # concat list
# groupIndices['measureIndices'].append(i)
# need to jump to the index of the last measure this
# rb contains; need to add indices for measures found within
Expand Down Expand Up @@ -1175,7 +1175,7 @@ def _repeatBracketsAreCoherent(self):
# get number list will return inclusive values; i.e.,
# 1,3 will
# return 1, 2, 3
target += rb.getNumberList()
target += rb.numberRange
match = list(range(1, max(target) + 1)) # max of target + 1
if match != target:
environLocal.printDebug([
Expand Down Expand Up @@ -1678,7 +1678,7 @@ def _processInnermostRepeatsAndBrackets(self,
# are at the last repeat under this bracket
if data['validIndices'] is not None:
# repeat times is the number of elements in the list
repeatTimes = len(data['repeatBracket'].getNumberList())
repeatTimes = len(data['repeatBracket'].numberRange)
# just get the expanded section
# streamObj.show('t')
out = self.processInnermostRepeatBars(
Expand Down
Loading

0 comments on commit 3d4b69c

Please sign in to comment.