From e63e8ba85c0efbc66da27c845ead52fc0f559b33 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 11:29:32 -1000 Subject: [PATCH 01/35] Explicit Keywords on all music21 objects Fixes #839 Each push will include one or more modules. Starting with Base. --- .idea/inspectionProfiles/Project_Default.xml | 1 + music21/base.py | 86 ++++++++++---------- music21/stream/tests.py | 14 ++-- 3 files changed, 52 insertions(+), 49 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 97fe104cfd..5149f08b6d 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -61,6 +61,7 @@ diff --git a/music21/base.py b/music21/base.py index 609d98fea4..2ed78360d7 100644 --- a/music21/base.py +++ b/music21/base.py @@ -38,6 +38,7 @@ ''' from __future__ import annotations +import builtins import copy import warnings import weakref @@ -55,14 +56,13 @@ from music21.common.numberTools import opFrac from music21.common.types import OffsetQL, OffsetQLIn from music21 import environment -from music21 import editorial +from music21.editorial import Editorial from music21 import defaults from music21.derivation import Derivation from music21.duration import Duration, DurationException from music21 import prebase -from music21 import sites -from music21 import style # pylint: disable=unused-import -from music21.sites import SitesException +from music21.sites import Sites, SitesException, WEAKREF_ACTIVE +from music21.style import Style # pylint: disable=unused-import from music21.sorting import SortTuple, ZeroSortTupleLow, ZeroSortTupleHigh # needed for temporal manipulations; not music21 objects from music21 import tie @@ -265,9 +265,9 @@ class Music21Object(prebase.ProtoM21Object): 1. id: identification string unique to the object's container (optional). Defaults to the `id()` of the element. - 2. groups: a Groups object: which is a list of strings identifying - internal sub-collections (voices, parts, selections) to which this - element belongs + 2. groups: a :class:`~music21.base.Groups` object: which is a + list of strings identifying internal sub-collections + (voices, parts, selections) to which this element belongs 3. duration: Duration object representing the length of the object 4. activeSite: a reference to the currently active Stream or None 5. offset: a floating point value, generally in quarter lengths, @@ -285,7 +285,6 @@ class Music21Object(prebase.ProtoM21Object): 10. editorial: a :class:`~music21.editorial.Editorial` object - Each of these may be passed in as a named keyword to any music21 object. Some of these may be intercepted by the subclassing object (e.g., duration @@ -296,16 +295,18 @@ class Music21Object(prebase.ProtoM21Object): # these values permit fast class comparisons for performance critical cases isStream = False - _styleClass: t.Type[style.Style] = style.Style + _styleClass: t.Type[Style] = Style # define order for presenting names in documentation; use strings _DOC_ORDER: t.List[str] = [] # documentation for all attributes (not properties or methods) _DOC_ATTR: t.Dict[str, str] = { - 'groups': '''An instance of a :class:`~music21.base.Group` + 'groups': '''An instance of a :class:`~music21.base.Groups` object which describes arbitrary `Groups` that this object belongs to.''', + 'sites': '''a :class:`~music21.sites.Sites` object that stores + references to Streams that hold this object.''', 'isStream': '''Boolean value for quickly identifying :class:`~music21.stream.Stream` objects (False by default).''', 'classSortOrder': '''Property which returns an number (int or otherwise) @@ -342,13 +343,24 @@ class Music21Object(prebase.ProtoM21Object): ''', } - def __init__(self, *arguments, **keywords): + def __init__(self, + *arguments, + id: t.Union[str, int, None] = None, + groups: t.Optional[Groups] = None, + sites: t.Optional[Sites] = None, + duration: t.Optional[Duration] = None, + activeSite: t.Optional['music21.stream.Stream'] = None, + style: t.Optional[Style] = None, + editorial: t.Optional[Editorial] = None, + offset: OffsetQL = 0.0, + quarterLength: t.Optional[OffsetQLIn] = None, + **keywords): # do not call super().__init__() since it just wastes time - self._id = None + self._id: t.Union[str, int, None] = id # None is stored as the internal location of an obj w/o any sites self._activeSite: t.Union['music21.stream.Stream', weakref.ReferenceType, None] = None # offset when no activeSite is available - self._naiveOffset: t.Union[float, fractions.Fraction] = 0.0 + self._naiveOffset: OffsetQL = offset # offset when activeSite is already garbage collected/dead, # as in short-lived sites @@ -359,8 +371,8 @@ def __init__(self, *arguments, **keywords): # pass a reference to this object self._derivation: t.Optional[Derivation] = None - self._style: t.Optional[style.Style] = None - self._editorial: t.Optional[editorial.Editorial] = None + self._style: t.Optional[Style] = style + self._editorial: t.Optional[Editorial] = None # private duration storage; managed by property self._duration: t.Optional[Duration] = None @@ -369,29 +381,17 @@ def __init__(self, *arguments, **keywords): # store cached values here: self._cache: t.Dict[str, t.Any] = {} - if 'id' in keywords: - self._id = keywords['id'] - - if 'groups' in keywords and keywords['groups'] is not None: - self.groups = keywords['groups'] - else: - self.groups = Groups() - - if 'sites' in keywords: - self.sites = keywords['sites'] - else: - self.sites = sites.Sites() + self.groups = groups or Groups() + self.sites = sites or Sites() # a duration object is not created until the .duration property is # accessed with _getDuration(); this is a performance optimization - if 'duration' in keywords: - self.duration = keywords['duration'] - if 'activeSite' in keywords: - self.activeSite = keywords['activeSite'] - if 'style' in keywords: - self.style = keywords['style'] - if 'editorial' in keywords: - self.editorial = keywords['editorial'] + if activeSite is not None: + self.activeSite = activeSite + if quarterLength is not None: + self.duration.quarterLength = quarterLength + elif duration is not None: + self.duration = duration @property def id(self) -> t.Union[int, str]: @@ -407,7 +407,7 @@ def id(self) -> t.Union[int, str]: ''' if self._id is not None: return self._id - return id(self) + return builtins.id(self) @id.setter def id(self, new_id: t.Union[int, str]): @@ -673,7 +673,7 @@ def editorial(self) -> 'music21.editorial.Editorial': # anytime something is changed here, change in style.StyleMixin and vice-versa if self._editorial is None: - self._editorial = editorial.Editorial() + self._editorial = Editorial() return self._editorial @editorial.setter @@ -705,7 +705,7 @@ def hasStyleInformation(self) -> bool: return not (self._style is None) @property - def style(self) -> 'music21.style.Style': + def style(self) -> Style: ''' Returns (or Creates and then Returns) the Style object associated with this object, or sets a new @@ -727,8 +727,6 @@ def style(self) -> 'music21.style.Style': >>> n.style.absoluteX is None True ''' - # Dev note: because property style shadows module style, - # typing has to be in quotes. # anytime something is changed here, change in style.StyleMixin and vice-versa if not self.hasStyleInformation: StyleClass = self._styleClass @@ -737,7 +735,7 @@ def style(self) -> 'music21.style.Style': return self._style @style.setter - def style(self, newStyle: t.Optional['music21.style.Style']): + def style(self, newStyle: t.Optional[Style]): # Dev note: because property style shadows module style, # typing has to be in quotes. self._style = newStyle @@ -2268,7 +2266,7 @@ def previous(self, def _getActiveSite(self): # can be None - if sites.WEAKREF_ACTIVE: + if WEAKREF_ACTIVE: if self._activeSite is None: # leave None return None else: # even if current activeSite is not a weakref, this will work @@ -2300,7 +2298,7 @@ def _setActiveSite(self, site: t.Union['music21.stream.Stream', None]): else: self._activeSiteStoredOffset = None - if sites.WEAKREF_ACTIVE: + if WEAKREF_ACTIVE: if site is None: # leave None alone self._activeSite = None else: @@ -2638,6 +2636,8 @@ def duration(self) -> Duration: @duration.setter def duration(self, durationObj: Duration): durationObjAlreadyExists = not (self._duration is None) + if durationObjAlreadyExists: + self._duration.client = None try: ql = durationObj.quarterLength diff --git a/music21/stream/tests.py b/music21/stream/tests.py index 33cff58501..9d858712d6 100644 --- a/music21/stream/tests.py +++ b/music21/stream/tests.py @@ -573,20 +573,22 @@ def testStreamDuration(self): def testStreamDurationRecalculated(self): from fractions import Fraction - a = Stream() + s = Stream() n = note.Note(quarterLength=1.0) - a.append(n) - self.assertEqual(a.duration.quarterLength, 1.0) + s.append(n) + self.assertEqual(s.duration.quarterLength, 1.0) tup = duration.Tuplet() n.duration.tuplets = (tup,) - self.assertEqual(a.duration.quarterLength, Fraction(2, 3)) + self.assertEqual(n.duration.quarterLength, Fraction(2, 3)) + self.assertEqual(s.duration.quarterLength, Fraction(2, 3)) # Also (regression) test clearing the cache # https://github.com/cuthbertLab/music21/issues/957 n.duration.tuplets = (tup, tup) - a.coreElementsChanged() - self.assertEqual(a.duration.quarterLength, Fraction(4, 9)) + self.assertEqual(s.duration.quarterLength, Fraction(4, 9)) + s.coreElementsChanged() + self.assertEqual(s.duration.quarterLength, Fraction(4, 9)) def testMeasureStream(self): '''An approach to setting TimeSignature measures in offsets and durations From 4a72f9f64ce66769900d19fe1d2ddfb65c82603d Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 12:42:04 -1000 Subject: [PATCH 02/35] Tuplet --- music21/base.py | 5 ++- music21/duration.py | 107 +++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/music21/base.py b/music21/base.py index 2ed78360d7..9540b77f84 100644 --- a/music21/base.py +++ b/music21/base.py @@ -2635,9 +2635,10 @@ def duration(self) -> Duration: @duration.setter def duration(self, durationObj: Duration): - durationObjAlreadyExists = not (self._duration is None) - if durationObjAlreadyExists: + durationObjAlreadyExists = False + if self._duration is not None: self._duration.client = None + durationObjAlreadyExists = True try: ql = durationObj.quarterLength diff --git a/music21/duration.py b/music21/duration.py index 9934060e35..2d5898396b 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -74,6 +74,8 @@ POSSIBLE_DOTS_IN_TUPLETS = (0, 1) +TupletType = t.Literal['start', 'stop', 'startStop', False, None] +TupletShowOptions = t.Literal['number', 'type', 'both', None] class DurationException(exceptions21.Music21Exception): pass @@ -1044,68 +1046,57 @@ class Tuplet(prebase.ProtoM21Object): # TODO: use __setattr__ to freeze all properties, and make a metaclass # exceptions: tuplet type, tuplet id: things that don't affect length ''' - def __init__(self, *arguments, **keywords): + def __init__(self, + numberNotesActual: int = 3, + numberNotesNormal: int = 2, + durationActual: t.Union[Duration, DurationTuple, str, t.Tuple[str, int], None] = None, + durationNormal: t.Union[Duration, DurationTuple, str, t.Tuple[str, int], None] = None, + *, + tupletId: int = 0, + nestedLevel: int = 1, + type: TupletType = None, + bracket: t.Literal[True, False, 'slur'] = True, + placement: t.Literal['above', 'below'] = 'above', + tupletActualShow: TupletShowOptions = 'number', + tupletNormalShow: TupletShowOptions = None, + **keywords): self.frozen = False # environLocal.printDebug(['creating Tuplet instance']) - self._durationNormal = None - self._durationActual = None + self._durationNormal: t.Optional[Duration] = None + self._durationActual: t.Optional[Duration] = None # necessary for some complex tuplets, interrupted, for instance - if len(arguments) == 3: - keywords['numberNotesActual'] = arguments[0] - keywords['numberNotesNormal'] = arguments[1] - keywords['durationActual'] = arguments[2] - keywords['durationNormal'] = arguments[2] - elif len(arguments) == 2: - keywords['numberNotesActual'] = arguments[0] - keywords['numberNotesNormal'] = arguments[1] - - if 'tupletId' in keywords: - self.tupletId = keywords['tupletId'] - else: - self.tupletId = 0 - - if 'nestedLevel' in keywords: - self.nestedLevel = keywords['nestedLevel'] - else: - self.nestedLevel = 1 + self.tupletId = tupletId + self.nestedLevel = nestedLevel # actual is the count of notes that happen in this space # this is not the previous/expected duration of notes that happen - if 'numberNotesActual' in keywords: - self.numberNotesActual = keywords['numberNotesActual'] - else: - self.numberNotesActual = 3 + self.numberNotesActual: int = numberNotesActual + self.durationActual: Duration # this stores a durationTuple - if 'durationActual' in keywords and keywords['durationActual'] is not None: - if isinstance(keywords['durationActual'], str): - self.durationActual = durationTupleFromTypeDots(keywords['durationActual'], 0) - elif common.isIterable(keywords['durationActual']): - self.durationActual = durationTupleFromTypeDots(keywords['durationActual'][0], - keywords['durationActual'][1]) + if durationActual is not None: + if isinstance(durationActual, str): + self.durationActual = durationTupleFromTypeDots(durationActual, 0) + elif common.isIterable(durationActual): + self.durationActual = durationTupleFromTypeDots(durationActual[0], + durationActual[1]) else: - self.durationActual = keywords['durationActual'] - else: - self.durationActual = None + self.durationActual = durationActual # normal is the space that would normally be occupied by the tuplet span - if 'numberNotesNormal' in keywords: - self.numberNotesNormal = keywords['numberNotesNormal'] - else: - self.numberNotesNormal = 2 - - if 'durationNormal' in keywords and keywords['durationNormal'] is not None: - if isinstance(keywords['durationNormal'], str): - self.durationNormal = durationTupleFromTypeDots(keywords['durationNormal'], 0) - elif common.isIterable(keywords['durationNormal']): - self.durationNormal = durationTupleFromTypeDots(keywords['durationNormal'][0], - keywords['durationNormal'][1]) + self.numberNotesNormal: int = numberNotesNormal + self.durationNormal: Duration + + if durationNormal is not None: + if isinstance(durationNormal, str): + self.durationNormal = durationTupleFromTypeDots(durationNormal, 0) + elif common.isIterable(durationNormal): + self.durationNormal = durationTupleFromTypeDots(durationNormal[0], + durationNormal[1]) else: - self.durationNormal = keywords['durationNormal'] - else: - self.durationNormal = None + self.durationNormal = durationNormal # Type is 'start', 'stop', 'startStop', False or None: determines whether to start or stop # the bracket/group drawing @@ -1113,11 +1104,11 @@ def __init__(self, *arguments, **keywords): # as two notations (start + stop) in musicxml # type of None means undetermined, # False means definitely neither start nor stop (not yet used) - self.type = None - self.bracket = True # True or False or 'slur' - self.placement = 'above' # above or below - self.tupletActualShow = 'number' # could be 'number', 'type', 'both', or None - self.tupletNormalShow = None # for ratios. Options are same as above. + self.type: TupletType = type + self.bracket: t.Literal[True, False, 'slur'] = bracket # True or False or 'slur' + self.placement: t.Literal['above', 'below'] = placement # 'above' or 'below' (TODO: 'by-context') + self.tupletActualShow: TupletShowOptions = tupletActualShow # could be 'number', 'type', 'both', or None + self.tupletNormalShow: TupletShowOptions = tupletNormalShow # for ratios. Options are same as above. # this attribute is not yet used anywhere # self.nestedInside = '' # could be a tuplet object @@ -1368,7 +1359,7 @@ def tupletMultiplier(self) -> OffsetQL: # PUBLIC PROPERTIES # @property - def durationActual(self): + def durationActual(self) -> t.Optional[DurationTuple]: ''' durationActual is a DurationTuple that represents the notes that are actually present and counted in a tuplet. For instance, in a 7 @@ -1376,6 +1367,8 @@ def durationActual(self): the duration actual would be... >>> d = duration.Tuplet(7, 2) + >>> print(d.durationActual) + None >>> d.durationActual = duration.Duration('eighth', dots=1) Notice that the Duration object gets converted to a DurationTuple @@ -1390,7 +1383,7 @@ def durationActual(self): return self._durationActual @durationActual.setter - def durationActual(self, dA: t.Union[DurationTuple, 'Duration']): + def durationActual(self, dA: t.Union[DurationTuple, Duration]): self._checkFrozen() if isinstance(dA, DurationTuple): @@ -1404,7 +1397,7 @@ def durationActual(self, dA: t.Union[DurationTuple, 'Duration']): self._durationActual = durationTupleFromTypeDots(dA, dots=0) @property - def durationNormal(self) -> DurationTuple: + def durationNormal(self) -> t.Optional[DurationTuple]: ''' durationNormal is a DurationTuple that represents the notes that would be present in the space normally (if there were no tuplets). For instance, in a 7 @@ -1412,6 +1405,8 @@ def durationNormal(self) -> DurationTuple: the durationNormal would be... >>> d = duration.Tuplet(7, 2) + >>> print(d.durationNormal) + None >>> d.durationNormal = duration.Duration('quarter', dots=2) Notice that the Duration object gets converted to a DurationTuple From 6a1785d66c6c905ef9e6ddba4de1eec93928823b Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 13:05:59 -1000 Subject: [PATCH 03/35] few fixes. More needed --- music21/base.py | 2 +- music21/duration.py | 81 +++++++++++++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/music21/base.py b/music21/base.py index 9540b77f84..a35236534d 100644 --- a/music21/base.py +++ b/music21/base.py @@ -345,7 +345,7 @@ class Music21Object(prebase.ProtoM21Object): def __init__(self, *arguments, - id: t.Union[str, int, None] = None, + id: t.Union[str, int, None] = None, # pylint: disable=redefined-builtin groups: t.Optional[Groups] = None, sites: t.Optional[Sites] = None, duration: t.Optional[Duration] = None, diff --git a/music21/duration.py b/music21/duration.py index 2d5898396b..d75d9766e0 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -1074,29 +1074,31 @@ def __init__(self, # this is not the previous/expected duration of notes that happen self.numberNotesActual: int = numberNotesActual - self.durationActual: Duration # this stores a durationTuple if durationActual is not None: if isinstance(durationActual, str): self.durationActual = durationTupleFromTypeDots(durationActual, 0) - elif common.isIterable(durationActual): + elif isinstance(durationActual, tuple): self.durationActual = durationTupleFromTypeDots(durationActual[0], durationActual[1]) else: self.durationActual = durationActual + else: + self.durationActual = None # normal is the space that would normally be occupied by the tuplet span self.numberNotesNormal: int = numberNotesNormal - self.durationNormal: Duration if durationNormal is not None: if isinstance(durationNormal, str): self.durationNormal = durationTupleFromTypeDots(durationNormal, 0) - elif common.isIterable(durationNormal): + elif isinstance(durationNormal, tuple): self.durationNormal = durationTupleFromTypeDots(durationNormal[0], durationNormal[1]) else: self.durationNormal = durationNormal + else: + self.durationNormal = None # Type is 'start', 'stop', 'startStop', False or None: determines whether to start or stop # the bracket/group drawing @@ -1421,7 +1423,7 @@ def durationNormal(self) -> t.Optional[DurationTuple]: return self._durationNormal @durationNormal.setter - def durationNormal(self, dN): + def durationNormal(self, dN: t.Union[DurationTuple, Duration, str]): self._checkFrozen() if isinstance(dN, DurationTuple): self._durationNormal = dN @@ -1578,12 +1580,34 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin): _DOC_ATTR = {'expressionIsInferred': ''' Boolean indicating whether this duration was created from a - number rather than a type and thus can be reexpressed. + number rather than a type and thus can be changed to another + expression. For instance the duration of 0.5 is generally + an eighth note, but in the middle of a triplet group might be + better written as a dotted-eighth triplet. If expressionIsInferred + is True then `music21` can change it according to complex. If + False, then the type, dots, and tuplets are considered immutable. + + >>> d = duration.Duration(0.5) + >>> d.expressionIsInferred + True + + >>> d = duration.Duration('eighth') + >>> d.expressionIsInferred + False '''} # INITIALIZER # - def __init__(self, *arguments, **keywords): + def __init__(self, + typeOrDuration: t.Union[str, OffsetQLIn, DurationTuple, None] = None, + *, + type: t.Optional[str] = None, # pylint: disable=redefined-builtin + dots: t.Optional[int] = None, + quarterLength: t.Optional[OffsetQLIn] = None, + durationTuple: t.Optional[DurationTuple] = None, + components: t.Optional[t.Iterable[DurationTuple]] = None, + client: t.Optional['music21.base.Music21Object'] = None, + **keywords): # First positional argument is assumed to be type string or a quarterLength. # no need for super() on ProtoM21 or SlottedObjectMixin @@ -1607,38 +1631,39 @@ def __init__(self, *arguments, **keywords): self._linked = True self.expressionIsInferred = False - for a in arguments: - if common.isNum(a) and 'quarterLength' not in keywords: - keywords['quarterLength'] = a - elif isinstance(a, str) and 'type' not in keywords: - keywords['type'] = a - elif isinstance(a, DurationTuple): - self.addDurationTuple(a) + if typeOrDuration is not None: + if common.isNum(typeOrDuration) and quarterLength is None: + quarterLength = typeOrDuration + elif isinstance(typeOrDuration, str) and type is None: + type = typeOrDuration + elif isinstance(typeOrDuration, DurationTuple) and durationTuple is None: + durationTuple = typeOrDuration else: - raise TypeError(f'Cannot parse argument {a}') + raise TypeError(f'Cannot parse argument {typeOrDuration} or conflicts with keywords') - if 'durationTuple' in keywords: - self.addDurationTuple(keywords['durationTuple']) + if durationTuple is not None: + self.addDurationTuple(durationTuple) - if 'dots' in keywords and keywords['dots'] is not None: - storeDots = int(keywords['dots']) + if dots is not None: + storeDots = dots else: storeDots = 0 - if 'components' in keywords: - self.components = keywords['components'] + if components is not None: + self.components = components # this is set in _setComponents # self._quarterLengthNeedsUpdating = True - if 'type' in keywords: - nt = durationTupleFromTypeDots(keywords['type'], storeDots) + + if type is not None: + nt = durationTupleFromTypeDots(type, storeDots) self.addDurationTuple(nt) # permit as keyword so can be passed from notes - elif 'quarterLength' in keywords: - self.quarterLength = keywords['quarterLength'] + elif quarterLength is not None: + self.quarterLength = quarterLength self.expressionIsInferred = True - if 'client' in keywords: - self.client = keywords['client'] + if client is not None: + self.client = client # SPECIAL METHODS # def __eq__(self, other): @@ -1808,7 +1833,7 @@ def _setLinked(self, value: bool): linked = property(_getLinked, _setLinked) - def addDurationTuple(self, dur: t.Union[DurationTuple, 'Duration']): + def addDurationTuple(self, dur: t.Union[DurationTuple, Duration, str, OffsetQLIn]): ''' Add a DurationTuple or a Duration's components to this Duration. Does not simplify the Duration. For instance, adding two From f902683dbe00b1422f20469803adb8fb09da92be Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 13:23:07 -1000 Subject: [PATCH 04/35] update some types --- music21/duration.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/music21/duration.py b/music21/duration.py index d75d9766e0..8973e58b37 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -1083,8 +1083,6 @@ def __init__(self, durationActual[1]) else: self.durationActual = durationActual - else: - self.durationActual = None # normal is the space that would normally be occupied by the tuplet span self.numberNotesNormal: int = numberNotesNormal @@ -1097,8 +1095,6 @@ def __init__(self, durationNormal[1]) else: self.durationNormal = durationNormal - else: - self.durationNormal = None # Type is 'start', 'stop', 'startStop', False or None: determines whether to start or stop # the bracket/group drawing @@ -1385,10 +1381,10 @@ def durationActual(self) -> t.Optional[DurationTuple]: return self._durationActual @durationActual.setter - def durationActual(self, dA: t.Union[DurationTuple, Duration]): + def durationActual(self, dA: t.Union[DurationTuple, Duration, str, None]): self._checkFrozen() - if isinstance(dA, DurationTuple): + if isinstance(dA, DurationTuple) or dA is None: self._durationActual = dA elif isinstance(dA, Duration): if len(dA.components) > 1: @@ -1423,9 +1419,9 @@ def durationNormal(self) -> t.Optional[DurationTuple]: return self._durationNormal @durationNormal.setter - def durationNormal(self, dN: t.Union[DurationTuple, Duration, str]): + def durationNormal(self, dN: t.Union[DurationTuple, Duration, str, None]): self._checkFrozen() - if isinstance(dN, DurationTuple): + if isinstance(dN, DurationTuple) or dN is None: self._durationNormal = dN elif isinstance(dN, Duration): if len(dN.components) > 1: From 41b9bc27a67d81efdf5587d988bef810e39ccf66 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 14:04:59 -1000 Subject: [PATCH 05/35] lint,mypy,test --- music21/common/classTools.py | 6 ++--- music21/duration.py | 46 ++++++++++++++++++++++++------------ music21/test/testRunner.py | 5 ++-- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/music21/common/classTools.py b/music21/common/classTools.py index 0a3fd1f845..452f6777ec 100644 --- a/music21/common/classTools.py +++ b/music21/common/classTools.py @@ -26,9 +26,10 @@ def isNum(usrData: t.Any) -> bool: unlike `isinstance(usrData, Number)` does not return True for `True, False`. - Does not use `isinstance(usrData, Number)` which is 6 times slower + Does not use `isinstance(usrData, Number)` which is 2-6 times slower than calling this function (except in the case of Fraction, when - it's 6 times faster, but that's rarer) + it's 6 times faster, but that's rarer). (6 times slower on Py3.4, now + only 2x slower in Python 3.10) Runs by adding 0 to the "number" -- so anything that implements add to a scalar works @@ -242,7 +243,6 @@ def saveAttributes(obj, *attributeList): # ------------------------------------------------------------------------------ # define presented order in documentation -# _DOC_ORDER = [fromRoman, toRoman] if __name__ == '__main__': import music21 diff --git a/music21/duration.py b/music21/duration.py index 8973e58b37..2a2ced0316 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -1049,12 +1049,14 @@ class Tuplet(prebase.ProtoM21Object): def __init__(self, numberNotesActual: int = 3, numberNotesNormal: int = 2, - durationActual: t.Union[Duration, DurationTuple, str, t.Tuple[str, int], None] = None, - durationNormal: t.Union[Duration, DurationTuple, str, t.Tuple[str, int], None] = None, + durationActual: t.Union[DurationTuple, Duration, + str, t.Tuple[str, int], None] = None, + durationNormal: t.Union[DurationTuple, Duration, + str, t.Tuple[str, int], None] = None, *, tupletId: int = 0, nestedLevel: int = 1, - type: TupletType = None, + type: TupletType = None, # pylint: disable=redefined-builtin bracket: t.Literal[True, False, 'slur'] = True, placement: t.Literal['above', 'below'] = 'above', tupletActualShow: TupletShowOptions = 'number', @@ -1063,8 +1065,8 @@ def __init__(self, self.frozen = False # environLocal.printDebug(['creating Tuplet instance']) - self._durationNormal: t.Optional[Duration] = None - self._durationActual: t.Optional[Duration] = None + self._durationNormal: t.Optional[DurationTuple] = None + self._durationActual: t.Optional[DurationTuple] = None # necessary for some complex tuplets, interrupted, for instance self.tupletId = tupletId @@ -1074,6 +1076,11 @@ def __init__(self, # this is not the previous/expected duration of notes that happen self.numberNotesActual: int = numberNotesActual + if durationActual is not None and durationNormal is None: + durationNormal = durationActual + elif durationActual is None and durationNormal is not None: + durationActual = durationNormal + # this stores a durationTuple if durationActual is not None: if isinstance(durationActual, str): @@ -1082,7 +1089,8 @@ def __init__(self, self.durationActual = durationTupleFromTypeDots(durationActual[0], durationActual[1]) else: - self.durationActual = durationActual + # type ignore until https://github.com/python/mypy/issues/3004 resolved + self.durationActual = durationActual # type: ignore # normal is the space that would normally be occupied by the tuplet span self.numberNotesNormal: int = numberNotesNormal @@ -1094,7 +1102,8 @@ def __init__(self, self.durationNormal = durationTupleFromTypeDots(durationNormal[0], durationNormal[1]) else: - self.durationNormal = durationNormal + # type ignore until https://github.com/python/mypy/issues/3004 resolved + self.durationNormal = durationNormal # type: ignore # Type is 'start', 'stop', 'startStop', False or None: determines whether to start or stop # the bracket/group drawing @@ -1103,10 +1112,14 @@ def __init__(self, # type of None means undetermined, # False means definitely neither start nor stop (not yet used) self.type: TupletType = type - self.bracket: t.Literal[True, False, 'slur'] = bracket # True or False or 'slur' - self.placement: t.Literal['above', 'below'] = placement # 'above' or 'below' (TODO: 'by-context') - self.tupletActualShow: TupletShowOptions = tupletActualShow # could be 'number', 'type', 'both', or None - self.tupletNormalShow: TupletShowOptions = tupletNormalShow # for ratios. Options are same as above. + # True or False or 'slur' + self.bracket: t.Literal[True, False, 'slur'] = bracket + # 'above' or 'below' (TODO: 'by-context') + self.placement: t.Literal['above', 'below'] = placement + # could be 'number', 'type', 'both', or None + self.tupletActualShow: TupletShowOptions = tupletActualShow + # for ratios. Options are same as above. + self.tupletNormalShow: TupletShowOptions = tupletNormalShow # this attribute is not yet used anywhere # self.nestedInside = '' # could be a tuplet object @@ -1557,7 +1570,7 @@ class Duration(prebase.ProtoM21Object, SlottedObjectMixin): # CLASS VARIABLES # - isGrace = False + isGrace = False # grace is stored as separate class. __slots__ = ( '_linked', @@ -1628,14 +1641,17 @@ def __init__(self, self.expressionIsInferred = False if typeOrDuration is not None: - if common.isNum(typeOrDuration) and quarterLength is None: + if isinstance(typeOrDuration, (int, float, fractions.Fraction) + ) and quarterLength is None: quarterLength = typeOrDuration elif isinstance(typeOrDuration, str) and type is None: type = typeOrDuration elif isinstance(typeOrDuration, DurationTuple) and durationTuple is None: durationTuple = typeOrDuration else: - raise TypeError(f'Cannot parse argument {typeOrDuration} or conflicts with keywords') + raise TypeError( + f'Cannot parse argument {typeOrDuration} or conflicts with keywords' + ) if durationTuple is not None: self.addDurationTuple(durationTuple) @@ -1646,7 +1662,7 @@ def __init__(self, storeDots = 0 if components is not None: - self.components = components + self.components = t.cast(t.Tuple[DurationTuple, ...], components) # this is set in _setComponents # self._quarterLengthNeedsUpdating = True diff --git a/music21/test/testRunner.py b/music21/test/testRunner.py index a3ef67d776..f0e1a43a02 100644 --- a/music21/test/testRunner.py +++ b/music21/test/testRunner.py @@ -45,14 +45,15 @@ def addDocAttrTestsToSuite(suite, >>> import doctest >>> s1 = doctest.DocTestSuite(chord) >>> s1TestsBefore = len(s1._tests) + >>> before = set(s1._tests) >>> allLocals = [getattr(chord, x) for x in dir(chord)] >>> test.testRunner.addDocAttrTestsToSuite(s1, allLocals) >>> s1TestsAfter = len(s1._tests) >>> s1TestsAfter - s1TestsBefore - 2 + 3 >>> lastTest = s1._tests[-1] >>> lastTest - isRest () + expressionIsInferred () ''' dtp = doctest.DocTestParser() if globs is False: From 44aad43419d1b2f26baacb2a18ade66d41d827be Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 14:49:30 -1000 Subject: [PATCH 06/35] note -- finished remove unused note.linkage --- music21/note.py | 162 +++++++++++++++++++++++++++++------------------ music21/pitch.py | 62 +++++++++--------- music21/style.py | 3 +- 3 files changed, 136 insertions(+), 91 deletions(-) diff --git a/music21/note.py b/music21/note.py index 472a78a091..6d89d87e08 100644 --- a/music21/note.py +++ b/music21/note.py @@ -27,11 +27,11 @@ from music21 import base from music21 import beam from music21 import common -from music21 import duration +from music21.duration import Duration from music21 import exceptions21 from music21 import expressions from music21 import interval -from music21 import pitch +from music21.pitch import Pitch from music21 import prebase from music21 import style from music21 import tie @@ -101,7 +101,9 @@ class NotRestException(exceptions21.Music21Exception): # ------------------------------------------------------------------------------ -SYLLABIC_CHOICES: t.List[t.Optional[str]] = [ +SyllabicChoices = t.Literal[None, 'begin', 'single', 'end', 'middle', 'composite'] + +SYLLABIC_CHOICES: t.List[SyllabicChoices] = [ None, 'begin', 'single', 'end', 'middle', 'composite', ] @@ -191,27 +193,33 @@ class Lyric(prebase.ProtoM21Object, style.StyleMixin): # INITIALIZER # - def __init__(self, text='', number=1, **kwargs): + def __init__(self, + text: str = '', + number: int = 1, + *, + applyRaw: bool = False, + syllabic: SyllabicChoices = None, + identifier: t.Optional[str] = None, + **keywords): super().__init__() self._identifier: t.Optional[str] = None self._number: int = 1 self._text: str = '' - self._syllabic = None + self._syllabic: SyllabicChoices = None self.components: t.Optional[t.List['music21.note.Lyric']] = None self.elisionBefore = ' ' - applyRaw = kwargs.get('applyRaw', False) - # these are set by setTextAndSyllabic if text: self.setTextAndSyllabic(text, applyRaw) # given as begin, middle, end, or single - if 'syllabic' in kwargs: - self.syllabic = kwargs['syllabic'] - + if syllabic is not None: + self.syllabic = syllabic self.number = number - self.identifier = kwargs.get('identifier', None) + + # type ignore until https://github.com/python/mypy/issues/3004 resolved + self.identifier = identifier # type: ignore # PRIVATE METHODS # @@ -283,7 +291,7 @@ def text(self, newText: str): self._text = newText @property - def syllabic(self) -> t.Optional[str]: + def syllabic(self) -> SyllabicChoices: ''' Returns or sets the syllabic property of a lyric. @@ -311,7 +319,7 @@ def syllabic(self) -> t.Optional[str]: @syllabic.setter - def syllabic(self, newSyllabic): + def syllabic(self, newSyllabic: SyllabicChoices): if newSyllabic not in SYLLABIC_CHOICES: raise LyricException( f'Syllabic value {newSyllabic!r} is not in ' @@ -336,6 +344,11 @@ def identifier(self) -> t.Union[str, int]: >>> l.identifier = 'Rainbow' >>> l.identifier 'Rainbow' + + Default value is the same as default for number, that is, 1: + + >>> note.Lyric().identifier + 1 ''' if self._identifier is None: return self._number @@ -343,7 +356,7 @@ def identifier(self) -> t.Union[str, int]: return self._identifier @identifier.setter - def identifier(self, value: str): + def identifier(self, value: t.Union[str, None]): self._identifier = value @property @@ -553,13 +566,18 @@ class GeneralNote(base.Music21Object): as :class:`~music21.articulations.Staccato`, etc.) that are stored on this Note.''' } - def __init__(self, *arguments, **keywords): - if 'duration' not in keywords: + def __init__(self, + *arguments, + duration: t.Optional[Duration] = None, + lyric: t.Union[None, str, Lyric] = None, + **keywords + ): + if duration is None: # ensure music21base not automatically create a duration. - if not keywords: - tempDuration = duration.Duration(1.0) + if not keywords or ('type' not in keywords and 'quarterLength' not in keywords): + tempDuration = Duration(1.0) else: - tempDuration = duration.Duration(**keywords) + tempDuration = Duration(**keywords) # only apply default if components are empty # looking at currentComponents so as not to trigger # _updateComponents @@ -567,16 +585,16 @@ def __init__(self, *arguments, **keywords): and not tempDuration.currentComponents()): tempDuration.quarterLength = 1.0 else: - tempDuration = keywords['duration'] + tempDuration = duration # this sets the stored duration defined in Music21Object super().__init__(duration=tempDuration) self.lyrics: t.List[Lyric] = [] # a list of lyric objects - self.expressions = [] - self.articulations = [] + self.expressions: t.List[expressions.Expression] = [] + self.articulations: t.List['music21.articulations.Articulation'] = [] - if 'lyric' in keywords and keywords['lyric'] is not None: - self.addLyric(keywords['lyric']) + if lyric is not None: + self.addLyric(lyric) # note: Chords handle ties differently self.tie = None # store a Tie object @@ -802,7 +820,7 @@ def fullName(self) -> str: return self.classes[0] # override in subclasses @property - def pitches(self) -> t.Tuple[pitch.Pitch, ...]: + def pitches(self) -> t.Tuple[Pitch, ...]: ''' Returns an empty tuple. (Useful for iterating over NotRests since they include Notes and Chords.) @@ -810,7 +828,7 @@ def pitches(self) -> t.Tuple[pitch.Pitch, ...]: return () @pitches.setter - def pitches(self, _value: t.Iterable[pitch.Pitch]): + def pitches(self, _value: t.Iterable[Pitch]): pass @@ -939,17 +957,18 @@ class NotRest(GeneralNote): information about the beaming of this note.''', } - def __init__(self, *arguments, **keywords): + def __init__(self, + *arguments, + beams: t.Optional[beam.Beams] = None, + **keywords): super().__init__(**keywords) self._notehead: str = 'normal' - self._noteheadFill = None + self._noteheadFill: t.Optional[bool] = None self._noteheadParenthesis: bool = False self._stemDirection: str = 'unspecified' self._volume: t.Optional[volume.Volume] = None # created on demand - # replace - self.linkage = 'tie' - if 'beams' in keywords: - self.beams = keywords['beams'] + if beams is not None: + self.beams = beams else: self.beams = beam.Beams() self._storedInstrument: t.Optional['music21.instrument.Instrument'] = None @@ -1091,7 +1110,7 @@ def notehead(self, value): self._notehead = value @property - def noteheadFill(self) -> bool: + def noteheadFill(self) -> t.Union[bool, None]: ''' Get or set the note head fill status of this NotRest. Valid note head fill values are True, False, or None (meaning default). "yes" and "no" are converted to True @@ -1113,6 +1132,7 @@ def noteheadFill(self) -> bool: @noteheadFill.setter def noteheadFill(self, value: t.Union[bool, None, str]): + boolValue: t.Optional[bool] if value in ('none', None, 'default'): boolValue = None # allow setting to none or None elif value in (True, 'filled', 'yes'): @@ -1397,6 +1417,16 @@ class Note(NotRest): >>> note.Note('C4') == note.Note('C4') True + + All keyword args that are valid for Duration or Pitch objects + are valid (as well as those for superclasses, NotRest, GeneralNote, + Music21Object): + + >>> n = note.Note(step='C', accidental='sharp', octave=2, id='csharp', type='eighth', dots=2) + >>> n.nameWithOctave + 'C#2' + >>> n.duration + ''' isNote = True @@ -1412,28 +1442,26 @@ class Note(NotRest): } # Accepts an argument for pitch - def __init__(self, pitchName: t.Union[str, pitch.Pitch, int, None] = None, **keywords): + def __init__(self, + pitch: t.Union[str, int, Pitch, None] = None, + *, + name: t.Optional[str] = None, + nameWithOctave: t.Optional[str] = None, + **keywords): super().__init__(**keywords) self._chordAttached: t.Optional['music21.chord.Chord'] = None - if 'pitch' in keywords and pitchName is None: - pitchName = keywords['pitch'] - del keywords['pitch'] - - if pitchName is not None: - if isinstance(pitchName, pitch.Pitch): - self.pitch = pitchName + if pitch is not None: + if isinstance(pitch, Pitch): + self.pitch = pitch else: # assume first argument is pitch - self.pitch = pitch.Pitch(pitchName, **keywords) + self.pitch = Pitch(pitch, **keywords) else: # supply a default pitch - name = 'C4' - if 'name' in keywords: - name = keywords['name'] - del keywords['name'] - elif 'nameWithOctave' in keywords: - name = keywords['nameWithOctave'] - del keywords['nameWithOctave'] - self.pitch = pitch.Pitch(name, **keywords) + if nameWithOctave is not None: + name = nameWithOctave + elif not name: + name = 'C4' + self.pitch = Pitch(name, **keywords) # noinspection PyProtectedMember self.pitch._client = self @@ -1599,7 +1627,7 @@ def _setOctave(self, value: t.Optional[int]): ''') @property - def pitches(self) -> t.Tuple[pitch.Pitch]: + def pitches(self) -> t.Tuple[Pitch, ...]: ''' Return the single :class:`~music21.pitch.Pitch` object in a tuple. This property is designed to provide an interface analogous to @@ -1641,7 +1669,7 @@ def pitches(self) -> t.Tuple[pitch.Pitch]: return (self.pitch,) @pitches.setter - def pitches(self, value: t.Sequence[pitch.Pitch]): + def pitches(self, value: t.Sequence[Pitch]): if common.isListLike(value) and value: self.pitch = value[0] else: @@ -1784,14 +1812,16 @@ class Unpitched(NotRest): AttributeError: 'Unpitched' object has no attribute 'pitch' ''' - def __init__(self, displayName=None, **keywords): + def __init__(self, + displayName=None, + **keywords): super().__init__(**keywords) self._chordAttached: t.Optional['music21.percussion.PercussionChord'] = None self.displayStep: StepName = 'B' self.displayOctave: int = 4 if displayName: - display_pitch = pitch.Pitch(displayName) + display_pitch = Pitch(displayName) self.displayStep = display_pitch.step self.displayOctave = display_pitch.octave @@ -1816,7 +1846,7 @@ def _setStoredInstrument(self, newValue): storedInstrument = property(_getStoredInstrument, _setStoredInstrument) - def displayPitch(self) -> pitch.Pitch: + def displayPitch(self) -> Pitch: ''' returns a pitch object that is the same as the displayStep and displayOctave. it will never have an accidental. @@ -1827,10 +1857,7 @@ def displayPitch(self) -> pitch.Pitch: >>> unp.displayPitch() ''' - p = pitch.Pitch() - p.step = self.displayStep - p.octave = self.displayOctave - return p + return Pitch(step=self.displayStep, octave=self.displayOctave) @property def displayName(self) -> str: @@ -1869,6 +1896,18 @@ class Rest(GeneralNote): >>> r.name 'rest' + + And their .pitches is an empty tuple + + >>> r.pitches + () + + + All arguments to Duration are valid in constructing: + + >>> r2 = note.Rest(type='whole') + >>> r2.duration.quarterLength + 4.0 ''' isRest = True name = 'rest' @@ -2034,10 +2073,11 @@ def testLyricRepr(self): def testComplex(self): from music21 import note + from music21.duration import DurationTuple note1 = note.Note() note1.duration.clear() - d1 = duration.DurationTuple('whole', 0, 4.0) - d2 = duration.DurationTuple('quarter', 0, 1.0) + d1 = DurationTuple('whole', 0, 4.0) + d2 = DurationTuple('quarter', 0, 1.0) note1.duration.addDurationTuple(d1) note1.duration.addDurationTuple(d2) self.assertEqual(note1.duration.quarterLength, 5.0) diff --git a/music21/pitch.py b/music21/pitch.py index f62ce4b672..864ddc1b62 100644 --- a/music21/pitch.py +++ b/music21/pitch.py @@ -1796,6 +1796,15 @@ class Pitch(prebase.ProtoM21Object): def __init__(self, name: t.Optional[t.Union[str, int, float]] = None, + *, + step: t.Optional[StepName] = None, + octave: t.Optional[int] = None, + accidental: t.Union[Accidental, str, int, float, None] = None, + microtone: t.Union[Microtone, int, float, None] = None, + pitchClass: t.Optional[t.Union[int, PitchClassString]] = None, + midi: t.Optional[int] = None, + ps: t.Optional[float] = None, + fundamental: t.Optional[Pitch] = None, **keywords): # No need for super().__init__() on protoM21Object self._groups: t.Optional[base.Groups] = None @@ -1843,36 +1852,31 @@ def __init__(self, self.spellingIsInferred = True if name >= 12: # is not a pitchClass self._octave = int(name / 12) - 1 + elif step is not None: + self.step = step - # override just about everything with keywords - # necessary for ImmutablePitch objects - if keywords: - if 'name' in keywords: - # noinspection PyArgumentList - self.name = keywords['name'] # set based on string - if 'step' in keywords: - self.step = keywords['step'] - if 'octave' in keywords: - self._octave = keywords['octave'] - if 'accidental' in keywords: - if isinstance(keywords['accidental'], Accidental): - self.accidental = keywords['accidental'] - else: - self.accidental = Accidental(keywords['accidental']) - if 'microtone' in keywords: - mt = keywords['microtone'] - if isinstance(mt, Microtone): - self.microtone = mt - else: - self.microtone = Microtone(mt) - if 'pitchClass' in keywords: - self.pitchClass = keywords['pitchClass'] - if 'fundamental' in keywords: - self.fundamental = keywords['fundamental'] - if 'midi' in keywords: - self.midi = keywords['midi'] - if 'ps' in keywords: - self.ps = keywords['ps'] + if octave is not None: + self._octave = octave + + if accidental is not None: + if isinstance(accidental, Accidental): + self.accidental = accidental + else: + self.accidental = Accidental(accidental) + if microtone is not None: + if isinstance(microtone, Microtone): + self.microtone = microtone + else: + self.microtone = Microtone(microtone) + if pitchClass is not None: + # type ignore until https://github.com/python/mypy/issues/3004 resolved + self.pitchClass = pitchClass # type: ignore + if fundamental is not None: + self.fundamental = fundamental + if midi is not None: + self.midi = midi + if ps is not None: + self.ps = ps def _reprInternal(self): return str(self) diff --git a/music21/style.py b/music21/style.py index 7c7aa32c60..d68f78f36b 100644 --- a/music21/style.py +++ b/music21/style.py @@ -617,7 +617,8 @@ class StyleMixin(common.SlottedObjectMixin): __slots__ = ('_style', '_editorial') def __init__(self): - # no need to call super().__init__() on SlottedObjectMixin + # no need to call super().__init__() on SlottedObjectMixin + # This might be dangerous though self._style: t.Optional[Style] = None self._editorial: t.Optional['music21.editorial.Editorial'] = None From 89cfd1f6e0f8914a7a29fec98a7e1d10e315a2ce Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 15:07:53 -1000 Subject: [PATCH 07/35] Stream; and fix previous typing --- music21/braille/basic.py | 3 +- music21/chord/__init__.py | 4 +- music21/note.py | 2 +- music21/stream/base.py | 97 ++++++++++++++++++++++++--------------- 4 files changed, 66 insertions(+), 40 deletions(-) diff --git a/music21/braille/basic.py b/music21/braille/basic.py index fe345b1a95..d2a3f2424c 100644 --- a/music21/braille/basic.py +++ b/music21/braille/basic.py @@ -19,6 +19,7 @@ from music21 import duration from music21 import environment from music21 import exceptions21 +from music21 import expressions from music21 import interval from music21 import note from music21 import tempo # for typing @@ -870,7 +871,7 @@ def handleExpressions(music21Note: note.GeneralNote, noteTrans: t.List[str]): # expressions (so far, just fermata) # ---------------------------------- for expr in music21Note.expressions: - if 'Fermata' in expr.classes: + if isinstance(expr, expressions.Fermata): try: fermataBraille = lookup.fermatas['shape'][expr.shape] noteTrans.append(fermataBraille) diff --git a/music21/chord/__init__.py b/music21/chord/__init__.py index bf2e064b0d..eb85272b81 100644 --- a/music21/chord/__init__.py +++ b/music21/chord/__init__.py @@ -388,7 +388,7 @@ def notes(self) -> t.Tuple[note.NotRest, ...]: return () @property - def tie(self): + def tie(self) -> t.Optional[tie.Tie]: ''' Get or set a single tie based on all the ties in this Chord. @@ -412,7 +412,7 @@ def tie(self): return None @tie.setter - def tie(self, value): + def tie(self, value: t.Optional[tie.Tie]): for d in self._notes: d.tie = value # set the same instance for each pitch diff --git a/music21/note.py b/music21/note.py index 6d89d87e08..60e6416bcd 100644 --- a/music21/note.py +++ b/music21/note.py @@ -597,7 +597,7 @@ def __init__(self, self.addLyric(lyric) # note: Chords handle ties differently - self.tie = None # store a Tie object + self.tie: t.Optional[tie.Tie] = None # store a Tie object def __eq__(self, other): ''' diff --git a/music21/stream/base.py b/music21/stream/base.py index 865ecfdec4..6ade0f7786 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -180,8 +180,9 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]): >>> s.first() - New in v7 -- providing a list of objects or Measures or Scores (but not other Stream - subclasses such as Parts or Voices) now positions sequentially, i.e. appends: + Providing a list of objects or Measures or Scores (but not other Stream + subclasses such as Parts or Voices) positions sequentially, i.e. appends, if they + all have offset 0.0 currently: >>> s2 = stream.Measure([note.Note(), note.Note(), bar.Barline()]) >>> s2.show('text') @@ -189,7 +190,7 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]): {1.0} {2.0} - A list of measures will let each be appended: + A list of measures will thus each be appended: >>> m1 = stream.Measure(n1, number=1) >>> m2 = stream.Measure(note.Rest(), number=2) @@ -200,7 +201,8 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]): {1.5} {0.0} - Here, every element is a Stream that's not a Measure, so we instead insert: + Here, every element is a Stream that's not a Measure (or Score), so it + will be inserted at 0.0, rather than appending: >>> s4 = stream.Score([stream.PartStaff(n1), stream.PartStaff(note.Rest())]) >>> s4.show('text') @@ -217,6 +219,25 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]): {0.0} {0.0} + This behavior can be modified by the `appendOrInsert` keyword to go against norms: + + >>> s6 = stream.Stream([note.Note('C'), note.Note('D')], appendOrInsert='insert') + >>> s6.show('text') # all notes at offset 0.0 + {0.0} + {0.0} + + >>> p1 = stream.Part(stream.Measure(note.Note('C')), id='p1') + >>> p2 = stream.Part(stream.Measure(note.Note('D')), id='p2') + >>> s7 = stream.Score([p1, p2], appendOrInsert='append') + >>> s7.show('text') # parts following each other (not recommended) + {0.0} + {0.0} + {0.0} + {1.0} + {0.0} + {0.0} + + For developers of subclasses, please note that because of how Streams are copied, there cannot be required parameters (i.e., without defaults) in initialization. @@ -226,6 +247,9 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]): class CrazyStream(Stream): def __init__(self, givenElements, craziness, *args, **kwargs): ... + + New in v.7 -- smart appending + New in v.8 -- appendOrInsert keyword configures the smart appending. ''' # this static attributes offer a performance boost over other # forms of checking class @@ -285,11 +309,14 @@ def __init__(self, givenElements, craziness, *args, **kwargs): # ''', } def __init__(self, - givenElements=None, - *args, - # restrictClass: t.Type[M21ObjType] = base.Music21Object, + givenElements: t.Union[None, + base.Music21Object, + t.Sequence[base.Music21Object]] = None, + *arguments, + appendOrInsert: t.Literal['append', 'insert', 'offsets'] = 'offsets', **keywords): - super().__init__(self, *args, **keywords) + # restrictClass: t.Type[M21ObjType] = base.Music21Object, + super().__init__(self, *arguments, **keywords) self.streamStatus = streamStatus.StreamStatus(self) self._unlinkedDuration = None @@ -319,19 +346,24 @@ def __init__(self, # but not if every element is a stream subclass other than a Measure or Score # (i.e. Part or Voice generally, but even Opus theoretically) # because these classes usually represent synchrony - append: bool = False - try: - append = all(e.offset == 0.0 for e in givenElements) - except AttributeError: - pass # appropriate failure will be raised by coreGuardBeforeAddElement() - if append and all( - (e.isStream and e.classSet.isdisjoint((Measure, Score))) - for e in givenElements): - append = False + appendBool = True + if appendOrInsert == 'offsets': + try: + appendBool = all(e.offset == 0.0 for e in givenElements) + except AttributeError: + pass # appropriate failure will be raised by coreGuardBeforeAddElement() + if appendBool and all( + (e.isStream and e.classSet.isdisjoint((Measure, Score))) + for e in givenElements): + appendBool = False + elif appendOrInsert == 'insert': + appendBool = False + else: + appendBool = True for e in givenElements: self.coreGuardBeforeAddElement(e) - if append: + if appendBool: self.coreAppend(e) else: self.coreInsert(e.offset, e) @@ -12655,9 +12687,9 @@ class Measure(Stream): for the amount of padding on the right side of a region.)''', } - def __init__(self, *args, **keywords): - if len(args) == 1 and isinstance(args[0], int) and 'number' not in keywords: - keywords['number'] = args[0] + def __init__(self, *args, number: t.Union[int, str] = 0, **keywords): + if len(args) == 1 and isinstance(args[0], int) and number == 0: + number = args[0] args = () super().__init__(*args, **keywords) @@ -12679,17 +12711,13 @@ def __init__(self, *args, **keywords): self.paddingRight: OffsetQL = 0.0 self.numberSuffix = None # for measure 14a would be 'a' - if 'number' in keywords: - num = keywords['number'] - if isinstance(num, str): - realNum, suffix = common.getNumFromStr(num) - self.number = int(realNum) - if suffix: - self.numberSuffix = suffix - else: - self.number = keywords['number'] + if isinstance(number, str): + realNum, suffix = common.getNumFromStr(number) + self.number = int(realNum) + if suffix: + self.numberSuffix = suffix else: - self.number = 0 # 0 means undefined or pickup + self.number = number # we can request layout width, using the same units used # in layout.py for systems; most musicxml readers do not support this # on input @@ -14181,12 +14209,9 @@ class SpannerStorage(Stream): TODO v7: rename spannerParent to client. ''' - def __init__(self, *arguments, **keywords): + def __init__(self, *arguments, spannerParent=None, **keywords): # No longer need store as weakref since Py2.3 and better references - self.spannerParent = None - if 'spannerParent' in keywords: - self.spannerParent = keywords['spannerParent'] - del keywords['spannerParent'] + self.spannerParent = spannerParent super().__init__(*arguments, **keywords) # must provide a keyword argument with a reference to the spanner From 48958b1faac12ff991accdfacd907543185a4137 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 16:40:56 -1000 Subject: [PATCH 08/35] fix mypy --- music21/note.py | 19 +++++++++++++++++-- music21/stream/base.py | 6 +++--- music21/tree/verticality.py | 7 +++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/music21/note.py b/music21/note.py index 60e6416bcd..3938e4133e 100644 --- a/music21/note.py +++ b/music21/note.py @@ -558,7 +558,6 @@ class GeneralNote(base.Music21Object): _DOC_ATTR: t.Dict[str, str] = { 'isChord': 'Boolean read-only value describing if this object is a Chord.', 'lyrics': 'A list of :class:`~music21.note.Lyric` objects.', - 'tie': 'either None or a :class:`~music21.note.Tie` object.', 'expressions': '''a list of expressions (such as :class:`~music21.expressions.Fermata`, etc.) that are stored on this Note.''', @@ -597,7 +596,7 @@ def __init__(self, self.addLyric(lyric) # note: Chords handle ties differently - self.tie: t.Optional[tie.Tie] = None # store a Tie object + self._tie: t.Optional[tie.Tie] = None # store a Tie object def __eq__(self, other): ''' @@ -628,6 +627,22 @@ def __eq__(self, other): return True # -------------------------------------------------------------------------- + @property + def tie(self) -> t.Optional[tie.Tie]: + ''' + Return and set a :class:`~music21.note.Tie` object, or None. + + >>> n = note.Note() + >>> n.tie is None + True + >>> n.tie = tie.Tie('start') + ''' + return self._tie + + @tie.setter + def tie(self, value: t.Optional[tie.Tie]): + self._tie = value + def _getLyric(self) -> t.Optional[str]: if not self.lyrics: return None diff --git a/music21/stream/base.py b/music21/stream/base.py index 6ade0f7786..4c9f6c2a2f 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -339,8 +339,8 @@ def __init__(self, if givenElements is None: return - if isinstance(givenElements, base.Music21Object) or not common.isIterable(givenElements): - givenElements = [givenElements] + if isinstance(givenElements, base.Music21Object): + givenElements = t.cast(t.List[base.Music21Object], [givenElements]) # Append rather than insert if every offset is 0.0 # but not if every element is a stream subclass other than a Measure or Score @@ -7087,7 +7087,7 @@ def updateEndMatch(nInner) -> bool: and isinstance(nInner, chord.Chord) and isinstance(nLast, chord.Chord) and None not in [inner_p.tie for inner_p in nInner.notes] - and {inner_p.tie.type for inner_p in nInner.notes} == {'stop'} + and {inner_p.tie.type for inner_p in nInner.notes} == {'stop'} # type: ignore and len(nLast.pitches) == len(nInner.pitches)): return True diff --git a/music21/tree/verticality.py b/music21/tree/verticality.py index 745be4e033..2faf9ee8fe 100644 --- a/music21/tree/verticality.py +++ b/music21/tree/verticality.py @@ -848,10 +848,13 @@ def conditionalAdd(ts, n: note.Note) -> None: if possibleNewNote.tie is None: return # do nothing - elif oldNoteTie is None: + + if oldNoteTie is None: notesToAdd[pitchKey] = possibleNewNote # a better note to add elif {oldNoteTie.type, possibleNewNote.tie.type} == startStopSet: - notesToAdd[pitchKey].tie.type = 'continue' + t = notesToAdd[pitchKey].tie + if t is not None: # this test is not needed, except for mypy. + t.type = 'continue' elif possibleNewNote.tie.type == 'continue': notesToAdd[pitchKey] = possibleNewNote # a better note to add elif possibleNewNote.tie.type == oldNoteTie.type: From db595882f01dbc81f7fde484ad297ac4e209c05c Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Sun, 14 Aug 2022 16:48:30 -1000 Subject: [PATCH 09/35] fixup chordBase; lint --- music21/chord/__init__.py | 20 +++++--------------- music21/tree/verticality.py | 6 +++--- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/music21/chord/__init__.py b/music21/chord/__init__.py index eb85272b81..a25de2c266 100644 --- a/music21/chord/__init__.py +++ b/music21/chord/__init__.py @@ -102,14 +102,12 @@ def __init__(self, self._notes: t.List[note.NotRest] = [] # here, pitch and duration data is extracted from notes - # if provided + # if provided. super().__init__(**keywords) # inherit Duration object from GeneralNote # keep it here in case we have no notes - # self.duration = None # inefficient, since note.Note.__init__ set it - # del self.pitch durationKeyword = None if 'duration' in keywords: durationKeyword = keywords['duration'] @@ -121,17 +119,6 @@ def __init__(self, elif 'type' in keywords or 'quarterLength' in keywords: # dots dont cut it self.duration = Duration(**keywords) - # elif len(notes) > 0: - # for thisNote in notes: - # # get duration from first note - # # but should other notes have the same duration? - # self.duration = notes[0].duration - # break - - if 'beams' in keywords: - self.beams = keywords['beams'] - else: - self.beams = beam.Beams() def __eq__(self, other): ''' @@ -199,7 +186,10 @@ def __len__(self): ''' return len(self._notes) - def _add_core_or_init(self, notes, *, useDuration=None): + def _add_core_or_init(self, + notes, + *, + useDuration: t.Union[None, t.Literal[False], duration.Duration] = None): ''' This is the private append method called by .add and called by __init__. diff --git a/music21/tree/verticality.py b/music21/tree/verticality.py index 2faf9ee8fe..357e3e625e 100644 --- a/music21/tree/verticality.py +++ b/music21/tree/verticality.py @@ -852,9 +852,9 @@ def conditionalAdd(ts, n: note.Note) -> None: if oldNoteTie is None: notesToAdd[pitchKey] = possibleNewNote # a better note to add elif {oldNoteTie.type, possibleNewNote.tie.type} == startStopSet: - t = notesToAdd[pitchKey].tie - if t is not None: # this test is not needed, except for mypy. - t.type = 'continue' + other_t = notesToAdd[pitchKey].tie + if other_t is not None: # this test is not needed, except for mypy. + other_t.type = 'continue' elif possibleNewNote.tie.type == 'continue': notesToAdd[pitchKey] = possibleNewNote # a better note to add elif possibleNewNote.tie.type == oldNoteTie.type: From 689e1ee3b9b9e7df72a56bc117a66a8639f972ca Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Mon, 15 Aug 2022 01:59:25 -1000 Subject: [PATCH 10/35] lint and mypy --- music21/chord/__init__.py | 5 ++++- music21/note.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/music21/chord/__init__.py b/music21/chord/__init__.py index a25de2c266..e009fa6cfa 100644 --- a/music21/chord/__init__.py +++ b/music21/chord/__init__.py @@ -189,7 +189,7 @@ def __len__(self): def _add_core_or_init(self, notes, *, - useDuration: t.Union[None, t.Literal[False], duration.Duration] = None): + useDuration: t.Union[None, t.Literal[False], Duration] = None): ''' This is the private append method called by .add and called by __init__. @@ -209,6 +209,7 @@ def _add_core_or_init(self, useDuration = self.duration quickDuration = True + newNote: note.NotRest for n in notes: if isinstance(n, pitch.Pitch): # assign pitch to a new Note @@ -1445,6 +1446,7 @@ def closedPosition( inPlace: t.Literal[True], leaveRedundantPitches=False ) -> None: + # astroid 1003 return None @overload @@ -1455,6 +1457,7 @@ def closedPosition( inPlace: t.Literal[False] = False, leaveRedundantPitches: bool = False ) -> _ChordType: + # astroid 1003 return self def closedPosition( diff --git a/music21/note.py b/music21/note.py index 3938e4133e..b56cbdf856 100644 --- a/music21/note.py +++ b/music21/note.py @@ -987,6 +987,7 @@ def __init__(self, else: self.beams = beam.Beams() self._storedInstrument: t.Optional['music21.instrument.Instrument'] = None + self._chordAttached: t.Optional['music21.chord.ChordBase'] = None # ============================================================================================== # Special functions @@ -1464,7 +1465,7 @@ def __init__(self, nameWithOctave: t.Optional[str] = None, **keywords): super().__init__(**keywords) - self._chordAttached: t.Optional['music21.chord.Chord'] = None + self._chordAttached: t.Optional['music21.chord.Chord'] if pitch is not None: if isinstance(pitch, Pitch): From 81f3d5a3803e60ff1659a54c0a19b668b9ab8af3 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Mon, 15 Aug 2022 02:10:42 -1000 Subject: [PATCH 11/35] discrete analysis --- music21/analysis/discrete.py | 18 ++++++--------- music21/analysis/reduction.py | 32 ++++++++++++--------------- music21/converter/__init__.py | 35 +++++++++--------------------- music21/converter/subConverters.py | 26 ++++++++++++++-------- music21/vexflow/toMusic21j.py | 4 +++- 5 files changed, 51 insertions(+), 64 deletions(-) diff --git a/music21/analysis/discrete.py b/music21/analysis/discrete.py index f89ee368b1..0bb0b8006c 100644 --- a/music21/analysis/discrete.py +++ b/music21/analysis/discrete.py @@ -1284,7 +1284,11 @@ def getSolution(self, sStream): # ----------------------------------------------------------------------------- # public access function -def analyzeStream(streamObj, *args, **keywords): +def analyzeStream( + streamObj: 'music21.stream.Stream', + method: str, + *args, + **keywords): ''' Public interface to discrete analysis methods to be applied to a Stream given as an argument. Methods return process-specific data format. @@ -1316,21 +1320,13 @@ def analyzeStream(streamObj, *args, **keywords): >>> s.analyze('span') - ''' - method = None - if 'method' in keywords: - method = keywords['method'] - - if args: - method = args[0] - if method == 'range': # getPitchRanges() was removed in v7 # this synonym is being added for compatibility method = 'span' - match = analysisClassFromMethodName(method) + match: t.Optional[t.Callable] = analysisClassFromMethodName(method) if match is not None: obj = match() # NOTE: Cuthbert, this was previously analysisClassName()? - out of scope @@ -1342,7 +1338,7 @@ def analyzeStream(streamObj, *args, **keywords): # noinspection SpellCheckingInspection -def analysisClassFromMethodName(method): +def analysisClassFromMethodName(method: str): ''' Returns an analysis class given a method name, or None if none can be found diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index d384ec10c8..93c8f39a82 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -461,7 +461,15 @@ class PartReduction: If the `normalize` parameter is False, no normalization will take place. The default is True. ''' - def __init__(self, srcScore=None, *args, **keywords): + def __init__(self, + srcScore=None, + *args, + partGroups: t.Optional[t.Dict[str, t.Any]] = None, + fillByMeasure: bool = True, + segmentByTarget: bool = True, + normalize: bool = True, + normalizeByPart: bool = False, + **keywords): if srcScore is None: return if not isinstance(srcScore, stream.Score): @@ -475,26 +483,14 @@ def __init__(self, srcScore=None, *args, **keywords): # define how parts are grouped # a list of dictionaries, with keys for name, color, and a match list - self._partGroups = None - if 'partGroups' in keywords: - self._partGroups = keywords['partGroups'] + self._partGroups = partGroups - self._fillByMeasure = True - if 'fillByMeasure' in keywords: - self._fillByMeasure = keywords['fillByMeasure'] + self._fillByMeasure = fillByMeasure # We re-partition if the spans change - self._segmentByTarget = True - if 'segmentByTarget' in keywords: - self._segmentByTarget = keywords['segmentByTarget'] - - self._normalizeByPart = False # norm by all parts is default - if 'normalizeByPart' in keywords: - self._normalizeByPart = keywords['normalizeByPart'] - - self._normalizeToggle = True - if 'normalize' in keywords: - self._normalizeToggle = keywords['normalize'] + self._segmentByTarget = segmentByTarget + self._normalizeByPart = normalizeByPart # norm by all parts is default + self._normalizeToggle = normalize # check that there are measures for p in self._score.parts: diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py index 1a6c04d98c..fca3294bdf 100644 --- a/music21/converter/__init__.py +++ b/music21/converter/__init__.py @@ -1191,6 +1191,9 @@ def parseURL(url, def parse(value: t.Union[bundles.MetadataEntry, bytes, str, pathlib.Path], *args, + forceSource: bool = False, + number: t.Optional[int] = None, + format: t.Optional[str] = None, # pylint: disable=redefined-builtin **keywords) -> t.Union[stream.Score, stream.Part, stream.Opus]: r''' Given a file path, encoded data in a Python string, or a URL, attempt to @@ -1241,25 +1244,7 @@ def parse(value: t.Union[bundles.MetadataEntry, bytes, str, pathlib.Path], possibility and has been removed. ''' # environLocal.printDebug(['attempting to parse()', value]) - if 'forceSource' in keywords: - forceSource = keywords['forceSource'] - del keywords['forceSource'] - else: - forceSource = False - # see if a work number is defined; for multi-work collections - if 'number' in keywords: - number = keywords['number'] - del keywords['number'] - else: - number = None - - if 'format' in keywords: - m21Format = keywords['format'] - del keywords['format'] - else: - m21Format = None - valueStr: str if isinstance(value, bytes): valueStr = value.decode('utf-8', 'ignore') @@ -1281,7 +1266,7 @@ def parse(value: t.Union[bundles.MetadataEntry, bytes, str, pathlib.Path], and value[1] is None and _osCanLoad(str(value[0]))): # comes from corpus.search - return parseFile(value[0], format=m21Format, **keywords) + return parseFile(value[0], format=format, **keywords) elif (common.isListLike(value) and isinstance(value, collections.abc.Sequence) and len(value) == 2 @@ -1297,26 +1282,26 @@ def parse(value: t.Union[bundles.MetadataEntry, bytes, str, pathlib.Path], 'If using a two-element list, the second value must be an integer number, ' f'not {value[1]!r}' ) - sc = parseFile(value[0], format=m21Format, **keywords) + sc = parseFile(value[0], format=format, **keywords) if isinstance(sc, stream.Opus): return sc.getScoreByNumber(value[1]) else: return sc # a midi string, must come before os.path.exists test elif not isinstance(value, bytes) and valueStr.startswith('MThd'): - return parseData(value, number=number, format=m21Format, **keywords) + return parseData(value, number=number, format=format, **keywords) elif (not isinstance(value, bytes) and _osCanLoad(valueStr)): - return parseFile(valueStr, number=number, format=m21Format, + return parseFile(valueStr, number=number, format=format, forceSource=forceSource, **keywords) elif (not isinstance(value, bytes) and _osCanLoad(common.cleanpath(valueStr))): - return parseFile(common.cleanpath(valueStr), number=number, format=m21Format, + return parseFile(common.cleanpath(valueStr), number=number, format=format, forceSource=forceSource, **keywords) elif not isinstance(valueStr, bytes) and (valueStr.startswith('http://') or valueStr.startswith('https://')): # it's a url; may need to broaden these criteria - return parseURL(value, number=number, format=m21Format, + return parseURL(value, number=number, format=format, forceSource=forceSource, **keywords) elif isinstance(value, pathlib.Path): raise FileNotFoundError(f'Cannot find file in {str(value)}') @@ -1325,7 +1310,7 @@ def parse(value: t.Union[bundles.MetadataEntry, bytes, str, pathlib.Path], raise FileNotFoundError(f'Cannot find file in {str(value)}') else: # all else, including MidiBytes - return parseData(value, number=number, format=m21Format, **keywords) + return parseData(value, number=number, format=format, **keywords) def freeze(streamObj, fmt=None, fp=None, fastButUnsafe=False, zipType='zlib') -> pathlib.Path: diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index feb99c2a22..fcc9ef3a5e 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -468,11 +468,17 @@ class ConverterLilypond(SubConverter): 'svg': ''} # sic! (Why?) codecWrite = True - def write(self, obj, fmt, fp=None, subformats=None, **keywords): # pragma: no cover + def write(self, + obj, + fmt, + fp=None, + subformats=None, + *, + coloredVariants: bool = False, + **keywords): # pragma: no cover from music21 import lily conv = lily.translate.LilypondConverter() - if 'coloredVariants' in keywords and keywords['coloredVariants'] is True: - conv.coloredVariants = True + conv.coloredVariants = coloredVariants if subformats is not None and 'pdf' in subformats: conv.loadFromMusic21Object(obj) @@ -543,14 +549,16 @@ class ConverterVexflow(SubConverter): registerFormats = ('vexflow',) registerOutputExtensions = ('html',) - def write(self, obj, fmt, fp=None, subformats=None, **keywords): # pragma: no cover + def write(self, + obj, + fmt, + fp=None, + subformats=None, + *, + local: bool = False + **keywords): # pragma: no cover # from music21 import vexflow from music21.vexflow import toMusic21j as vexflow - if 'local' in keywords: - local = keywords['local'] - else: - local = False - dataStr = vexflow.fromObject(obj, mode='html', local=local) fp = self.writeDataStream(fp, dataStr) return fp diff --git a/music21/vexflow/toMusic21j.py b/music21/vexflow/toMusic21j.py index 26d1d30c31..4f274b3ef7 100644 --- a/music21/vexflow/toMusic21j.py +++ b/music21/vexflow/toMusic21j.py @@ -26,7 +26,7 @@ ] -def fromObject(thisObject, mode='html', local=False): +def fromObject(thisObject, *, mode='html', local=False): ''' returns a string of data for a given Music21Object such as a Score, Note, etc. that can be displayed in a browser using the music21j package. Called by .show('vexflow'). @@ -68,6 +68,8 @@ def fromObject(thisObject, mode='html', local=False): + + Changed in v.8 -- mode and useLocal are keyword only. ''' conv = VexflowPickler() conv.mode = mode From 3c92d8c9fecf93e44a328851623908c8f97271ca Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Mon, 15 Aug 2022 18:21:00 -1000 Subject: [PATCH 12/35] fix lints --- music21/analysis/reduction.py | 3 ++- music21/common/stringTools.py | 11 +++++---- music21/converter/subConverters.py | 23 +++++++++++-------- music21/interval.py | 36 +++++++++++++----------------- music21/layout.py | 34 ++++++++++++---------------- 5 files changed, 51 insertions(+), 56 deletions(-) diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index 93c8f39a82..7ee1371af4 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -15,9 +15,10 @@ Used by graph.PlotHorizontalBarWeighted() ''' +import copy import re +import typing as t import unittest -import copy from music21 import exceptions21 diff --git a/music21/common/stringTools.py b/music21/common/stringTools.py index d869ff3445..106b133e06 100644 --- a/music21/common/stringTools.py +++ b/music21/common/stringTools.py @@ -261,7 +261,10 @@ def getMd5(value=None) -> str: return m.hexdigest() -def formatStr(msg, *arguments, **keywords) -> str: +def formatStr(msg, + *args, + format: t.Optional[str] = None, # pylint: disable=redefined-builtin + **keywords) -> str: ''' Format one or more data elements into string suitable for printing straight to stderr or other outputs @@ -271,12 +274,8 @@ def formatStr(msg, *arguments, **keywords) -> str: test 1 2 3 ''' - if 'format' in keywords: - formatType = keywords['format'] - else: - formatType = None - msg = [msg] + list(arguments) + msg = [msg] + list(args) for i in range(len(msg)): x = msg[i] if isinstance(x, bytes): diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index fcc9ef3a5e..929da29ada 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -555,7 +555,7 @@ def write(self, fp=None, subformats=None, *, - local: bool = False + local: bool = False, **keywords): # pragma: no cover # from music21 import vexflow from music21.vexflow import toMusic21j as vexflow @@ -953,6 +953,8 @@ def parseFile(self, def runThroughMusescore(self, fp, subformats=None, + *, + dpi: t.Optional[int] = None, **keywords) -> pathlib.Path: # pragma: no cover ''' Take the output of the conversion process and run it through musescore to convert it @@ -979,8 +981,8 @@ def runThroughMusescore(self, fpOut += subformatExtension musescoreRun = [str(musescorePath), fp, '-o', fpOut, '-T', '0'] - if 'dpi' in keywords: - musescoreRun.extend(['-r', str(keywords['dpi'])]) + if dpi is not None: + musescoreRun.extend(['-r', str(dpi)]) prior_qt = os.getenv('QT_QPA_PLATFORM') prior_xdg = os.getenv('XDG_RUNTIME_DIR') @@ -1188,16 +1190,19 @@ def parseFile(self, from music21.midi import translate as midiTranslate midiTranslate.midiFilePathToStream(filePath, self.stream, **keywords) - def write(self, obj, fmt, fp=None, subformats=None, **keywords): # pragma: no cover + def write(self, + obj, + fmt, + fp=None, + subformats=None, + *, + addStartDelay: bool = False, + **keywords): # pragma: no cover from music21.midi import translate as midiTranslate if fp is None: fp = self.getTemporaryFile() - midiTranslateKeywords = {} - if 'addStartDelay' in keywords: - midiTranslateKeywords['addStartDelay'] = keywords['addStartDelay'] - - mf = midiTranslate.music21ObjectToMidiFile(obj, **midiTranslateKeywords) + mf = midiTranslate.music21ObjectToMidiFile(obj, addStartDelay=addStartDelay) mf.open(fp, 'wb') # write binary mf.write() mf.close() diff --git a/music21/interval.py b/music21/interval.py index 8aede39f64..0219a355f8 100644 --- a/music21/interval.py +++ b/music21/interval.py @@ -2877,7 +2877,14 @@ class Interval(IntervalBase): >>> aInterval.isStep True ''' - def __init__(self, *arguments, **keywords): + def __init__(self, + *arguments, + diatonic: t.Optional[DiatonicInterval] = None, + chromatic: t.Optional[ChromaticInterval] = None, + noteStart: t.Optional[note.Note] = None, + noteEnd: t.Optional[note.Note] = None, + name: t.Optional[str] = None, + **keywords): # requires either (1) a string ('P5' etc.) or # (2) named arguments: # (2a) either both of @@ -2891,19 +2898,21 @@ def __init__(self, *arguments, **keywords): # both self.diatonic and self.chromatic can still both be None if an # empty Interval class is being created, such as in deepcopy - self.diatonic: t.Optional[DiatonicInterval] = None - self.chromatic: t.Optional[ChromaticInterval] = None + self.diatonic: t.Optional[DiatonicInterval] = diatonic + self.chromatic: t.Optional[ChromaticInterval] = chromatic # these can be accessed through noteStart and noteEnd properties - self._noteStart = None - self._noteEnd = None + self._noteStart = noteStart + self._noteEnd = noteEnd self.type = '' # harmonic or melodic self.implicitDiatonic = False # is this basically a ChromaticInterval object in disguise? - if len(arguments) == 1 and isinstance(arguments[0], str): + if len(arguments) == 1 and isinstance(arguments[0], str) or name is not None: # convert common string representations - dInterval, cInterval = _stringToDiatonicChromatic(arguments[0]) + if name is None: + name = arguments[0] + dInterval, cInterval = _stringToDiatonicChromatic(name) self.diatonic = dInterval self.chromatic = cInterval @@ -2933,19 +2942,6 @@ def __init__(self, *arguments, **keywords): self._noteStart = arguments[0] self._noteEnd = arguments[1] - if 'diatonic' in keywords: - self.diatonic = keywords['diatonic'] - if 'chromatic' in keywords: - self.chromatic = keywords['chromatic'] - if 'noteStart' in keywords: - self._noteStart = keywords['noteStart'] - if 'noteEnd' in keywords: - self._noteEnd = keywords['noteEnd'] - if 'name' in keywords: - dInterval, cInterval = _stringToDiatonicChromatic(keywords['name']) - self.diatonic = dInterval - self.chromatic = cInterval - # catch case where only one Note is provided if (self.diatonic is None and self.chromatic is None and ((self._noteStart is not None and self._noteEnd is None) diff --git a/music21/layout.py b/music21/layout.py index 6703aa71f9..7a8d565f54 100644 --- a/music21/layout.py +++ b/music21/layout.py @@ -454,34 +454,30 @@ class StaffGroup(spanner.Spanner): .. image:: images/layout_StaffGroup_01.* :width: 400 - ''' - - def __init__(self, *arguments, **keywords): + def __init__(self, + *arguments, + name: t.Optional[str] = None, + barTogether: t.Literal[True, False, None, 'Mensurstrich'] = True, + abbreviation: t.Optional[str] = None, + symbol: t.Literal['bracket', 'line', 'grace', 'square'] = None, + **keywords): super().__init__(*arguments, **keywords) - self.name = None # if this group has a name - self.abbreviation = None + self.name = name or abbreviation # if this group has a name + self.abbreviation = abbreviation self._symbol = None # Choices: bracket, line, brace, square + self.symbol = symbol # determines if barlines are grouped through; this is group barline # in musicxml - self._barTogether = True - - if 'symbol' in keywords: - self.symbol = keywords['symbol'] # user property - if 'barTogether' in keywords: - self.barTogether = keywords['barTogether'] # user property - if 'name' in keywords: - self.name = keywords['name'] # user property - if 'abbreviation' in keywords: - self.name = keywords['abbreviation'] # user property + self._barTogether = barTogether # -------------------------------------------------------------------------- - def _getBarTogether(self): + def _getBarTogether(self) -> t.Literal[True, False, None, 'Mensurstrich']: return self._barTogether - def _setBarTogether(self, value): + def _setBarTogether(self, value: t.Literal[True, False, None, 'Mensurstrich', 'yes', 'no']): if value is None: pass # do nothing for now; could set a default elif value in ['yes', True]: @@ -498,8 +494,7 @@ def _setBarTogether(self, value): or yes or no strings. Or the string 'Mensurstrich' which indicates barring between staves but not in staves. - Currently Mensurstrich i - + Currently Mensurstrich is not supported by most exporters. >>> sg = layout.StaffGroup() >>> sg.barTogether = 'yes' @@ -524,7 +519,6 @@ def _setSymbol(self, value): symbol = property(_getSymbol, _setSymbol, doc=''' Get or set the symbol value, with either Boolean values or yes or no strings. - >>> sg = layout.StaffGroup() >>> sg.symbol = 'Brace' >>> sg.symbol From 6c4de9aa308314ff03fbbf847ab12c051a3aec32 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Mon, 15 Aug 2022 19:35:10 -1000 Subject: [PATCH 13/35] lint, mypy, flake --- music21/analysis/discrete.py | 3 ++- music21/analysis/reduction.py | 15 ++++++++------- music21/common/stringTools.py | 2 +- music21/interval.py | 6 +++--- music21/layout.py | 6 +++--- music21/variant.py | 15 +++++++++++---- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/music21/analysis/discrete.py b/music21/analysis/discrete.py index 0bb0b8006c..52e104b954 100644 --- a/music21/analysis/discrete.py +++ b/music21/analysis/discrete.py @@ -1288,7 +1288,8 @@ def analyzeStream( streamObj: 'music21.stream.Stream', method: str, *args, - **keywords): + **keywords +): ''' Public interface to discrete analysis methods to be applied to a Stream given as an argument. Methods return process-specific data format. diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index 7ee1371af4..ee78e04c88 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -478,9 +478,10 @@ def __init__(self, self._score = srcScore # an ordered list of dictionaries for # part id, part color, and a list of Part objs - self._partBundles = [] + # TODO: typed dict + self._partBundles: t.List[t.Dict[str, t.Any]] = [] # a dictionary of part id to a list of events - self._eventSpans = {} + self._eventSpans: t.Dict[t.Union[str, int], t.List[t.Any]] = {} # define how parts are grouped # a list of dictionaries, with keys for name, color, and a match list @@ -744,10 +745,10 @@ def _dynamicToWeight(targets): finalBundle.append(dsFirst) continue # create new spans for each target in this segment - for i, t in enumerate(match): - targetStart = t.getOffsetBySite(flatRef) + for i, tar in enumerate(match): + targetStart = tar.getOffsetBySite(flatRef) # can use extended duration - targetSpan = t.duration.quarterLength + targetSpan = tar.duration.quarterLength # if dur of target is greater tn this span # end at this span if targetStart + targetSpan > offsetEnd: @@ -1321,8 +1322,8 @@ class TestExternal(unittest.TestCase): show = True def testPartReductionB(self): - t = Test() - t.testPartReductionB(show=self.show) + test = Test() + test.testPartReductionB(show=self.show) # ------------------------------------------------------------------------------ diff --git a/music21/common/stringTools.py b/music21/common/stringTools.py index 106b133e06..0bc05a8f52 100644 --- a/music21/common/stringTools.py +++ b/music21/common/stringTools.py @@ -288,7 +288,7 @@ def formatStr(msg, msg[i] = x.decode('utf-8') except AttributeError: msg[i] = '' - if formatType == 'block': + if format == 'block': return '\n*** '.join(msg) + '\n' else: # catch all others return ' '.join(msg) + '\n' diff --git a/music21/interval.py b/music21/interval.py index 0219a355f8..4b5a1afece 100644 --- a/music21/interval.py +++ b/music21/interval.py @@ -2880,9 +2880,9 @@ class Interval(IntervalBase): def __init__(self, *arguments, diatonic: t.Optional[DiatonicInterval] = None, - chromatic: t.Optional[ChromaticInterval] = None, - noteStart: t.Optional[note.Note] = None, - noteEnd: t.Optional[note.Note] = None, + chromatic: t.Optional[ChromaticInterval] = None, + noteStart: t.Optional['music21.note.Note'] = None, + noteEnd: t.Optional['music21.note.Note'] = None, name: t.Optional[str] = None, **keywords): # requires either (1) a string ('P5' etc.) or diff --git a/music21/layout.py b/music21/layout.py index 7a8d565f54..42d169708d 100644 --- a/music21/layout.py +++ b/music21/layout.py @@ -458,13 +458,13 @@ class StaffGroup(spanner.Spanner): def __init__(self, *arguments, name: t.Optional[str] = None, - barTogether: t.Literal[True, False, None, 'Mensurstrich'] = True, + barTogether: t.Literal[True, False, None, 'Mensurstrich'] = True, abbreviation: t.Optional[str] = None, symbol: t.Literal['bracket', 'line', 'grace', 'square'] = None, **keywords): super().__init__(*arguments, **keywords) - self.name = name or abbreviation # if this group has a name + self.name = name or abbreviation # if this group has a name self.abbreviation = abbreviation self._symbol = None # Choices: bracket, line, brace, square self.symbol = symbol @@ -484,7 +484,7 @@ def _setBarTogether(self, value: t.Literal[True, False, None, 'Mensurstrich', 'y self._barTogether = True elif value in ['no', False]: self._barTogether = False - elif hasattr(value, 'lower') and value.lower() == 'mensurstrich': + elif isinstance(value, str) and value.lower() == 'mensurstrich': self._barTogether = 'Mensurstrich' else: raise StaffGroupException(f'the bar together value {value} is not acceptable') diff --git a/music21/variant.py b/music21/variant.py index 01d77c1013..078e1e9e4c 100644 --- a/music21/variant.py +++ b/music21/variant.py @@ -90,16 +90,23 @@ class Variant(base.Music21Object): classSortOrder = stream.Stream.classSortOrder - 2 # variants should always come first? # this copies the init of Streams - def __init__(self, givenElements=None, *args, **keywords): + def __init__(self, + givenElements: t.Union[None, + base.Music21Object, + t.Sequence[base.Music21Object]] = None, + *args, + name: t.Optional[str] = None, + **keywords): super().__init__() self.exposeTime = False self._stream = stream.VariantStorage(givenElements=givenElements, - *args, **keywords) + *args, + **keywords) self._replacementDuration = None - if 'name' in keywords: - self.groups.append(keywords['name']) + if name is not None: + self.groups.append(name) def _deepcopySubclassable(self, memo=None, ignoreAttributes=None, removeFromIgnore=None): From 6723a1de48437976c6c8db1638b3a9123e7c200d Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Mon, 15 Aug 2022 21:16:15 -1000 Subject: [PATCH 14/35] Variant takes only one arg --- music21/analysis/reduction.py | 8 ++++---- music21/variant.py | 28 +++++++++++++--------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index ee78e04c88..5ccf835924 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -465,7 +465,7 @@ class PartReduction: def __init__(self, srcScore=None, *args, - partGroups: t.Optional[t.Dict[str, t.Any]] = None, + partGroups: t.Optional[t.List[t.Dict[str, t.Any]]] = None, fillByMeasure: bool = True, segmentByTarget: bool = True, normalize: bool = True, @@ -766,7 +766,7 @@ def _dynamicToWeight(targets): # as the start of this existing span # dsFirst['eStart'] = targetStart dsFirst['span'] = targetSpan - dsFirst['weight'] = targetToWeight(t) + dsFirst['weight'] = targetToWeight(tar) finalBundle.append(dsFirst) elif t == 0 and ds['eStart'] != targetStart: # add two, one for the empty region, one for target @@ -777,13 +777,13 @@ def _dynamicToWeight(targets): dsNext = copy.deepcopy(ds) dsNext['eStart'] = targetStart dsNext['span'] = targetSpan - dsNext['weight'] = targetToWeight(t) + dsNext['weight'] = targetToWeight(tar) finalBundle.append(dsNext) else: # for all other cases, create segment for each dsNext = copy.deepcopy(ds) dsNext['eStart'] = targetStart dsNext['span'] = targetSpan - dsNext['weight'] = targetToWeight(t) + dsNext['weight'] = targetToWeight(tar) finalBundle.append(dsNext) # after iterating all ds spans, reassign self._eventSpans[partBundle['pGroupId']] = finalBundle diff --git a/music21/variant.py b/music21/variant.py index 078e1e9e4c..1e73074ccf 100644 --- a/music21/variant.py +++ b/music21/variant.py @@ -59,7 +59,6 @@ class Variant(base.Music21Object): To use Variants from a Stream, see the :func:`~music21.stream.Stream.activateVariants` method. - >>> v = variant.Variant() >>> v.repeatAppend(note.Note(), 8) >>> len(v.notes) @@ -90,18 +89,19 @@ class Variant(base.Music21Object): classSortOrder = stream.Stream.classSortOrder - 2 # variants should always come first? # this copies the init of Streams - def __init__(self, - givenElements: t.Union[None, - base.Music21Object, - t.Sequence[base.Music21Object]] = None, - *args, - name: t.Optional[str] = None, - **keywords): - super().__init__() + def __init__( + self, + givenElements: t.Union[None, + base.Music21Object, + t.Sequence[base.Music21Object]] = None, + name: t.Optional[str] = None, + appendOrInsert: t.Literal['append', 'insert', 'offsets'] = 'offsets', + **music21ObjectKeywords, + ): + super().__init__(**music21ObjectKeywords) self.exposeTime = False self._stream = stream.VariantStorage(givenElements=givenElements, - *args, - **keywords) + appendOrInsert=appendOrInsert) self._replacementDuration = None @@ -165,7 +165,6 @@ def __getattr__(self, attr): def __getitem__(self, key): return self._stream.__getitem__(key) - def __len__(self): return len(self._stream) @@ -177,7 +176,6 @@ def getElementIds(self): self._cache['elementIds'] = [id(c) for c in self._stream._elements] return self._cache['elementIds'] - def replaceElement(self, old, new): ''' When copying a Variant, we need to update the Variant with new @@ -1741,8 +1739,8 @@ def getMeasureHashes(s): # noinspection PyShadowingNames ''' Takes in a stream containing measures and returns a list of hashes, - one for each measure. Currently - implemented with search.translateStreamToString() + one for each measure. This is currently implemented + with search.translateStreamToString() >>> s = converter.parse("tinynotation: 2/4 c4 d8. e16 FF4 a'4 b-2") >>> sm = s.makeMeasures() From a627b5602135445f5c9c0474438e5cce6bf5b142 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Mon, 15 Aug 2022 21:24:37 -1000 Subject: [PATCH 15/35] kwargs -> keywords throughout --- music21/common/decorators.py | 16 +++++++------- music21/corpus/chorales.py | 41 ++++++++++++++++-------------------- music21/corpus/corpora.py | 4 ++-- music21/corpus/manager.py | 4 ++-- music21/derivation.py | 4 ++-- music21/graph/primitives.py | 4 ++-- music21/harmony.py | 9 +------- music21/mei/test_base.py | 14 ++++++------ music21/metadata/__init__.py | 8 +++---- music21/metadata/bundles.py | 14 ++++++------ music21/search/lyrics.py | 4 ++-- music21/sorting.py | 8 +++---- music21/stream/base.py | 4 ++-- music21/test/commonTest.py | 6 +++--- music21/test/testRunner.py | 22 +++++++++---------- 15 files changed, 75 insertions(+), 87 deletions(-) diff --git a/music21/common/decorators.py b/music21/common/decorators.py index bbf46d6f0d..7da0cf3a2e 100644 --- a/music21/common/decorators.py +++ b/music21/common/decorators.py @@ -26,7 +26,7 @@ def optional_arg_decorator(fn): a decorator for decorators. Allows them to either have or not have arguments. ''' @wraps(fn) - def wrapped_decorator(*args, **kwargs): + def wrapped_decorator(*args, **keywords): is_bound_method = hasattr(args[0], fn.__name__) if args else False klass = None @@ -35,7 +35,7 @@ def wrapped_decorator(*args, **kwargs): args = args[1:] # If no arguments were passed... - if len(args) == 1 and not kwargs and callable(args[0]): + if len(args) == 1 and not keywords and callable(args[0]): if is_bound_method: return fn(klass, args[0]) else: @@ -44,9 +44,9 @@ def wrapped_decorator(*args, **kwargs): else: def real_decorator(toBeDecorated): if is_bound_method: - return fn(klass, toBeDecorated, *args, **kwargs) + return fn(klass, toBeDecorated, *args, **keywords) else: - return fn(toBeDecorated, *args, **kwargs) + return fn(toBeDecorated, *args, **keywords) return real_decorator return wrapped_decorator @@ -129,7 +129,7 @@ def deprecated(method, startDate=None, removeDate=None, message=None): 'message': m} @wraps(method) - def func_wrapper(*args, **kwargs): + def func_wrapper(*args, **keywords): if len(args) > 1 and args[1] in ('_ipython_canary_method_should_not_exist_', '_repr_mimebundle_', '_is_coroutine', @@ -147,7 +147,7 @@ def func_wrapper(*args, **kwargs): exceptions21.Music21DeprecationWarning, stacklevel=2) callInfo['calledAlready'] = True - return method(*args, **kwargs) + return method(*args, **keywords) return func_wrapper @@ -175,11 +175,11 @@ def cacheMethod(method): funcName = method.__name__ @wraps(method) - def inner(instance, *args, **kwargs): + def inner(instance, *args, **keywords): if funcName in instance._cache: return instance._cache[funcName] - instance._cache[funcName] = method(instance, *args, **kwargs) + instance._cache[funcName] = method(instance, *args, **keywords) return instance._cache[funcName] return inner diff --git a/music21/corpus/chorales.py b/music21/corpus/chorales.py index b3e8852307..69734dff8a 100644 --- a/music21/corpus/chorales.py +++ b/music21/corpus/chorales.py @@ -956,15 +956,12 @@ class Iterator: (currentNumber, highestNumber, numberingSystem). For example corpus.chorales.Iterator(1, 26,'riemenschneider') iterates through the riemenschneider numbered chorales from 1 to 26. - Additionally, the following kwargs can be set: + Additionally, the following keywords can be set: - returnType = either 'stream' (default) or 'filename' - - iterationType = either 'number' or 'index' - - titleList = [list, of, titles] - - numberList = [list, of, numbers] + * `returnType` = either 'stream' (default) or 'filename' + * `iterationType` = either 'number' or 'index' + * `titleList` = [list, of, titles] + * `numberList` = [list, of, numbers] >>> from music21 import corpus >>> for chorale in corpus.chorales.Iterator(1, 4, returnType='filename'): @@ -974,7 +971,6 @@ class Iterator: bach/bwv153.1 bach/bwv86.6 - >>> BCI = corpus.chorales.Iterator() >>> BCI.numberingSystem 'riemenschneider' @@ -985,9 +981,8 @@ class Iterator: >>> BCI.highestNumber 371 - An exception will be raised if the number set is not in the - numbering system selected, or if the - numbering system selected is not valid. + An Exception will be raised if the number set is not in the + numbering system selected, or if the numbering system selected is not valid. >>> BCI.currentNumber = 377 Traceback (most recent call last): @@ -1022,8 +1017,9 @@ class Iterator: note that the first chorale in the given numberList will not be part of the iteration because the first currentNumber is set to 2 at the - start by the first argument. (If iterationType = 'index' setting the currentNumber to 1 and the - highestNumber to 7 would have the same effect as the given example. + start by the first argument. (If `iterationType=='index'`, + setting the currentNumber to 1 and the highestNumber to 7 + would have the same effect as the given example. >>> BCI = corpus.chorales.Iterator(2, 371, numberingSystem='riemenschneider', ... numberList=[1, 2, 3, 4, 6, 190, 371, 500], @@ -1054,7 +1050,6 @@ class Iterator: >>> print(corpus.chorales.Iterator(returnType='filename')[55]) bach/bwv121.6 - For the first 20 chorales in the Riemenschneider numbering system, there are professionally annotated roman numeral analyses in romanText format, courtesy of Dmitri Tymoczko of Princeton University. To get them as an additional part to the score set returnType to "stream", and @@ -1074,7 +1069,7 @@ def __init__(self, currentNumber=None, highestNumber=None, numberingSystem='riemenschneider', - **kwargs): + **keywords): ''' By default: numberingSystem = 'riemenschneider', currentNumber = 1, highestNumber = 371, iterationType = 'number', @@ -1102,19 +1097,19 @@ def __init__(self, self._choraleList1 = ChoraleList() # For budapest, baerenreiter self._choraleList2 = ChoraleListRKBWV() # for kalmus, riemenschneider, title, and bwv - self.numberingSystem = numberingSystem # This assignment must come before the kwargs + self.numberingSystem = numberingSystem # This assignment must come before the keywords - for key in kwargs: + for key in keywords: if key == 'returnType': - self.returnType = kwargs[key] + self.returnType = keywords[key] elif key == 'numberList': - self.numberList = kwargs[key] + self.numberList = keywords[key] elif key == 'titleList': - self.titleList = kwargs[key] + self.titleList = keywords[key] elif key == 'iterationType': - self.iterationType = kwargs[key] + self.iterationType = keywords[key] elif key == 'analysis': - self.analysis = kwargs[key] + self.analysis = keywords[key] # These assignments must come after .iterationType diff --git a/music21/corpus/corpora.py b/music21/corpus/corpora.py index 1eb41320a0..3f2ebb30d3 100644 --- a/music21/corpus/corpora.py +++ b/music21/corpus/corpora.py @@ -343,7 +343,7 @@ def search(self, query, field=None, fileExtensions=None, - **kwargs): + **keywords): r''' Search this corpus for metadata entries, returning a metadataBundle @@ -368,7 +368,7 @@ def search(self, query, field=field, fileExtensions=fileExtensions, - **kwargs + **keywords ) # PUBLIC PROPERTIES # diff --git a/music21/corpus/manager.py b/music21/corpus/manager.py index f9919f03c9..b4dca30bf1 100644 --- a/music21/corpus/manager.py +++ b/music21/corpus/manager.py @@ -214,7 +214,7 @@ def _addCorpusFilepathToStreamObject(streamObj, filePath): streamObj.corpusFilepath = filePath -def search(query=None, field=None, corpusNames=None, fileExtensions=None, **kwargs): +def search(query=None, field=None, corpusNames=None, fileExtensions=None, **keywords): ''' Search all stored metadata bundles and return a list of file paths. @@ -278,7 +278,7 @@ def search(query=None, field=None, corpusNames=None, fileExtensions=None, **kwar for corpusName in corpusNames: c = fromName(corpusName) searchResults = c.metadataBundle.search( - query, field, fileExtensions=fileExtensions, **kwargs) + query, field, fileExtensions=fileExtensions, **keywords) allSearchResults = allSearchResults.union(searchResults) return allSearchResults diff --git a/music21/derivation.py b/music21/derivation.py index 5bdd7e2f75..797065d048 100644 --- a/music21/derivation.py +++ b/music21/derivation.py @@ -52,8 +52,8 @@ def derivationMethod(function): from via 'allGreen'> ''' @functools.wraps(function) - def wrapper(self, *args, **kwargs): - result = function(self, *args, **kwargs) + def wrapper(self, *args, **keywords): + result = function(self, *args, **keywords) result.derivation.origin = self result.derivation.method = function.__name__ return result diff --git a/music21/graph/primitives.py b/music21/graph/primitives.py index 1657a817a9..09d161cf58 100644 --- a/music21/graph/primitives.py +++ b/music21/graph/primitives.py @@ -695,9 +695,9 @@ class GraphColorGrid(Graph): figureSizeDefault = (9, 6) keywordConfigurables = Graph.keywordConfigurables + ('hideLeftBottomSpines',) - def __init__(self, *args, **kwargs): + def __init__(self, *args, **keywords): self.hideLeftBottomSpines = True - super().__init__(*args, **kwargs) + super().__init__(*args, **keywords) def renderSubplot(self, subplot): # do not need a grid for the outer container diff --git a/music21/harmony.py b/music21/harmony.py index f9978a15db..23d3696a3c 100644 --- a/music21/harmony.py +++ b/music21/harmony.py @@ -214,7 +214,7 @@ def __init__(self, updatePitches: bool = True, **keywords ): - super().__init__() + super().__init__(**keywords) self._writeAsChord = False # TODO: Deal with the roman numeral property of harmonies. # MusicXML documentation is ambiguous: @@ -250,13 +250,6 @@ def __init__(self, self._updatePitches() self._updateFromParameters(root=root, bass=bass, inversion=inversion) - # TODO(jtw): make these kwargs explicit somehow - # once there is a general solution for this with GeneralNote - ql = keywords.get('duration', None) - ql = keywords.get('quarterLength', ql) - if ql: - self.duration = duration.Duration(ql) - # SPECIAL METHODS # def _reprInternal(self): diff --git a/music21/mei/test_base.py b/music21/mei/test_base.py index 2f4703c6cf..ec1f78710e 100644 --- a/music21/mei/test_base.py +++ b/music21/mei/test_base.py @@ -159,7 +159,7 @@ def testSafePitch2(self): self.assertEqual(expected.octave, actual.octave) def testSafePitch3(self): - '''safePitch(): when ``name`` is not given, but there are various kwargs''' + '''safePitch(): when ``name`` is not given, but there are various **keywords''' expected = pitch.Pitch('D#6') actual = base.safePitch(name='D', accidental='#', octave='6') self.assertEqual(expected.name, actual.name) @@ -4335,7 +4335,7 @@ def testScoreIntegration1(self): def testCoreUnit1(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE): ''' sectionScoreCore(): everything basic, as called by scoreFromElement() - - no kwargs + - no keywords - and the has no @n; it would be set to "1" automatically - one of everything (
, , and ) - that the in here won't be processed ( must be in a
) @@ -4412,7 +4412,7 @@ def testCoreUnit1(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE def testCoreIntegration1(self): ''' sectionScoreCore(): everything basic, as called by scoreFromElement() - - no kwargs + - no keywords - and the has no @n; it would be set to "1" automatically - one of everything (
, , and ) - that the in here won't be processed ( must be in a
) @@ -4471,7 +4471,7 @@ def testCoreIntegration1(self): def testCoreUnit2(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE): ''' sectionScoreCore(): everything basic, as called by sectionFromElement() - - no kwargs + - no keywords - but the elements do have @n so those values should be used - one of most things (
, , and ) - two of (one in a
) @@ -4569,7 +4569,7 @@ def testCoreUnit2(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE def testCoreIntegration2(self): ''' sectionScoreCore(): everything basic, as called by sectionFromElement() - - no kwargs + - no keywords - but the elements do have @n so those values should be used - one of most things (
, , and ) - two of (one in a
) @@ -4649,7 +4649,7 @@ def testCoreIntegration2(self): def testCoreUnit3(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE): ''' sectionScoreCore(): everything basic, as called by sectionFromElement() - - all kwargs + - all keywords - and the has no @n; it should use the backupNum - activeMeter = a MagicMock (we expect this returned) - nextMeasureLeft = 'next left measure' (expected in the Measure) @@ -4712,7 +4712,7 @@ def testCoreUnit3(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE def testCoreIntegration3(self): ''' sectionScoreCore(): everything basic, as called by sectionFromElement() - - all kwargs + - all keywords - and the has no @n; it should use the backupNum - activeMeter = a MagicMock (we expect this returned) - nextMeasureLeft = 'next left measure' (expected in the Measure) diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index 5493445121..266e5ce5f5 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -1012,7 +1012,7 @@ def getContributorsByRole(self, role: t.Optional[str]) -> t.Tuple[Contributor, . result.append(contrib) return tuple(result) - def search(self, query=None, field=None, **kwargs): + def search(self, query=None, field=None, **keywords): r''' Search one or all fields with a query, given either as a string or a regular expression match. @@ -1080,10 +1080,10 @@ def search(self, query=None, field=None, **kwargs): # field (so that 'Joplin' would return 'Joplin, Scott') reQuery = None valueFieldPairs = [] - if query is None and field is None and not kwargs: + if query is None and field is None and not keywords: return (False, None) - elif query is None and field is None and kwargs: - field, query = kwargs.popitem() + elif query is None and field is None and keywords: + field, query = keywords.popitem() if field is not None: field = field.lower() diff --git a/music21/metadata/bundles.py b/music21/metadata/bundles.py index 660f04a8ef..dd411529c2 100644 --- a/music21/metadata/bundles.py +++ b/music21/metadata/bundles.py @@ -126,9 +126,9 @@ def show(self, showFormat=None): score = self.parse() score.show(showFormat) - def search(self, query=None, field=None, **kwargs): + def search(self, query=None, field=None, **keywords): # runs search on the RichMetadata object - return self.metadata.search(query, field, **kwargs) + return self.metadata.search(query, field, **keywords) # PUBLIC PROPERTIES # @@ -1141,7 +1141,7 @@ def read(self, filePath=None): ]) return self - def search(self, query=None, field=None, fileExtensions=None, **kwargs): + def search(self, query=None, field=None, fileExtensions=None, **keywords): r''' Perform search, on all stored metadata, permit regular expression matching. @@ -1193,9 +1193,9 @@ def search(self, query=None, field=None, fileExtensions=None, **kwargs): newMetadataBundle = MetadataBundle() if query is None and field is None: - if not kwargs: + if not keywords: raise MetadataBundleException('Query cannot be empty') - field, query = kwargs.popitem() + field, query = keywords.popitem() for key in self._metadataEntries: metadataEntry = self._metadataEntries[key] @@ -1226,8 +1226,8 @@ def search(self, query=None, field=None, fileExtensions=None, **kwargs): sorted(list(newMetadataBundle._metadataEntries.items()), key=lambda mde: mde[1].sourcePath)) - if kwargs: - return newMetadataBundle.search(**kwargs) + if keywords: + return newMetadataBundle.search(**keywords) return newMetadataBundle diff --git a/music21/search/lyrics.py b/music21/search/lyrics.py index 56ebb298ec..874fd14e64 100644 --- a/music21/search/lyrics.py +++ b/music21/search/lyrics.py @@ -55,11 +55,11 @@ def __repr__(self): + f'measure={self.measure!r}, lyric={self.lyric!r}, text={self.text!r}, ' + f'identifier={self.identifier!r})') - def modify(self, **kw): + def modify(self, **keywords): ''' see docs for SortTuple for what this does ''' - outList = [kw.get(attr, getattr(self, attr)) for attr in _attrList] + outList = [keywords.get(attr, getattr(self, attr)) for attr in _attrList] return self.__class__(*outList) diff --git a/music21/sorting.py b/music21/sorting.py index 21d4d03cee..c63b29e340 100644 --- a/music21/sorting.py +++ b/music21/sorting.py @@ -95,9 +95,9 @@ class SortTuple(namedtuple('SortTuple', ( '1.0 <0.20.323>' ''' - def __new__(cls, *tupEls, **kw): + def __new__(cls, *tupEls, **keywords): # noinspection PyTypeChecker - return super(SortTuple, cls).__new__(cls, *tupEls, **kw) + return super(SortTuple, cls).__new__(cls, *tupEls, **keywords) def __eq__(self, other): if isinstance(other, tuple): @@ -176,7 +176,7 @@ def shortRepr(self): reprParts.append('>') return ''.join(reprParts) - def modify(self, **kw): + def modify(self, **keywords): ''' return a new SortTuple identical to the previous, except with the given keyword modified. Works only with keywords. @@ -201,7 +201,7 @@ def modify(self, **kw): Changing offset, but nothing else, helps in creating .flatten() positions. ''' # _fields are the namedtuple attributes - outList = [kw.get(attr, getattr(self, attr)) for attr in self._fields] + outList = [keywords.get(attr, getattr(self, attr)) for attr in self._fields] return self.__class__(*outList) def add(self, other): diff --git a/music21/stream/base.py b/music21/stream/base.py index 4c9f6c2a2f..8ae82f4162 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -245,7 +245,7 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]): be allowed, because craziness and givenElements are required:: class CrazyStream(Stream): - def __init__(self, givenElements, craziness, *args, **kwargs): + def __init__(self, givenElements, craziness, *args, **keywords): ... New in v.7 -- smart appending @@ -9985,7 +9985,7 @@ def findConsecutiveNotes( N.B. for chords, currently, only the first pitch is tested for unison. this is a bug TODO: FIX - (\*\*kwargs is there so that other methods that pass along dicts to + (\*\*keywords is there so that other methods that pass along dicts to findConsecutiveNotes don't have to remove their own args; this method is used in melodicIntervals.) diff --git a/music21/test/commonTest.py b/music21/test/commonTest.py index 3155b863e8..2da4530d37 100644 --- a/music21/test/commonTest.py +++ b/music21/test/commonTest.py @@ -60,15 +60,15 @@ def testImports(): def defaultDoctestSuite(name=None): globs = __import__('music21').__dict__.copy() docTestOptions = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE) - kwArgs = { + keywords = { 'globs': globs, 'optionflags': docTestOptions, } # in case there are any tests here, get a suite to load up later if name is not None: - s1 = doctest.DocTestSuite(name, **kwArgs) + s1 = doctest.DocTestSuite(name, **keywords) else: - s1 = doctest.DocTestSuite(**kwArgs) + s1 = doctest.DocTestSuite(**keywords) return s1 # from testRunner... diff --git a/music21/test/testRunner.py b/music21/test/testRunner.py index f0e1a43a02..4da17285f2 100644 --- a/music21/test/testRunner.py +++ b/music21/test/testRunner.py @@ -130,7 +130,7 @@ def stripAddresses(textString, replacement='ADDRESS') -> str: # ------------------------------------------------------------------------------ -def mainTest(*testClasses, **kwargs): +def mainTest(*testClasses, **keywords): ''' Takes as its arguments modules (or a string 'noDocTest' or 'verbose') and runs all of these modules through a unittest suite @@ -164,7 +164,7 @@ def testHello(self): runAllTests = True # default -- is fail fast. - failFast = bool(kwargs.get('failFast', True)) + failFast = bool(keywords.get('failFast', True)) if failFast: optionflags = ( doctest.ELLIPSIS @@ -181,7 +181,7 @@ def testHello(self): if ('noDocTest' in testClasses or 'noDocTest' in sys.argv or 'nodoctest' in sys.argv - or bool(kwargs.get('noDocTest', False))): + or bool(keywords.get('noDocTest', False))): skipDoctest = True else: skipDoctest = False @@ -195,14 +195,14 @@ def testHello(self): # here we use '__main__' instead of a module if ('moduleRelative' in testClasses or 'moduleRelative' in sys.argv - or bool(kwargs.get('moduleRelative', False))): + or bool(keywords.get('moduleRelative', False))): pass else: for di in defaultImports: globs = __import__(di).__dict__.copy() if ('importPlusRelative' in testClasses or 'importPlusRelative' in sys.argv - or bool(kwargs.get('importPlusRelative', False))): + or bool(keywords.get('importPlusRelative', False))): globs.update(inspect.stack()[1][0].f_globals) try: @@ -219,14 +219,14 @@ def testHello(self): verbosity = 1 if ('verbose' in testClasses or 'verbose' in sys.argv - or bool(kwargs.get('verbose', False))): + or bool(keywords.get('verbose', False))): verbosity = 2 # this seems to hide most display displayNames = False if ('list' in sys.argv or 'display' in sys.argv - or bool(kwargs.get('display', False)) - or bool(kwargs.get('list', False))): + or bool(keywords.get('display', False)) + or bool(keywords.get('list', False))): displayNames = True runAllTests = False @@ -236,13 +236,13 @@ def testHello(self): if arg not in ('list', 'display', 'verbose', 'nodoctest'): # run a test directly named in this module runThisTest = sys.argv[1] - if bool(kwargs.get('runTest', False)): - runThisTest = kwargs.get('runTest', False) + if bool(keywords.get('runTest', False)): + runThisTest = keywords.get('runTest', False) # -f, --failfast if ('onlyDocTest' in sys.argv or 'onlyDocTest' in testClasses - or bool(kwargs.get('onlyDocTest', False))): + or bool(keywords.get('onlyDocTest', False))): testClasses = [] # remove cases for t in testClasses: if not isinstance(t, str): From 25228ea03e736e38fb366b458e82855d04c36cff Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 07:35:29 -1000 Subject: [PATCH 16/35] midi realtime, etc. --- music21/converter/__init__.py | 10 ++-- music21/metadata/primitives.py | 47 +++++++++++-------- music21/midi/realtime.py | 44 +++++++----------- music21/midi/translate.py | 11 ++--- music21/search/segment.py | 10 ++-- music21/spanner.py | 85 +++++++++++++++++++++------------- music21/stream/base.py | 14 +++--- 7 files changed, 119 insertions(+), 102 deletions(-) diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py index fca3294bdf..cec88f40b3 100644 --- a/music21/converter/__init__.py +++ b/music21/converter/__init__.py @@ -263,6 +263,8 @@ def __init__(self, fp: t.Union[str, pathlib.Path], forceSource: bool = False, number: t.Optional[int] = None, + # quantizePost: bool = False, + # quarterLengthDivisors: t.Optional[t.Iterable[int]] = None, **keywords): self.fp: pathlib.Path = common.cleanpath(fp, returnPathlib=True) self.forceSource: bool = forceSource @@ -295,7 +297,7 @@ def getPickleFp(self, pathNameToParse = str(self.fp) - quantization = [] + quantization: t.List[str] = [] if 'quantizePost' in self.keywords and self.keywords['quantizePost'] is False: quantization.append('noQtz') elif 'quarterLengthDivisors' in self.keywords: @@ -2016,7 +2018,7 @@ def testParseMidiQuantize(self): from music21 import omr midiFp = omr.correctors.pathName + os.sep + 'k525short.mid' - midiStream = parse(midiFp, forceSource=True, storePickle=False, quarterLengthDivisors=[2]) + midiStream = parse(midiFp, forceSource=True, storePickle=False, quarterLengthDivisors=(2,)) # midiStream.show() for n in midiStream[note.Note]: self.assertTrue(isclose(n.quarterLength % 0.5, 0.0, abs_tol=1e-7)) @@ -2035,7 +2037,7 @@ def testParseMidiNoQuantize(self): streamFpNotQuantized = parse(fp, quantizePost=False) self.assertIn(0.875, streamFpNotQuantized.flatten()._uniqueOffsetsAndEndTimes()) - streamFpCustomQuantized = parse(fp, quarterLengthDivisors=[2]) + streamFpCustomQuantized = parse(fp, quarterLengthDivisors=(2,)) self.assertNotIn(0.75, streamFpCustomQuantized.flatten()._uniqueOffsetsAndEndTimes()) # Also check raw data: https://github.com/cuthbertLab/music21/issues/546 @@ -2050,7 +2052,7 @@ def testParseMidiNoQuantize(self): pf1.removePickle() pf2 = PickleFilter(fp, quantizePost=False) pf2.removePickle() - pf3 = PickleFilter(fp, quarterLengthDivisors=[2]) + pf3 = PickleFilter(fp, quarterLengthDivisors=(2,)) pf3.removePickle() def testIncorrectNotCached(self): diff --git a/music21/metadata/primitives.py b/music21/metadata/primitives.py index 7b9d4d6537..04664f0f20 100644 --- a/music21/metadata/primitives.py +++ b/music21/metadata/primitives.py @@ -440,7 +440,7 @@ class DateSingle(prebase.ProtoM21Object): # INITIALIZER # - def __init__(self, data: t.Any = '', relevance='certain'): + def __init__(self, data: str = '', relevance='certain'): self._data: t.List[Date] = [] self._relevance = None # managed by property # not yet implemented @@ -478,7 +478,7 @@ def __str__(self): # PRIVATE METHODS # - def _prepareData(self, data): + def _prepareData(self, data: str): r''' Assume a string is supplied as argument ''' @@ -998,37 +998,46 @@ class Contributor(prebase.ProtoM21Object): # INITIALIZER # - def __init__(self, *args, **keywords): + def __init__(self, + *args, + name: t.Optional[str] = None, + names: t.Iterable[str] = (), + role: t.Optional[str] = None, + birth: t.Union[None, DateSingle, str] = None, + death: t.Union[None, DateSingle, str] = None, + **keywords): self._role = None - if 'role' in keywords: + if role: # stored in self._role - self.role = keywords['role'] # validated with property + self.role = role # validated with property else: self.role = None # a list of Text objects to support various spellings or # language translations self._names: t.List[Text] = [] - if 'name' in keywords: # a single - self._names.append(Text(keywords['name'])) - if 'names' in keywords: # many - for n in keywords['names']: + if name: # a single + self._names.append(Text(name)) + if names: # many + for n in names: self._names.append(Text(n)) # store the nationality, if known self._nationality = [] - self.birth = None - self.death = None + self.birth: t.Optional[DateSingle] = None + self.death: t.Optional[DateSingle] = None - if 'birth' in keywords: - birth = keywords['birth'] + if birth is not None: if not isinstance(birth, DateSingle): - birth = DateSingle(birth) - self.birth = birth - if 'death' in keywords: - death = keywords['death'] + birthDS = DateSingle(birth) + else: + birthDS = birth + self.birth = birthDS + if death is not None: if not isinstance(death, DateSingle): - death = DateSingle(death) - self.death = death + deathDS = DateSingle(death) + else: + deathDS = death + self.death = deathDS def _reprInternal(self): return f'{self.role}:{self.name}' diff --git a/music21/midi/realtime.py b/music21/midi/realtime.py index 33bf075659..0ad3b7ba7c 100644 --- a/music21/midi/realtime.py +++ b/music21/midi/realtime.py @@ -73,35 +73,22 @@ class StreamPlayer: # pragma: no cover ''' mixerInitialized = False - def __init__(self, streamIn, **keywords): + def __init__( + self, + streamIn: stream.Stream, + reinitMixer: bool = False, + mixerFreq: int = 44100, + mixerBitSize: int = -16, + mixerChannels: int = 2, + mixerBuffer: int = 1024, + ): try: # noinspection PyPackageRequirements import pygame # type: ignore self.pygame = pygame except ImportError: raise StreamPlayerException('StreamPlayer requires pygame. Install first') - if (self.mixerInitialized is False - or ('reinitMixer' in keywords and keywords['reinitMixer'] is not False)): - if 'mixerFreq' in keywords: - mixerFreq = keywords['mixerFreq'] - else: - mixerFreq = 44100 - - if 'mixerBitSize' in keywords: - mixerBitSize = keywords['mixerBitSize'] - else: - mixerBitSize = -16 - - if 'mixerChannels' in keywords: - mixerChannels = keywords['mixerChannels'] - else: - mixerChannels = 2 - - if 'mixerBuffer' in keywords: - mixerBuffer = keywords['mixerBuffer'] - else: - mixerBuffer = 1024 - + if self.mixerInitialized is False or reinitMixer: pygame.mixer.init(mixerFreq, mixerBitSize, mixerChannels, mixerBuffer) self.streamIn = streamIn @@ -128,9 +115,14 @@ def play(self, you to completely control whether to stop it. Ignore every other arguments ''' streamStringIOFile = self.getStringOrBytesIOFile() - self.playStringIOFile(streamStringIOFile, busyFunction, busyArgs, - endFunction, endArgs, busyWaitMilliseconds, - playForMilliseconds=playForMilliseconds, blocked=blocked) + self.playStringIOFile(streamStringIOFile, + busyFunction=busyFunction, + busyArgs=busyArgs, + endFunction=endFunction, + endArgs=endArgs, + busyWaitMilliseconds=busyWaitMilliseconds, + playForMilliseconds=playForMilliseconds, + blocked=blocked) def getStringOrBytesIOFile(self): streamMidiFile = midiTranslate.streamToMidiFile(self.streamIn) diff --git a/music21/midi/translate.py b/music21/midi/translate.py index 3b4f7521fd..1b3a8e2733 100644 --- a/music21/midi/translate.py +++ b/music21/midi/translate.py @@ -1872,6 +1872,7 @@ def midiTrackToStream( inputM21=None, conductorPart: t.Optional[stream.Part] = None, isFirst: bool = False, + quarterLengthDivisors: t.Sequence[int] = (), **keywords ) -> stream.Part: # noinspection PyShadowingNames @@ -1968,9 +1969,7 @@ def midiTrackToStream( iGathered = [] # store a list of indexes of gathered values put into chords voicesRequired = False - if 'quarterLengthDivisors' in keywords: - quarterLengthDivisors = keywords['quarterLengthDivisors'] - else: + if not quarterLengthDivisors: quarterLengthDivisors = defaults.quantizationQuarterLengthDivisors if len(notes) > 1: @@ -2823,6 +2822,7 @@ def midiStringToStream(strData, **keywords): def midiFileToStream( mf: 'music21.midi.MidiFile', + *, inputM21=None, quantizePost=True, **keywords @@ -2856,6 +2856,8 @@ def midiFileToStream( >>> len(s.flatten().notesAndRests) 14 + + Changed in v8: inputM21 and quantizePost are keyword only. ''' # environLocal.printDebug(['got midi file: tracks:', len(mf.tracks)]) if inputM21 is None: @@ -2866,9 +2868,6 @@ def midiFileToStream( if not mf.tracks: raise exceptions21.StreamException('no tracks are defined in this MIDI file.') - if 'quantizePost' in keywords: - quantizePost = keywords.pop('quantizePost') - # create a stream for each track # may need to check if tracks actually have event data midiTracksToStreams(mf.tracks, diff --git a/music21/search/segment.py b/music21/search/segment.py index c84fb283af..c0b06bb420 100644 --- a/music21/search/segment.py +++ b/music21/search/segment.py @@ -138,23 +138,19 @@ def indexScoreParts(scoreFile, *args, **keywords): return indexedList -def _indexSingleMulticore(filePath, *args, **keywords): +def _indexSingleMulticore(filePath, *args, failFast=False, **keywords): ''' Index one path in the context of multicore. ''' - keywords2 = copy.copy(keywords) - if 'failFast' in keywords2: - del keywords2['failFast'] - if not isinstance(filePath, pathlib.Path): filePath = pathlib.Path(filePath) shortFp = filePath.name try: - indexOutput = indexOnePath(filePath, *args, **keywords2) + indexOutput = indexOnePath(filePath, *args, **keywords) except Exception as e: # pylint: disable=broad-except - if 'failFast' not in keywords or keywords['failFast'] is False: + if not failFast: print(f'Failed on parse/index for, {filePath}: {e}') indexOutput = '' else: diff --git a/music21/spanner.py b/music21/spanner.py index 896841ed0f..a1aebf7910 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -1266,7 +1266,7 @@ class RepeatBracket(Spanner): ''' - def __init__(self, *arguments, **keywords): + def __init__(self, *arguments, number: t.Optional[int] = None, **keywords): super().__init__(*arguments, **keywords) self._number = None @@ -1275,8 +1275,8 @@ def __init__(self, *arguments, **keywords): self._numberSpanIsContiguous = None self.overrideDisplay = None - if 'number' in keywords: - self.number = keywords['number'] + if number is not None: + self.number = number # property to enforce numerical numbers def _getNumber(self): @@ -1456,13 +1456,14 @@ class Ottava(Spanner): ''' validOttavaTypes = ('8va', '8vb', '15ma', '15mb', '22da', '22db') - def __init__(self, *arguments, **keywords): + def __init__(self, + *arguments, + type: str = '8va', + # transposing: bool = + **keywords): super().__init__(*arguments, **keywords) self._type = None # can be 8va, 8vb, 15ma, 15mb - if 'type' in keywords: - self.type = keywords['type'] # use property - else: # use 8 as a default - self.type = '8va' + self.type = type self.placement = 'above' # can above or below, after musicxml if 'transposing' in keywords and keywords['transposing'] in (True, False): @@ -1636,12 +1637,13 @@ def undoTransposition(self): p.transpose(myInterval, inPlace=True) + class Line(Spanner): - '''A line or bracket represented as a spanner above two Notes. + ''' + A line or bracket represented as a spanner above two Notes. Brackets can take many line types. - >>> b = spanner.Line() >>> b.lineType = 'dotted' >>> b.lineType @@ -1649,37 +1651,50 @@ class Line(Spanner): >>> b = spanner.Line(endHeight=20) >>> b.endHeight 20 - ''' validLineTypes = ('solid', 'dashed', 'dotted', 'wavy') validTickTypes = ('up', 'down', 'arrow', 'both', 'none') - def __init__(self, *arguments, **keywords): + def __init__( + self, + *arguments, + lineType: str = 'solid', + tick: str = 'down', + startTick: str = 'down', + endTick: str = 'down', + startHeight: t.Optional[t.Union[int, float]] = None, + endHeight: t.Optional[t.Union[int, float]] = None, + **keywords + ): super().__init__(*arguments, **keywords) - self._endTick = 'down' # can ne up/down/arrow/both/None - self._startTick = 'down' # can ne up/down/arrow/both/None + DEFAULT_TICK = 'down' + self._endTick = DEFAULT_TICK # can ne up/down/arrow/both/None + self._startTick = DEFAULT_TICK # can ne up/down/arrow/both/None self._endHeight = None # for up/down, specified in tenths self._startHeight = None # for up/down, specified in tenths - self._lineType = 'solid' # can be solid, dashed, dotted, wavy - self.placement = 'above' # can above or below, after musicxml + DEFAULT_LINE_TYPE = 'solid' + self._lineType = DEFAULT_LINE_TYPE # can be solid, dashed, dotted, wavy + + DEFAULT_PLACEMENT = 'above' + self.placement = DEFAULT_PLACEMENT # can above or below, after musicxml - if 'lineType' in keywords: - self.lineType = keywords['lineType'] # use property + if lineType != DEFAULT_LINE_TYPE: + self.lineType = lineType # use property - if 'startTick' in keywords: - self.startTick = keywords['startTick'] # use property - if 'endTick' in keywords: - self.endTick = keywords['endTick'] # use property - if 'tick' in keywords: - self.tick = keywords['tick'] # use property + if startTick != DEFAULT_TICK: + self.startTick = startTick # use property + if endTick != DEFAULT_TICK: + self.endTick = endTick # use property + if tick != DEFAULT_TICK: + self.tick = tick # use property - if 'endHeight' in keywords: - self.endHeight = keywords['endHeight'] # use property - if 'startHeight' in keywords: - self.startHeight = keywords['startHeight'] # use property + if endHeight is not None: + self.endHeight = endHeight # use property + if startHeight is not None: + self.startHeight = startHeight # use property def _getEndTick(self): return self._endTick @@ -1805,7 +1820,11 @@ class Glissando(Spanner): validLineTypes = ('solid', 'dashed', 'dotted', 'wavy') validSlideTypes = ('chromatic', 'continuous', 'diatonic', 'white', 'black') - def __init__(self, *arguments, **keywords): + def __init__(self, + *arguments, + lineType: str = 'wavy', + label: t.Optional[str], + **keywords): super().__init__(*arguments, **keywords) self._lineType = 'wavy' @@ -1813,10 +1832,10 @@ def __init__(self, *arguments, **keywords): self.label = None - if 'lineType' in keywords: - self.lineType = keywords['lineType'] # use property - if 'label' in keywords: - self.label = keywords['label'] # use property + if lineType != GLISSANDO_DEFAULT_LINETYPE: + self.lineType = lineType # use property + if label is not None: + self.label = label # use property def _getLineType(self): return self._lineType diff --git a/music21/stream/base.py b/music21/stream/base.py index 8ae82f4162..28d19677e3 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -9025,11 +9025,11 @@ def augmentOrDiminish(self, amountToScale, *, inPlace=False): def quantize( self, - quarterLengthDivisors=None, - processOffsets=True, - processDurations=True, - inPlace=False, - recurse=False, + quarterLengthDivisors: t.Iterable[int] = (), + processOffsets: bool = True, + processDurations: bool = True, + inPlace: bool = False, + recurse: bool = False, ): # noinspection PyShadowingNames ''' @@ -9069,7 +9069,7 @@ def quantize( >>> nShort.quarterLength = 0.26 >>> s.repeatInsert(nShort, [1.49, 1.76]) - >>> s.quantize([4], processOffsets=True, processDurations=True, inPlace=True) + >>> s.quantize((4,), processOffsets=True, processDurations=True, inPlace=True) >>> [e.offset for e in s] [0.0, 0.5, 1.0, 1.5, 1.75] >>> [e.duration.quarterLength for e in s] @@ -9155,7 +9155,7 @@ def quantize( >>> [e.duration.quarterLength for e in v] [0.5, 0.5, 0.5, 0.25, 0.25] ''' - if quarterLengthDivisors is None: + if not quarterLengthDivisors: quarterLengthDivisors = defaults.quantizationQuarterLengthDivisors # this presently is not trying to avoid overlaps that From a51069770d769111422ad7b15af67fcc57ef5222 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 08:36:28 -1000 Subject: [PATCH 17/35] lint, mypy --- music21/midi/translate.py | 7 +++++-- music21/search/segment.py | 1 - music21/spanner.py | 35 +++++++++++++++++------------------ 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/music21/midi/translate.py b/music21/midi/translate.py index 1b3a8e2733..2f087a7462 100644 --- a/music21/midi/translate.py +++ b/music21/midi/translate.py @@ -2671,8 +2671,9 @@ def streamToMidiFile( def midiFilePathToStream( filePath, + *, inputM21=None, - **keywords + **keywords, ): ''' Used by music21.converter: @@ -2693,13 +2694,15 @@ def midiFilePathToStream( >>> streamScore = midi.translate.midiFilePathToStream(fp) >>> streamScore + + Changed in v8: inputM21 is keyword only. ''' from music21 import midi as midiModule mf = midiModule.MidiFile() mf.open(filePath) mf.read() mf.close() - return midiFileToStream(mf, inputM21, **keywords) + return midiFileToStream(mf, inputM21=inputM21, **keywords) def midiAsciiStringToBinaryString( diff --git a/music21/search/segment.py b/music21/search/segment.py index c0b06bb420..64a0bee4a1 100644 --- a/music21/search/segment.py +++ b/music21/search/segment.py @@ -24,7 +24,6 @@ (But then PyPy probably won't work.) ''' -import copy import difflib import json import math diff --git a/music21/spanner.py b/music21/spanner.py index a1aebf7910..492bee0587 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -1406,11 +1406,11 @@ class Ottava(Spanner): >>> print(ottava) - An Ottava spanner can either be transposing or non-transposing. - In a transposing Ottava spanner, the notes should be in their - written octave (as if the spanner were not there) and all the - notes in the spanner will be transposed on Stream.toSoundingPitch() + In a transposing Ottava spanner, the notes in the stream should be + in their written octave (as if the spanner were not there) and all the + notes in the spanner will be transposed on Stream.toSoundingPitch(). + A non-transposing spanner has notes that are at the pitch that they would sound (therefore the Ottava spanner is a decorative line). @@ -1421,6 +1421,7 @@ class Ottava(Spanner): >>> n2 = note.Note('E4') >>> n2.offset = 2.0 >>> ottava.addSpannedElements([n1, n2]) + >>> s = stream.Stream([ottava, n1, n2]) >>> s.atSoundingPitch = False >>> s2 = s.toSoundingPitch() @@ -1434,7 +1435,7 @@ class Ottava(Spanner): D3 E3 - All valid types + All valid types are given below: >>> ottava.validOttavaTypes ('8va', '8vb', '15ma', '15mb', '22da', '22db') @@ -1458,18 +1459,16 @@ class Ottava(Spanner): def __init__(self, *arguments, - type: str = '8va', - # transposing: bool = + type: str = '8va', # pylint: disable=redefined-builtin + transposing: bool = True, + placement: t.Literal['above', 'below'] = 'above', **keywords): super().__init__(*arguments, **keywords) self._type = None # can be 8va, 8vb, 15ma, 15mb self.type = type - self.placement = 'above' # can above or below, after musicxml - if 'transposing' in keywords and keywords['transposing'] in (True, False): - self.transposing = keywords['transposing'] - else: - self.transposing = True + self.placement = placement # can above or below, after musicxml + self.transposing = transposing def _getType(self): return self._type @@ -1506,12 +1505,12 @@ def _setType(self, newType): (such as 8va or 15mb) or with a pair specifying size and direction. >>> os = spanner.Ottava() - >>> os.type = 15, 'down' - >>> os.type - '15mb' >>> os.type = '8vb' >>> os.type '8vb' + >>> os.type = 15, 'down' + >>> os.type + '15mb' ''') def _reprInternal(self): @@ -1622,7 +1621,6 @@ def undoTransposition(self): True >>> n1.nameWithOctave 'D#3' - ''' if self.transposing: return @@ -1823,11 +1821,12 @@ class Glissando(Spanner): def __init__(self, *arguments, lineType: str = 'wavy', - label: t.Optional[str], + label: t.Optional[str] = None, **keywords): super().__init__(*arguments, **keywords) - self._lineType = 'wavy' + GLISSANDO_DEFAULT_LINETYPE = 'wavy' + self._lineType = GLISSANDO_DEFAULT_LINETYPE self._slideType = 'chromatic' self.label = None From 5a2ed20984f2c181c99931f9cb32fd2b7fef3a92 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 10:23:06 -1000 Subject: [PATCH 18/35] Chorales, Date primitives, RepeatBracket --- music21/corpus/chorales.py | 76 ++++++++++---------- music21/metadata/primitives.py | 124 ++++++++++++++++----------------- music21/spanner.py | 49 +++++++++---- music21/stream/base.py | 11 ++- 4 files changed, 145 insertions(+), 115 deletions(-) diff --git a/music21/corpus/chorales.py b/music21/corpus/chorales.py index 69734dff8a..25fd3e6bec 100644 --- a/music21/corpus/chorales.py +++ b/music21/corpus/chorales.py @@ -14,9 +14,10 @@ numbering schemes and filters and includes the corpus.chorales.Iterator() class for easily iterating through the chorale collection. ''' - -import unittest import copy +import typing as t +import warnings +import unittest from music21 import exceptions21 from music21 import environment @@ -950,6 +951,9 @@ class Iterator: .highestNumber to the highest in the range. This can either be done by catalogue number (iterationType = 'number') or by index (iterationType = 'index'). + Note that these numbers are 1-indexed (as most catalogues are) and + unlike Python's range feature, the final number is included. + Changing the numberingSystem will reset the iterator and change the range values to span the entire numberList. The iterator can be initialized with three parameters @@ -1066,10 +1070,16 @@ class Iterator: 'titleList', 'numberList', 'returnType', 'iterationType'] def __init__(self, - currentNumber=None, - highestNumber=None, - numberingSystem='riemenschneider', - **keywords): + currentNumber: t.Optional[int] = None, + highestNumber: t.Optional[int] = None, + *, + numberingSystem: str = 'riemenschneider', + returnType: str = 'stream', + iterationType: str = 'number', + analysis: bool = False, + numberList: t.Optional[t.List[int]] = None, + titleList: t.Optional[t.List[str]] = None, + ): ''' By default: numberingSystem = 'riemenschneider', currentNumber = 1, highestNumber = 371, iterationType = 'number', @@ -1092,24 +1102,23 @@ def __init__(self, self._numberingSystem = None self._returnType = 'stream' self._iterationType = 'number' - self.analysis = False + self.analysis = analysis self._choraleList1 = ChoraleList() # For budapest, baerenreiter self._choraleList2 = ChoraleListRKBWV() # for kalmus, riemenschneider, title, and bwv self.numberingSystem = numberingSystem # This assignment must come before the keywords - for key in keywords: - if key == 'returnType': - self.returnType = keywords[key] - elif key == 'numberList': - self.numberList = keywords[key] - elif key == 'titleList': - self.titleList = keywords[key] - elif key == 'iterationType': - self.iterationType = keywords[key] - elif key == 'analysis': - self.analysis = keywords[key] + self.returnType = returnType + self.iterationType = iterationType + + if numberList is not None: + # TODO: overly complex order of setting... + self.numberList = numberList + + if titleList is not None: + # TODO: overly complex order of setting... + self.titleList = titleList # These assignments must come after .iterationType @@ -1167,7 +1176,8 @@ def __next__(self): self._currentIndex += 1 return nextChorale - # ### Functions + # ### Private Methods + def _returnChorale(self, choraleIndex=None): # noinspection SpellCheckingInspection ''' @@ -1179,7 +1189,6 @@ def _returnChorale(self, choraleIndex=None): the chorale is instead queried by Title from the titleList and the numberList is ignored. - >>> from music21 import corpus >>> BCI = corpus.chorales.Iterator() >>> riemenschneider1 = BCI._returnChorale() @@ -1294,7 +1303,6 @@ def _bwvSort(bwv: str) -> float: ''' This takes a string such as '69.6-a' and returns a float for sorting. ''' - out = '' for char in bwv: if char.isdigit() or char == '.': @@ -1439,7 +1447,7 @@ def _setTitleList(self, value=None): if v in self._choraleList2.byTitle: self._titleList.append(v) else: - print(f'{v} will be skipped because it is not a recognized title') + warnings.warn(f'{v} will be skipped because it is not a recognized title') if not self._titleList: self._titleList = None @@ -1470,7 +1478,7 @@ def _setNumberList(self, value): if v in self._choraleList2.byRiemenschneider: self._numberList.append(v) else: - print( + warnings.warn( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1480,7 +1488,7 @@ def _setNumberList(self, value): if v in self._choraleList2.byKalmus and v != 0: self._numberList.append(v) else: - print( + warnings.warn( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1490,7 +1498,7 @@ def _setNumberList(self, value): if v in self._choraleList2.byBWV: self._numberList.append(v) else: - print( + warnings.warn( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1500,7 +1508,7 @@ def _setNumberList(self, value): if v in self._choraleList1.byBudapest: self._numberList.append(v) else: - print( + warnings.warn( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1510,7 +1518,7 @@ def _setNumberList(self, value): if v in self._choraleList1.byBaerenreiter: self._numberList.append(v) else: - print( + warnings.warn( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1518,13 +1526,12 @@ def _setNumberList(self, value): if self._numberList is None: self.currentNumber = 0 self.highestNumber = 0 + elif self.iterationType == 'number': + self.currentNumber = self._numberList[0] + self.highestNumber = self._numberList[-1] else: - if self.iterationType == 'number': - self.currentNumber = self._numberList[0] - self.highestNumber = self._numberList[-1] - else: - self.currentNumber = 0 - self.highestNumber = len(self._numberList) - 1 + self.currentNumber = 0 + self.highestNumber = len(self._numberList) - 1 numberList = property(_getNumberList, _setNumberList, doc='''Allows access to the catalogue numbers @@ -1774,9 +1781,6 @@ class BachException(exceptions21.Music21Exception): pass -# class Test(unittest.TestCase): -# pass - class TestExternal(unittest.TestCase): show = True diff --git a/music21/metadata/primitives.py b/music21/metadata/primitives.py index 04664f0f20..3cb4ce887f 100644 --- a/music21/metadata/primitives.py +++ b/music21/metadata/primitives.py @@ -46,7 +46,6 @@ # ----------------------------------------------------------------------------- - class Date(prebase.ProtoM21Object): r''' A single date value, specified by year, month, day, hour, minute, and @@ -409,38 +408,13 @@ def hasError(self): # ----------------------------------------------------------------------------- - - -class DateSingle(prebase.ProtoM21Object): - r''' - Store a date, either as certain, approximate, or uncertain relevance. - - The relevance attribute is limited within each DateSingle subclass - depending on the design of the class. Alternative relevance types should be - configured as other DateSingle subclasses. - - >>> dd = metadata.DateSingle('2009/12/31', 'approximate') - >>> dd - - - >>> str(dd) - '2009/12/31' - - >>> dd.relevance - 'approximate' - - >>> dd = metadata.DateSingle('1805/3/12', 'uncertain') - >>> str(dd) - '1805/03/12' +class DatePrimitive(prebase.ProtoM21Object): + ''' + A default class for all date objects, which can have different types. ''' - - # CLASS VARIABLES # - - isSingle = True - # INITIALIZER # - def __init__(self, data: str = '', relevance='certain'): + def __init__(self, relevance='certain'): self._data: t.List[Date] = [] self._relevance = None # managed by property # not yet implemented @@ -448,7 +422,6 @@ def __init__(self, data: str = '', relevance='certain'): # is certain, approximate, or uncertain # here, dataError is relevance self._dataError: t.List[str] = [] - self._prepareData(data) self.relevance = relevance # will use property # SPECIAL METHODS # @@ -474,19 +447,7 @@ def _reprInternal(self) -> str: return str(self) def __str__(self): - return str(self._data[0]) # always the first - - # PRIVATE METHODS # - - def _prepareData(self, data: str): - r''' - Assume a string is supplied as argument - ''' - # here, using a list to store one object; this provides more - # compatibility w/ other formats - self._data = [] # clear list - self._data.append(Date()) - self._data[0].load(data) + return str([str(d) for d in self._data]) # PUBLIC PROPERTIES # @@ -530,6 +491,47 @@ def relevance(self, value): f'Relevance value is not supported by this object: {value!r}') +class DateSingle(DatePrimitive): + r''' + Store a date, either as certain, approximate, or uncertain relevance. + + The relevance attribute is limited within each DateSingle subclass + depending on the design of the class. Alternative relevance types should be + configured as other DateSingle subclasses. + + >>> dd = metadata.DateSingle('2009/12/31', 'approximate') + >>> dd + + + >>> str(dd) + '2009/12/31' + + >>> dd.relevance + 'approximate' + + >>> dd = metadata.DateSingle('1805/3/12', 'uncertain') + >>> str(dd) + '1805/03/12' + ''' + def __init__(self, data: str = '', relevance='certain'): + super().__init__(relevance) + self._prepareData(data) + + def __str__(self): + return str(self._data[0]) + + def _prepareData(self, data: str): + r''' + Assume a string is supplied as argument + ''' + # here, using a list to store one object; this provides more + # compatibility w/ other formats + self._data = [] # clear list + self._data.append(Date()) + self._data[0].load(data) + + + # ----------------------------------------------------------------------------- @@ -551,14 +553,11 @@ class DateRelative(DateSingle): supported by this object: 'certain' ''' - # CLASS VARIABLES # - - isSingle = True - # INITIALIZER # - def __init__(self, data='', relevance='after'): # pylint: disable=useless-super-delegation - super().__init__(data, relevance) + def __init__(self, data='', relevance='after'): + # not a useless constructor because default value for relevance changed + super().__init__(data, relevance) # pylint: disable=useless-super-delegation # PUBLIC PROPERTIES # @@ -597,7 +596,7 @@ def relevance(self, value): # ----------------------------------------------------------------------------- -class DateBetween(DateSingle): +class DateBetween(DatePrimitive): r''' Store a relative date, sometime between two dates: @@ -610,17 +609,13 @@ class DateBetween(DateSingle): music21.exceptions21.MetadataException: Relevance value is not supported by this object: 'certain' ''' - - # CLASS VARIABLES # - - isSingle = False - # INITIALIZER # def __init__(self, data: t.Optional[t.Iterable[str]] = None, relevance='between'): if data is None: data = [] - super().__init__(data, relevance) + super().__init__(relevance) + self._prepareData(data) # SPECIAL METHODS # @@ -666,7 +661,7 @@ def relevance(self, value): # ----------------------------------------------------------------------------- -class DateSelection(DateSingle): +class DateSelection(DatePrimitive): r''' Store a selection of dates, or a collection of dates that might all be possible @@ -695,8 +690,11 @@ class DateSelection(DateSingle): def __init__(self, data: t.Optional[t.Iterable[str]] = None, - relevance='or'): # pylint: disable=useless-super-delegation - super().__init__(data, relevance) + relevance='or'): + super().__init__(relevance) + if data is None: + data = [] + self._prepareData(data) # SPECIAL METHODS # @@ -717,7 +715,7 @@ def _prepareData(self, data): for part in data: d = Date() d.load(part) - self._data.append(d) # a lost of Date objects + self._data.append(d) # a list of Date objects # can look at Date and determine overall error self._dataError.append(None) @@ -1020,8 +1018,8 @@ def __init__(self, if names: # many for n in names: self._names.append(Text(n)) - # store the nationality, if known - self._nationality = [] + # store the nationality, if known (not currently used) + self._nationality: t.List[Text] = [] self.birth: t.Optional[DateSingle] = None self.death: t.Optional[DateSingle] = None diff --git a/music21/spanner.py b/music21/spanner.py index 492bee0587..28fb4c4678 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -1223,7 +1223,6 @@ class RepeatBracket(Spanner): `ouvert` and `clos` for medieval music. However, if you use it for something like '1-3' be sure to set number properly too. - >>> m = stream.Measure() >>> sp = spanner.RepeatBracket(m, number=1) >>> sp # can be one or more measures @@ -1237,43 +1236,65 @@ class RepeatBracket(Spanner): >>> sp.number '3' - >>> sp.number = '1-3' # range of repeats + Range of repeats as string: + + >>> sp.number = '1-3' >>> sp.getNumberList() [1, 2, 3] >>> sp.number '1-3' - >>> sp.number = [2, 3] # range of repeats + Range of repeats as list: + + >>> sp.number = [2, 3] >>> sp.getNumberList() [2, 3] >>> sp.number '2, 3' - >>> sp.number = '1, 2, 3' # comma separated + Comma separated numbers: + + >>> sp.number = '1, 2, 3' >>> sp.getNumberList() [1, 2, 3] >>> sp.number '1-3' + Disjunct numbers: - >>> sp.number = '1, 2, 3, 7' # disjunct + >>> sp.number = '1, 2, 3, 7' >>> sp.getNumberList() [1, 2, 3, 7] >>> sp.number '1, 2, 3, 7' - >>> sp.overrideDisplay = '1-3, 7' # does not work for number. + Override the display. - ''' + >>> sp.overrideDisplay = '1-3, 7' + >>> sp + > + + number is not affected by display overrides: - def __init__(self, *arguments, number: t.Optional[int] = None, **keywords): + >>> sp.number + '1, 2, 3, 7' + ''' + def __init__(self, + *arguments, + number: t.Optional[int] = None, + overrideDisplay: t.Optional[str] = None, + **keywords): super().__init__(*arguments, **keywords) - self._number = None - self._numberRange = [] # store a range, inclusive of the single number assignment - self._numberSpanIsAdjacent = None - self._numberSpanIsContiguous = None - self.overrideDisplay = None + self._number: t.Optional[int] = None + # store a range, inclusive of the single number assignment + self._numberRange: t.List[int] = [] + # are there exactly two numbers that should be written as 3, 4 not 3-4. + self._numberSpanIsAdjacent: bool = False + # can we write as '3, 4' or '5-10' and not as '1, 5, 6, 11' + self._numberSpanIsContiguous: bool = True + self.overrideDisplay = overrideDisplay if number is not None: self.number = number @@ -1289,7 +1310,7 @@ def _getNumber(self): elif len(self._numberRange) == 1: return str(self._number) else: - if self._numberSpanIsContiguous is False: + if not self._numberSpanIsContiguous: return ', '.join([str(x) for x in self._numberRange]) elif self._numberSpanIsAdjacent: return f'{self._numberRange[0]}, {self._numberRange[-1]}' diff --git a/music21/stream/base.py b/music21/stream/base.py index 28d19677e3..45806ae39e 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -682,7 +682,14 @@ def __getitem__(self, return t.cast(M21ObjType, searchElements[k]) elif isinstance(k, type): - return self.recurse().getElementsByClass(k) + if issubclass(k, base.Music21Object): + return self.recurse().getElementsByClass(k) + else: + # this is explicitly NOT true, but we're pretending + # it is a Music21Object for now, because the only things returnable + # from getElementsByClass are Music21Objects that also inherit from k. + m21Type = t.cast(t.Type[M21ObjType], k) # type: ignore + return self.recurse().getElementsByClass(m21Type) elif common.isIterable(k) and all(isinstance(maybe_type, type) for maybe_type in k): return self.recurse().getElementsByClass(k) @@ -9181,7 +9188,7 @@ def bestMatch(target, divisors): useStreams = [returnStream] if recurse is True: - useStreams = returnStream.recurse(streamsOnly=True, includeSelf=True) + useStreams = list(returnStream.recurse(streamsOnly=True, includeSelf=True)) rests_lacking_durations: t.List[note.Rest] = [] for useStream in useStreams: From ba6f1e4f9154c9f37f56acac99321c7c134cd13a Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 11:00:26 -1000 Subject: [PATCH 19/35] lint --- CONTRIBUTING.md | 17 ++++--- music21/converter/subConverters.py | 2 +- music21/metadata/primitives.py | 79 +++++++++++++++++------------- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddbecaca7c..e9f64d722a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,8 @@ or Stack Overflow. ## Submitting Pull Requests ## Open an issue to propose a feature or report a bug before raising a pull request. -You can include a diff or link to your own repo if you've already started some of the work. +You can include a diff or link to your own repo if you've +already started some of the work. (Changes where the motivation is self-evident, like handling exceptions or increasing test coverage, don't need issue tickets.) @@ -75,12 +76,13 @@ be in camelCase. Conventions: - **strings MUST be 'single-quoted', but "double quotes" are allowed internally** - - this rule applies to triple quotes and doc strings also, contrary to PEP 257. - - when there is a hyphen or single quote in the string, double quotes should be used, not escaping/backslashing. - - For long streams of TinyNotation or Lilypond code, which both use single quotes to indicate octave, + - this rule applies to triple quotes and doc strings also, contrary to PEP 257. + - when there is a hyphen or single quote in the string, double quotes should be used, + not escaping/backslashing. + - For long streams of TinyNotation or Lilypond code, which both use single quotes to indicate octave, triple single quotes around the string are better than double quotes. Internal whitespace rarely matters in those formats. - - Documentation should follow quoting in American English grammar when not + - Documentation should follow quoting in American English grammar when not discussing code. So for instance, a quotation in documentation is in double quotes. - variable names: - need to be unambiguous, even if rather long. @@ -110,9 +112,10 @@ Conventions: - no more than three positional arguments (in addition to `self`) - keyword arguments should be keyword-only by using `*` to consume any other positional arguments: `def makeNoise(self, volume, *, color=noise.PINK):` - - avoid generic `**kwargs`; make keywords explicit. + - avoid generic `**keywords`; make keywords explicit. (This rule does not necessarily apply for subclass inheritance where you want to allow the superclass to add more features later. But see the Liskov principle next.) + See also https://github.com/cuthbertLab/music21/issues/1389 - prefer methods that by default do not alter the object passed in and instead return a new one. It is permitted and encouraged to have an `inPlace: bool = False` argument that allows for manipulation of the original object. When `inPlace` is True, nothing should be returned @@ -151,7 +154,7 @@ browser windows or playing music). Pull requests that increase or improve coverage of existing features are very welcome. Coverage reports can be found at [Coveralls](https://coveralls.io/github/cuthbertLab/music21). Pull requests that lower overall coverage are likely to be rejected (exception: replace -30 covered lines with 5 covered lines that do the same job more efficiently and you've +30 covered lines with 5 covered lines that do the same job more efficiently, and you've lowered the overall coverage, but that's okay). For changes to file parsing, please test both import and export (when supported for diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index 929da29ada..acd1ba20ba 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -1188,7 +1188,7 @@ def parseFile(self, in defaults.quantizationQuarterLengthDivisors. (Default: (4, 3)). ''' from music21.midi import translate as midiTranslate - midiTranslate.midiFilePathToStream(filePath, self.stream, **keywords) + midiTranslate.midiFilePathToStream(filePath, inputM21=self.stream, **keywords) def write(self, obj, diff --git a/music21/metadata/primitives.py b/music21/metadata/primitives.py index 3cb4ce887f..9f9bf78fa7 100644 --- a/music21/metadata/primitives.py +++ b/music21/metadata/primitives.py @@ -77,7 +77,7 @@ class Date(prebase.ProtoM21Object): ''' # CLASS VARIABLES # - + # TODO: these are basically Humdrum specific and should be moved there. approximateSymbols = ('~', 'x') uncertainSymbols = ('?', 'z') priorTimeSymbols = ('<', '{', '>', '}') @@ -213,7 +213,7 @@ def errorToSymbol(value): if value.lower() in Date.uncertainSymbols + ('uncertain',): return Date.uncertainSymbols[0] - def load(self, value): + def load(self, value: DateParseType): r''' Load values by string, datetime object, or Date object: @@ -421,7 +421,7 @@ def __init__(self, relevance='certain'): # store an array of values marking if date data itself # is certain, approximate, or uncertain # here, dataError is relevance - self._dataError: t.List[str] = [] + self._dataUncertainty: t.List[t.Union[str, None]] = [] self.relevance = relevance # will use property # SPECIAL METHODS # @@ -440,7 +440,7 @@ def __eq__(self, other) -> bool: ''' return (type(self) is type(other) and self._data == other._data - and self._dataError == other._dataError + and self._dataUncertainty == other._dataUncertainty and self.relevance == other.relevance) def _reprInternal(self) -> str: @@ -483,9 +483,9 @@ def relevance(self): def relevance(self, value): if value in ('certain', 'approximate', 'uncertain'): self._relevance = value - self._dataError = [] + self._dataUncertainty = [] # only here is dataError the same as relevance - self._dataError.append(value) + self._dataUncertainty.append(value) else: raise exceptions21.MetadataException( f'Relevance value is not supported by this object: {value!r}') @@ -513,20 +513,21 @@ class DateSingle(DatePrimitive): >>> str(dd) '1805/03/12' ''' - def __init__(self, data: str = '', relevance='certain'): + def __init__(self, data: DateParseType = '', relevance='certain'): super().__init__(relevance) self._prepareData(data) def __str__(self): return str(self._data[0]) - def _prepareData(self, data: str): + def _prepareData(self, data: DateParseType): r''' Assume a string is supplied as argument ''' # here, using a list to store one object; this provides more # compatibility w/ other formats self._data = [] # clear list + self._dataUncertainty = [self.relevance] self._data.append(Date()) self._data[0].load(data) @@ -535,7 +536,7 @@ def _prepareData(self, data: str): # ----------------------------------------------------------------------------- -class DateRelative(DateSingle): +class DateRelative(DatePrimitive): r''' Store a relative date, sometime `prior` or sometime `after`, `onorbefore`, or onorafter`. @@ -557,7 +558,8 @@ class DateRelative(DateSingle): def __init__(self, data='', relevance='after'): # not a useless constructor because default value for relevance changed - super().__init__(data, relevance) # pylint: disable=useless-super-delegation + super().__init__(relevance) + self._prepareData(data) # PUBLIC PROPERTIES # @@ -592,7 +594,16 @@ def relevance(self, value): f'Relevance value is not supported by this object: {value!r}') self._relevance = value.lower() - + def _prepareData(self, data: DateParseType): + r''' + Assume a string is supplied as argument + ''' + # here, using a list to store one object; this provides more + # compatibility w/ other formats + self._data = [] # clear list + self._dataUncertainty = [None] + self._data.append(Date()) + self._data[0].load(data) # ----------------------------------------------------------------------------- @@ -611,9 +622,7 @@ class DateBetween(DatePrimitive): ''' # INITIALIZER # - def __init__(self, data: t.Optional[t.Iterable[str]] = None, relevance='between'): - if data is None: - data = [] + def __init__(self, data: t.Iterable[DateParseType] = (), relevance='between'): super().__init__(relevance) self._prepareData(data) @@ -627,18 +636,18 @@ def __str__(self): # PRIVATE METHODS # - def _prepareData(self, data): + def _prepareData(self, data: t.Iterable[DateParseType]): r''' Assume a list of dates as strings is supplied as argument ''' self._data = [] - self._dataError = [] + self._dataUncertainty = [] for part in data: d = Date() d.load(part) self._data.append(d) # a list of Date objects # can look at Date and determine overall error - self._dataError.append(None) + self._dataUncertainty.append(None) # PUBLIC PROPERTIES # @@ -689,11 +698,9 @@ class DateSelection(DatePrimitive): # INITIALIZER # def __init__(self, - data: t.Optional[t.Iterable[str]] = None, + data: t.Iterable[DateParseType] = (), relevance='or'): super().__init__(relevance) - if data is None: - data = [] self._prepareData(data) # SPECIAL METHODS # @@ -706,18 +713,18 @@ def __str__(self): # PRIVATE METHODS # - def _prepareData(self, data): + def _prepareData(self, data: t.Iterable[DateParseType]): r''' Assume a list of dates as strings is supplied as argument. ''' self._data = [] - self._dataError = [] + self._dataUncertainty = [] for part in data: d = Date() d.load(part) self._data.append(d) # a list of Date objects # can look at Date and determine overall error - self._dataError.append(None) + self._dataUncertainty.append(None) # PUBLIC PROPERTIES # @@ -998,9 +1005,9 @@ class Contributor(prebase.ProtoM21Object): def __init__(self, *args, - name: t.Optional[str] = None, - names: t.Iterable[str] = (), - role: t.Optional[str] = None, + name: t.Union[str, Text, None] = None, + names: t.Iterable[t.Union[str, Text]] = (), + role: t.Union[str, Text, None] = None, birth: t.Union[None, DateSingle, str] = None, death: t.Union[None, DateSingle, str] = None, **keywords): @@ -1014,10 +1021,16 @@ def __init__(self, # language translations self._names: t.List[Text] = [] if name: # a single - self._names.append(Text(name)) + if isinstance(name, Text): + self._names.append(name) + else: + self._names.append(Text(name)) if names: # many for n in names: - self._names.append(Text(n)) + if isinstance(n, Text): + self._names.append(n) + else: + self._names.append(Text(n)) # store the nationality, if known (not currently used) self._nationality: t.List[Text] = [] @@ -1444,7 +1457,7 @@ def testDateSingle(self): self.assertEqual(str(dateSingle), '2009/12/31') self.assertEqual(len(dateSingle._data), 1) self.assertEqual(dateSingle._relevance, 'approximate') - self.assertEqual(dateSingle._dataError, ['approximate']) + self.assertEqual(dateSingle._dataUncertainty, ['approximate']) def testDateRelative(self): from music21 import metadata @@ -1453,7 +1466,7 @@ def testDateRelative(self): self.assertEqual(str(dateRelative), 'prior to 2001/12/31') self.assertEqual(dateRelative.relevance, 'prior') self.assertEqual(len(dateRelative._data), 1) - self.assertEqual(dateRelative._dataError, []) + self.assertEqual(dateRelative._dataUncertainty, []) def testDateBetween(self): from music21 import metadata @@ -1462,7 +1475,7 @@ def testDateBetween(self): ('2009/12/31', '2010/1/28')) self.assertEqual(str(dateBetween), '2009/12/31 to 2010/01/28') self.assertEqual(dateBetween.relevance, 'between') - self.assertEqual(dateBetween._dataError, [None, None]) + self.assertEqual(dateBetween._dataUncertainty, [None, None]) self.assertEqual(len(dateBetween._data), 2) def testDateSelection(self): @@ -1475,7 +1488,7 @@ def testDateSelection(self): self.assertEqual(str(dateSelection), '2009/12/31 or 2010/01/28 or 1894/01/28') self.assertEqual(dateSelection.relevance, 'or') - self.assertEqual(dateSelection._dataError, [None, None, None]) + self.assertEqual(dateSelection._dataUncertainty, [None, None, None]) self.assertEqual(len(dateSelection._data), 3) @@ -1493,7 +1506,7 @@ def testDateSelection(self): Copyright, ) - +DateParseType = t.Union[Date, datetime.datetime, str] ValueType = t.Union[DateSingle, DateRelative, DateBetween, DateSelection, Text, Contributor, Copyright, int] From 38d103a2ca3c4270ffe2dd53105541506257f586 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 11:22:53 -1000 Subject: [PATCH 20/35] more metadata DatePrimitive work --- music21/metadata/__init__.py | 16 +++++++++++----- music21/metadata/primitives.py | 3 +-- music21/metadata/properties.py | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index 266e5ce5f5..de04fb268b 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -152,7 +152,8 @@ from music21.metadata import bundles from music21.metadata import caching from music21.metadata import primitives -from music21.metadata.primitives import (Date, DateSingle, DateRelative, DateBetween, +from music21.metadata.primitives import (Date, DatePrimitive, + DateSingle, DateRelative, DateBetween, DateSelection, Text, Contributor, Creator, Imprint, Copyright, ValueType) @@ -2311,21 +2312,26 @@ def _convertValue(uniqueName: str, value: t.Any) -> ValueType: raise exceptions21.MetadataException( f'invalid type for Copyright: {type(value).__name__}') - if valueType is DateSingle: + if valueType is DatePrimitive: + # note -- this may return something other than DateSingle depending + # on the context. if isinstance(value, Text): value = str(value) - if isinstance(value, (str, datetime.datetime, Date)): + + if isinstance(value, DatePrimitive): # If you want other DateSingle-derived types (DateRelative, # DateBetween, or DateSelection), you have to create those # yourself before adding/setting them. + return value + if isinstance(value, (str, datetime.datetime, Date)): # noinspection PyBroadException # pylint: disable=bare-except try: return DateSingle(value) except: - # Couldn't convert; just return unconverted value - return originalValue + # Couldn't convert; just return a generic text. + return Text(str(originalValue)) # pylint: enable=bare-except raise exceptions21.MetadataException( diff --git a/music21/metadata/primitives.py b/music21/metadata/primitives.py index 9f9bf78fa7..51bff4a5ba 100644 --- a/music21/metadata/primitives.py +++ b/music21/metadata/primitives.py @@ -1507,8 +1507,7 @@ def testDateSelection(self): ) DateParseType = t.Union[Date, datetime.datetime, str] -ValueType = t.Union[DateSingle, DateRelative, DateBetween, DateSelection, - Text, Contributor, Copyright, int] +ValueType = t.Union[DatePrimitive, Text, Contributor, Copyright, int] if __name__ == '__main__': diff --git a/music21/metadata/properties.py b/music21/metadata/properties.py index 0ecd71e453..3f14b62897 100644 --- a/music21/metadata/properties.py +++ b/music21/metadata/properties.py @@ -14,7 +14,7 @@ import typing as t from dataclasses import dataclass -from music21.metadata.primitives import (DateSingle, Text, Contributor, Copyright, ValueType) +from music21.metadata.primitives import (DatePrimitive, Text, Contributor, Copyright, ValueType) @dataclass @@ -94,7 +94,7 @@ class PropertyDescription: uniqueName='dateAvailable', name='available', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # bibliographicCitation: A bibliographic reference for the resource. @@ -115,7 +115,7 @@ class PropertyDescription: name='created', namespace='dcterms', oldMusic21WorkId='date', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # otherDate: A point or period of time associated with an event in the lifecycle @@ -124,28 +124,28 @@ class PropertyDescription: uniqueName='otherDate', name='date', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # dateAccepted: Date of acceptance of the resource. PropertyDescription( name='dateAccepted', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # dateCopyrighted: Date of copyright of the resource. PropertyDescription( name='dateCopyrighted', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # dateSubmitted: Date of submission of the resource. PropertyDescription( name='dateSubmitted', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # description: An account of the resource. @@ -248,7 +248,7 @@ class PropertyDescription: uniqueName='dateIssued', name='issued', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # isVersionOf: A related resource of which the described resource is a @@ -282,7 +282,7 @@ class PropertyDescription: uniqueName='dateModified', name='modified', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # provenance: A statement of any changes in ownership and custody of @@ -381,7 +381,7 @@ class PropertyDescription: uniqueName='dateValid', name='valid', namespace='dcterms', - valueType=DateSingle, # including DateRelative, DateBetween, DateSelection + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # The following 'marcrel' property terms are MARC Relator terms, @@ -1009,7 +1009,7 @@ class PropertyDescription: uniqueName='dateFirstPublished', name='PDT', namespace='humdrum', - valueType=DateSingle, + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # publicationTitle: Title of the publication (volume) from which the work @@ -1121,7 +1121,7 @@ class PropertyDescription: uniqueName='electronicReleaseDate', name='YER', namespace='humdrum', - valueType=DateSingle, + valueType=DatePrimitive, # including DateSingle, DateRelative, DateBetween, DateSelection isContributor=False), # The following properties are in the music21 namespace, and are specific to From dbeac11ac5f0f252783b72c01d51cfecd04887b9 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 12:38:01 -1000 Subject: [PATCH 21/35] layout. Remove chorales warnings; separate issue --- music21/corpus/chorales.py | 13 ++- music21/layout.py | 184 ++++++++++++++++--------------------- 2 files changed, 85 insertions(+), 112 deletions(-) diff --git a/music21/corpus/chorales.py b/music21/corpus/chorales.py index 25fd3e6bec..1d0227a6b3 100644 --- a/music21/corpus/chorales.py +++ b/music21/corpus/chorales.py @@ -16,7 +16,6 @@ class for easily iterating through the chorale collection. ''' import copy import typing as t -import warnings import unittest from music21 import exceptions21 @@ -1447,7 +1446,7 @@ def _setTitleList(self, value=None): if v in self._choraleList2.byTitle: self._titleList.append(v) else: - warnings.warn(f'{v} will be skipped because it is not a recognized title') + print(f'{v} will be skipped because it is not a recognized title') if not self._titleList: self._titleList = None @@ -1478,7 +1477,7 @@ def _setNumberList(self, value): if v in self._choraleList2.byRiemenschneider: self._numberList.append(v) else: - warnings.warn( + print( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1488,7 +1487,7 @@ def _setNumberList(self, value): if v in self._choraleList2.byKalmus and v != 0: self._numberList.append(v) else: - warnings.warn( + print( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1498,7 +1497,7 @@ def _setNumberList(self, value): if v in self._choraleList2.byBWV: self._numberList.append(v) else: - warnings.warn( + print( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1508,7 +1507,7 @@ def _setNumberList(self, value): if v in self._choraleList1.byBudapest: self._numberList.append(v) else: - warnings.warn( + print( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) @@ -1518,7 +1517,7 @@ def _setNumberList(self, value): if v in self._choraleList1.byBaerenreiter: self._numberList.append(v) else: - warnings.warn( + print( f'{v} will be skipped because it is not in the numberingSystem ' + self.numberingSystem ) diff --git a/music21/layout.py b/music21/layout.py index 42d169708d..82c9075785 100644 --- a/music21/layout.py +++ b/music21/layout.py @@ -120,7 +120,7 @@ class LayoutBase(base.Music21Object): classSortOrder = -10 def __init__(self, *args, **keywords): - super().__init__() + super().__init__(**keywords) def _reprInternal(self): return '' @@ -146,41 +146,37 @@ class ScoreLayout(LayoutBase): True This object represents both and - elements in musicxml + elements in musicxml. The appearance tag is handled in the `.style` + for the stream (it was here in v7 and before, but did nothing). - TODO -- make sure that the first pageLayout and systemLayout - for each page are working together. + Note that the appearance and style elements are subject to change during + and after the v8 releases. ''' + # TODO -- make sure that the first pageLayout and systemLayout + # for each page are working together. - def __init__(self, *args, **keywords): - super().__init__() + def __init__(self, + *args, + scalingMillimeters: t.Union[int, float, None] = None, + scalingTenths: t.Union[int, float, None] = None, + musicFont: t.Optional[str] = None, + wordFont: t.Optional[str] = None, + pageLayout: t.Optional[PageLayout] = None, + systemLayout: t.Optional[SystemLayout] = None, + staffLayoutList: t.Optional[t.List[StaffLayout]] = None, + **keywords): + super().__init__(**keywords) - self.scalingMillimeters = None - self.scalingTenths = None - self.pageLayout = None - self.systemLayout = None - self.staffLayoutList = [] - self.appearance = None - self.musicFont = None - self.wordFont = None - - for key in keywords: - if key.lower() == 'scalingmillimeters': - self.scalingMillimeters = keywords[key] - elif key.lower() == 'scalingtenths': - self.scalingTenths = keywords[key] - elif key.lower() == 'pagelayout': - self.rightMargin = keywords[key] - elif key.lower() == 'systemlayout': - self.systemLayout = keywords[key] - elif key.lower() == 'stafflayout': - self.staffLayoutList = keywords[key] - elif key.lower() == 'appearance': - self.appearance = keywords[key] - elif key.lower() == 'musicfont': - self.musicFont = keywords[key] - elif key.lower() == 'wordfont': - self.wordFont = keywords[key] + self.scalingMillimeters = scalingMillimeters + self.scalingTenths = scalingTenths + self.pageLayout: t.Optional[PageLayout] = pageLayout + self.systemLayout: t.Optional[SystemLayout] = systemLayout + self.staffLayoutList: t.List[StaffLayout] = [] + self.musicFont = musicFont + self.wordFont = wordFont + + if staffLayoutList is not None: + self.staffLayoutList = staffLayoutList def tenthsToMillimeters(self, tenths): ''' @@ -232,37 +228,30 @@ class PageLayout(LayoutBase): ''' - def __init__(self, *args, **keywords): - super().__init__() + def __init__(self, + *args, + pageNumber: t.Optional[int] = None, + leftMargin: t.Union[int, float, None] = None, + rightMargin: t.Union[int, float, None] = None, + topMargin: t.Union[int, float, None] = None, + bottomMargin: t.Union[int, float, None] = None, + pageHeight: t.Union[int, float, None] = None, + pageWidth: t.Union[int, float, None] = None, + isNew: t.Union[bool, None] = None, + **keywords): + super().__init__(**keywords) - self.pageNumber = None - self.leftMargin = None - self.rightMargin = None - self.topMargin = None - self.bottomMargin = None - self.pageHeight = None - self.pageWidth = None + self.pageNumber = pageNumber + self.leftMargin = leftMargin + self.rightMargin = rightMargin + self.topMargin = topMargin + self.bottomMargin = bottomMargin + self.pageHeight = pageHeight + self.pageWidth = pageWidth # store if this is the start of a new page - self.isNew = None - - for key in keywords: - if key.lower() == 'pagenumber': - self.pageNumber = keywords[key] - elif key.lower() == 'leftmargin': - self.leftMargin = keywords[key] - elif key.lower() == 'rightmargin': - self.rightMargin = keywords[key] - elif key.lower() == 'topmargin': - self.topMargin = keywords[key] - elif key.lower() == 'bottommargin': - self.bottomMargin = keywords[key] - elif key.lower() == 'pageheight': - self.pageHeight = keywords[key] - elif key.lower() == 'pagewidth': - self.pageWidth = keywords[key] - elif key.lower() == 'isnew': - self.isNew = keywords[key] + self.isNew = isNew + # ------------------------------------------------------------------------------ @@ -289,32 +278,26 @@ class SystemLayout(LayoutBase): True ''' - def __init__(self, *args, **keywords): - super().__init__() + def __init__(self, + *args, + leftMargin: t.Union[int, float, None] = None, + rightMargin: t.Union[int, float, None] = None, + distance: t.Union[int, float, None] = None, + topDistance: t.Union[int, float, None] = None, + isNew: t.Union[bool, None] = None, + **keywords): + super().__init__(**keywords) - self.leftMargin = None - self.rightMargin = None + self.leftMargin = leftMargin + self.rightMargin = rightMargin # no top or bottom margins # this is probably the distance between adjacent systems - self.distance = None - self.topDistance = None + self.distance = distance + self.topDistance = topDistance # store if this is the start of a new system - self.isNew = None - - for key in keywords: - if key.lower() == 'leftmargin': - self.leftMargin = keywords[key] - elif key.lower() == 'rightmargin': - self.rightMargin = keywords[key] - - elif key.lower() == 'distance': - self.distance = keywords[key] - elif key.lower() == 'topdistance': - self.topDistance = keywords[key] - elif key.lower() == 'isnew': - self.isNew = keywords[key] + self.isNew = isNew class StaffLayout(LayoutBase): @@ -379,33 +362,24 @@ class StaffLayout(LayoutBase): ''', } - def __init__(self, *args, **keywords): - super().__init__() + def __init__(self, + *args, + distance: t.Union[int, float, None] = None, + staffNumber: t.Union[int, float, None] = None, + staffSize: t.Union[int, float, None] = None, + staffLines: t.Optional[int] = None, + hidden: t.Union[bool, None] = None, + staffType: StaffType = StaffType.REGULAR, + **keywords): + super().__init__(**keywords) # this is the distance between adjacent staves - self.distance = None - self.staffNumber = None - self.staffSize = None - self.staffLines = None - self.hidden = None # True = hidden; False = shown; None = inherit - self.staffType: StaffType = StaffType.REGULAR - - for key in keywords: - keyLower = key.lower() - if keyLower == 'distance': - self.distance = keywords[key] - elif keyLower == 'staffnumber': - self.staffNumber = keywords[key] - elif keyLower == 'staffsize': - if keywords[key] is not None: - self.staffSize = float(keywords[key]) - elif keyLower == 'stafflines': - self.staffLines = keywords[key] - elif keyLower == 'hidden': - if keywords[key] is not False and keywords[key] is not None: - self.hidden = True - elif keyLower == 'staffType': - self.staffType = keywords[key] + self.distance = distance + self.staffNumber = staffNumber + self.staffSize = staffSize + self.staffLines = staffLines + self.hidden = hidden # True = hidden; False = shown; None = inherit + self.staffType: StaffType = staffType def _reprInternal(self): return (f'distance {self.distance!r}, staffNumber {self.staffNumber!r}, ' From 29841b673e04fbb176fb56db71e0f16a2289356c Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 12:45:20 -1000 Subject: [PATCH 22/35] layout import annotations --- music21/converter/subConverters.py | 10 +++++++--- music21/layout.py | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index acd1ba20ba..5b75f4235b 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -269,10 +269,10 @@ def write(self, Calls .writeDataStream on the repr of obj, and returns the fp returned by it. ''' dataStr = repr(obj) - fp = self.writeDataStream(fp, dataStr) + fp = self.writeDataStream(fp, dataStr, **keywords) return fp - def writeDataStream(self, fp, dataStr): # pragma: no cover + def writeDataStream(self, fp, dataStr, **keywords): # pragma: no cover ''' Writes the data stream to `fp` or to a temporary file and returns the filename written. @@ -1023,7 +1023,10 @@ def runThroughMusescore(self, return pathlib.Path(fpOut) # common.cropImageFromPath(fp) - def writeDataStream(self, fp, dataBytes: bytes) -> pathlib.Path: # pragma: no cover + def writeDataStream(self, + fp, + dataBytes: bytes, + **keywords) -> pathlib.Path: # pragma: no cover # noinspection PyShadowingNames ''' Writes `dataBytes` to `fp`. @@ -1068,6 +1071,7 @@ def write(self, fmt, fp=None, subformats=None, + *, makeNotation=True, compress: t.Optional[bool] = None, **keywords): diff --git a/music21/layout.py b/music21/layout.py index 82c9075785..ba63153a2f 100644 --- a/music21/layout.py +++ b/music21/layout.py @@ -85,6 +85,7 @@ SmartScore Pro tends to produce very good MusicXML layout data. ''' +from __future__ import annotations # may need to have an object to convert between size units import copy @@ -330,7 +331,8 @@ class StaffLayout(LayoutBase): 5 staffSize is a percentage of the base staff size, so - this defines a staff 13% larger than normal. + this defines a staff 13% larger than normal. Note that it is always converted to + a floating point number. >>> sl.staffSize 113.0 @@ -376,7 +378,7 @@ def __init__(self, # this is the distance between adjacent staves self.distance = distance self.staffNumber = staffNumber - self.staffSize = staffSize + self.staffSize: t.Optional[float] = None if staffSize is None else float(staffSize) self.staffLines = staffLines self.hidden = hidden # True = hidden; False = shown; None = inherit self.staffType: StaffType = staffType From 5376b5af8e96846615f91a04035dc73019eb54fb Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 12:52:18 -1000 Subject: [PATCH 23/35] Remove *arguments where not used These just mask errors and push debugging down the road --- music21/base.py | 2 -- music21/duration.py | 12 ++++++++---- music21/note.py | 3 --- music21/serial.py | 14 -------------- 4 files changed, 8 insertions(+), 23 deletions(-) diff --git a/music21/base.py b/music21/base.py index a35236534d..27d80fe91e 100644 --- a/music21/base.py +++ b/music21/base.py @@ -344,7 +344,6 @@ class Music21Object(prebase.ProtoM21Object): } def __init__(self, - *arguments, id: t.Union[str, int, None] = None, # pylint: disable=redefined-builtin groups: t.Optional[Groups] = None, sites: t.Optional[Sites] = None, @@ -1803,7 +1802,6 @@ def contextSites( offset=0.0, recurseType='elementsFirst') - >>> partIterator = c.parts >>> m3 = partIterator[1].measure(3) >>> for csTuple in m3.contextSites(): diff --git a/music21/duration.py b/music21/duration.py index 2a2ced0316..2f017e9f54 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -3089,8 +3089,10 @@ class GraceDuration(Duration): ) # INITIALIZER # - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, + typeOrDuration: t.Union[str, OffsetQLIn, DurationTuple, None] = None, + **keywords): + super().__init__(typeOrDuration, **keywords) # update components to derive types; this sets ql, but this # will later be removed if self._componentsNeedUpdating: @@ -3154,8 +3156,10 @@ class AppoggiaturaDuration(GraceDuration): # INITIALIZER # - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, + typeOrDuration: t.Union[str, OffsetQLIn, DurationTuple, None] = None, + **keywords): + super().__init__(typeOrDuration, **keywords) self.slash = False # can be True, False, or None; make None go to True? self.makeTime = True diff --git a/music21/note.py b/music21/note.py index b56cbdf856..fbbe208063 100644 --- a/music21/note.py +++ b/music21/note.py @@ -542,7 +542,6 @@ class GeneralNote(base.Music21Object): objects directly, and not use this underlying structure. - >>> gn = note.GeneralNote(type='16th', dots=2) >>> gn.quarterLength 0.4375 @@ -566,7 +565,6 @@ class GeneralNote(base.Music21Object): } def __init__(self, - *arguments, duration: t.Optional[Duration] = None, lyric: t.Union[None, str, Lyric] = None, **keywords @@ -973,7 +971,6 @@ class NotRest(GeneralNote): } def __init__(self, - *arguments, beams: t.Optional[beam.Beams] = None, **keywords): super().__init__(**keywords) diff --git a/music21/serial.py b/music21/serial.py index 3a2f75794f..4f0f2e818a 100644 --- a/music21/serial.py +++ b/music21/serial.py @@ -666,23 +666,9 @@ class TwelveToneRow(ToneRow): ''' A Stream representation of a twelve-tone row, capable of producing a 12-tone matrix. ''' - # row = None - - # _DOC_ATTR: t.Dict[str, str] = { - # 'row': 'A list representing the pitch class values of the row.', - # } - _DOC_ORDER = ['matrix', 'isAllInterval', 'getLinkClassification', 'isLinkChord', 'areCombinatorial'] - # def __init__(self, *arguments, **keywords): - # super().__init__(*arguments, **keywords) - # # environLocal.printDebug(['TwelveToneRow.__init__: length of elements', len(self)]) - # - # # if self.row != None: - # # for pc in self.row: - # # self.append(pitch.Pitch(pc)) - def matrix(self): # noinspection PyShadowingNames ''' From 5327e2663096f4aa6582d7f729e81636238405e4 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 13:03:35 -1000 Subject: [PATCH 24/35] fix substitution principle; sigh. --- music21/converter/subConverters.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index 5b75f4235b..a3c0c2afce 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -272,10 +272,13 @@ def write(self, fp = self.writeDataStream(fp, dataStr, **keywords) return fp - def writeDataStream(self, fp, dataStr, **keywords): # pragma: no cover + def writeDataStream(self, + fp, + dataStr: t.Union[str, bytes], + **keywords) -> pathlib.Path: # pragma: no cover ''' Writes the data stream to `fp` or to a temporary file and returns the - filename written. + Path object of the filename written. ''' if fp is None: fp = self.getTemporaryFile() @@ -1025,11 +1028,11 @@ def runThroughMusescore(self, def writeDataStream(self, fp, - dataBytes: bytes, + dataStr: t.Union[str, bytes], **keywords) -> pathlib.Path: # pragma: no cover # noinspection PyShadowingNames ''' - Writes `dataBytes` to `fp`. + Writes `dataStr` which must be bytes to `fp`. Adds `.musicxml` suffix to `fp` if it does not already contain some suffix. Changed in v7 -- returns a pathlib.Path @@ -1051,20 +1054,25 @@ def writeDataStream(self, True >>> os.remove(outFp) ''' + if not isinstance(dataStr, bytes): + raise ValueError(f'{dataStr} must be bytes to write to this format') + dataBytes = dataStr + + fpPath: pathlib.Path if fp is None: - fp = self.getTemporaryFile() + fpPath = self.getTemporaryFile() else: - fp = common.cleanpath(fp, returnPathlib=True) + fpPath = common.cleanpath(fp, returnPathlib=True) - if not fp.suffix or fp.suffix == '.mxl': - fp = fp.with_suffix('.musicxml') + if not fpPath.suffix or fpPath.suffix == '.mxl': + fpPath = fpPath.with_suffix('.musicxml') writeFlags = 'wb' - with open(fp, writeFlags) as f: + with open(fpPath, writeFlags) as f: f.write(dataBytes) # type: ignore - return fp + return fpPath def write(self, obj: music21.Music21Object, From d2482fc130fe2bb8623a009232a022d2ca158690 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 13:10:44 -1000 Subject: [PATCH 25/35] Remove more *args --- music21/analysis/discrete.py | 1 - music21/braille/segment.py | 7 +++---- music21/converter/__init__.py | 2 +- music21/note.py | 3 +-- music21/serial.py | 9 +++++---- music21/stream/base.py | 6 +++--- music21/stream/core.py | 4 ++-- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/music21/analysis/discrete.py b/music21/analysis/discrete.py index 52e104b954..ca377ad708 100644 --- a/music21/analysis/discrete.py +++ b/music21/analysis/discrete.py @@ -1287,7 +1287,6 @@ def getSolution(self, sStream): def analyzeStream( streamObj: 'music21.stream.Stream', method: str, - *args, **keywords ): ''' diff --git a/music21/braille/segment.py b/music21/braille/segment.py index dc2ee44418..8690c6c151 100644 --- a/music21/braille/segment.py +++ b/music21/braille/segment.py @@ -167,9 +167,9 @@ class BrailleElementGrouping(ProtoM21Object): 'withHyphen': 'If True, this grouping will end with a music hyphen.', 'numRepeats': 'The number of times this grouping is repeated.' } - def __init__(self, *args): + def __init__(self, *listElements): ''' - A BrailleElementGrouping is a superclass of list of objects which should be displayed + A BrailleElementGrouping mimics a list of objects which should be displayed without a space in braille. >>> from music21.braille import segment @@ -209,8 +209,7 @@ def __init__(self, *args): >>> bg.numRepeats 0 ''' - super().__init__() - self.internalList = list(*args) + self.internalList = list(*listElement) setGroupingGlobals() self.keySignature = GROUPING_GLOBALS['keySignature'] diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py index cec88f40b3..b548754dfb 100644 --- a/music21/converter/__init__.py +++ b/music21/converter/__init__.py @@ -1192,7 +1192,7 @@ def parseURL(url, def parse(value: t.Union[bundles.MetadataEntry, bytes, str, pathlib.Path], - *args, + *, forceSource: bool = False, number: t.Optional[int] = None, format: t.Optional[str] = None, # pylint: disable=redefined-builtin diff --git a/music21/note.py b/music21/note.py index fbbe208063..1d5c17e30a 100644 --- a/music21/note.py +++ b/music21/note.py @@ -1952,7 +1952,7 @@ class Rest(GeneralNote): ''', } - def __init__(self, *arguments, **keywords): + def __init__(self, **keywords): super().__init__(**keywords) self.stepShift = 0 # display line self.fullMeasure = 'auto' # see docs; True, False, 'always', @@ -1973,7 +1973,6 @@ def __eq__(self, other): A Music21 rest is equal to another object if that object is also a rest which has the same duration. - >>> r1 = note.Rest() >>> r2 = note.Rest() >>> r1 == r2 diff --git a/music21/serial.py b/music21/serial.py index 4f0f2e818a..9aefc2d5aa 100644 --- a/music21/serial.py +++ b/music21/serial.py @@ -290,8 +290,8 @@ class ToneRow(stream.Stream): 'zeroCenteredTransformation', 'originalCenteredTransformation', 'findZeroCenteredTransformations', 'findOriginalCenteredTransformations'] - def __init__(self, row=None, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, row=None, **keywords): + super().__init__(**keywords) if row is not None: self.row = row else: @@ -1096,8 +1096,9 @@ def __init__(self, composer: t.Union[None, str] = None, opus: t.Union[None, str] = None, title: t.Union[None, str] = None, - row=None): - super().__init__(row) + row=None, + **keywords): + super().__init__(row, **keywords) self.composer = composer self.opus = opus self.title = title diff --git a/music21/stream/base.py b/music21/stream/base.py index 45806ae39e..3b1ec2a78e 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -245,7 +245,7 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]): be allowed, because craziness and givenElements are required:: class CrazyStream(Stream): - def __init__(self, givenElements, craziness, *args, **keywords): + def __init__(self, givenElements, craziness, **keywords): ... New in v.7 -- smart appending @@ -312,11 +312,11 @@ def __init__(self, givenElements: t.Union[None, base.Music21Object, t.Sequence[base.Music21Object]] = None, - *arguments, + *, appendOrInsert: t.Literal['append', 'insert', 'offsets'] = 'offsets', **keywords): # restrictClass: t.Type[M21ObjType] = base.Music21Object, - super().__init__(self, *arguments, **keywords) + super().__init__(self, **keywords) self.streamStatus = streamStatus.StreamStatus(self) self._unlinkedDuration = None diff --git a/music21/stream/core.py b/music21/stream/core.py index 7df949c0fa..cd2e1bae3d 100644 --- a/music21/stream/core.py +++ b/music21/stream/core.py @@ -43,8 +43,8 @@ class StreamCore(Music21Object): Core aspects of a Stream's behavior. Any of these can change at any time. Users are encouraged only to create stream.Stream objects. ''' - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, **keywords): + super().__init__(**keywords) # hugely important -- keeps track of where the _elements are # the _offsetDict is a dictionary where id(element) is the # index and the value is a tuple of offset and element. From ee330bafac4fbab43a0e041ae4cd5457754f4ecc Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 13:38:57 -1000 Subject: [PATCH 26/35] fix metadata tests pinned to 8.0.0a9 --- music21/__init__.py | 4 +- music21/_version.py | 2 +- music21/base.py | 5 +- music21/metadata/__init__.py | 58 +++---------------- music21/metadata/primitives.py | 9 ++- music21/test/__init__.py | 4 ++ music21/test/commonTest.py | 3 + .../testMetadata.py => test/test_metadata.py} | 55 +++++++----------- 8 files changed, 50 insertions(+), 90 deletions(-) rename music21/{metadata/testMetadata.py => test/test_metadata.py} (95%) diff --git a/music21/__init__.py b/music21/__init__.py index 78bf6bd3c0..08597b8f82 100644 --- a/music21/__init__.py +++ b/music21/__init__.py @@ -79,6 +79,9 @@ __all__ = [ + # testing routines + 'mainTest', + 'prebase', # before all 'base', # top... 'sites', # important @@ -189,7 +192,6 @@ __version__ = VERSION_STR -# legacy reason why it's here... from music21.test.testRunner import mainTest # noqa: E402 # ----------------------------------------------------------------------------- diff --git a/music21/_version.py b/music21/_version.py index 093ae8357c..5a22573d8e 100644 --- a/music21/_version.py +++ b/music21/_version.py @@ -41,7 +41,7 @@ Changing this number invalidates old pickles -- do it if the old pickles create a problem. ''' -__version_info__ = (8, 0, 0, 'a9') # can be 3-tuple or 4+-tuple: (7, 0, 5, 'a2') +__version_info__ = (8, 0, 0, 'a10') # can be 3-tuple or 4+-tuple: (7, 0, 5, 'a2') v = '.'.join(str(x) for x in __version_info__[0:3]) if len(__version_info__) > 3 and __version_info__[3]: # type: ignore diff --git a/music21/base.py b/music21/base.py index a35236534d..05209e07b7 100644 --- a/music21/base.py +++ b/music21/base.py @@ -28,7 +28,7 @@ >>> music21.VERSION_STR -'8.0.0a9' +'8.0.0a10' Alternatively, after doing a complete import, these classes are available under the module "base": @@ -68,8 +68,6 @@ from music21 import tie from music21 import exceptions21 from music21._version import __version__, __version_info__ -from music21.test.testRunner import mainTest - _M21T = t.TypeVar('_M21T', bound='music21.base.Music21Object') @@ -93,7 +91,6 @@ 'VERSION', 'VERSION_STR', - 'mainTest', ] # N.B. for PyDev "all" import working, we need to list this diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index de04fb268b..b51efb8dfb 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -156,8 +156,6 @@ DateSingle, DateRelative, DateBetween, DateSelection, Text, Contributor, Creator, Imprint, Copyright, ValueType) - -from music21.metadata import testMetadata # ----------------------------------------------------------------------------- __all__ = [ @@ -676,15 +674,12 @@ def all( ('software', 'music21 v...')) >>> c.metadata.all(returnPrimitives=True, returnSorted=False) - (('software', ), - ('software', ), - ('software', ), + (('software', ), + ('software', ), + ('software', ), ('movementName', <...Text Sonata da Chiesa, No. I (opus 3, no. 1)>), ('composer', ), - ('arranger', ), - ('copyright', <...Copyright © 2014, Creative Commons License (CC-BY)>), - ('filePath', <...Text ...corelli/opus3no1/1grave.xml>), - ('fileFormat', ), + ... ('dateCreated', ), ('localeOfComposition', )) @@ -2623,20 +2618,7 @@ def all( AmbitusShort(semitones=48, diatonic='P1', pitchLowest='C2', pitchHighest='C6')), ('arranger', 'Michael Scott Cuthbert'), ('composer', 'Arcangelo Corelli'), - ('copyright', '© 2014, Creative Commons License (CC-BY)'), - ('fileFormat', 'musicxml'), - ('filePath', '...corpus/corelli/opus3no1/1grave.xml'), - ('keySignatureFirst', -1), - ('keySignatures', [-1]), - ('movementName', 'Sonata da Chiesa, No. I (opus 3, no. 1)'), - ('noteCount', 259), - ('numberOfParts', 3), - ('pitchHighest', 'C6'), - ('pitchLowest', 'C2'), - ('quarterLength', 76.0), - ('software', 'Dolet Light for Finale 2014'), - ('software', 'Finale 2014 for Mac'), - ('software', 'music21 v.8.0.0a9'), + ... ('sourcePath', 'corelli/opus3no1/1grave.xml'), ('tempoFirst', None), ('tempos', []), ('timeSignatureFirst', '4/4'), @@ -2658,30 +2640,12 @@ def all( ('timeSignatures', ['4/4'])) >>> rmd.all(returnPrimitives=True, returnSorted=False) - (('software', ), + (('software', ), ('software', ), ('software', ), ('movementName', <...Text Sonata da Chiesa, No. I (opus 3, no. 1)>), ('composer', ), - ('arranger', ), - ('copyright', <...Copyright © 2014, Creative Commons License (CC-BY)>), - ('filePath', <...Text ...corelli/opus3no1/1grave.xml>), - ('fileFormat', ), - ('dateCreated', ), - ('localeOfComposition', ), - ('ambitus', - AmbitusShort(semitones=48, diatonic='P1', pitchLowest='C2', pitchHighest='C6')), - ('keySignatureFirst', -1), - ('keySignatures', [-1]), - ('noteCount', 259), - ('numberOfParts', 3), - ('pitchHighest', 'C6'), - ('pitchLowest', 'C2'), - ('quarterLength', 76.0), - ('sourcePath', 'corelli/opus3no1/1grave.xml'), - ('tempoFirst', None), - ('tempos', []), - ('timeSignatureFirst', '4/4'), + ... ('timeSignatures', ['4/4'])) >>> rmd.all(skipNonContributors=True, returnPrimitives=True, returnSorted=True) @@ -2747,18 +2711,14 @@ def _isStandardUniqueName(self, uniqueName: str) -> bool: return False # ----------------------------------------------------------------------------- +# tests are in test/test_metadata -class Test(unittest.TestCase): - pass - - -# ----------------------------------------------------------------------------- _DOC_ORDER: t.List[type] = [] if __name__ == '__main__': import music21 - music21.mainTest(Test) + music21.mainTest() # ----------------------------------------------------------------------------- diff --git a/music21/metadata/primitives.py b/music21/metadata/primitives.py index 51bff4a5ba..d3bbc1deee 100644 --- a/music21/metadata/primitives.py +++ b/music21/metadata/primitives.py @@ -447,7 +447,12 @@ def _reprInternal(self) -> str: return str(self) def __str__(self): - return str([str(d) for d in self._data]) + if len(self._data) == 0: + return '' + elif len(self._data) == 1: + return str(self._data[0]) + else: + return str([str(d) for d in self._data]) # PUBLIC PROPERTIES # @@ -1466,7 +1471,7 @@ def testDateRelative(self): self.assertEqual(str(dateRelative), 'prior to 2001/12/31') self.assertEqual(dateRelative.relevance, 'prior') self.assertEqual(len(dateRelative._data), 1) - self.assertEqual(dateRelative._dataUncertainty, []) + self.assertEqual(dateRelative._dataUncertainty, [None]) def testDateBetween(self): from music21 import metadata diff --git a/music21/test/__init__.py b/music21/test/__init__.py index 9853dfa78f..b369f97e53 100644 --- a/music21/test/__init__.py +++ b/music21/test/__init__.py @@ -2,3 +2,7 @@ _DOC_IGNORE_MODULE_OR_PACKAGE = True +from music21.test import test_base as base +from music21.test import test_metadata as metadata +from music21.test import test_pitch as pitch +from music21.test import test_repeat as repeat diff --git a/music21/test/commonTest.py b/music21/test/commonTest.py index 2da4530d37..cdda7b748a 100644 --- a/music21/test/commonTest.py +++ b/music21/test/commonTest.py @@ -228,6 +228,9 @@ def __init__(self, useExtended=False, autoWalk=True): 'musicxml/xmlToM21', 'romanText/translate', + 'corpus/testCorpus', + 'audioSearch/transcriber', + 'audioSearch/__init__', 'alpha/theoryAnalysis/theoryAnalyzer', ] diff --git a/music21/metadata/testMetadata.py b/music21/test/test_metadata.py similarity index 95% rename from music21/metadata/testMetadata.py rename to music21/test/test_metadata.py index 050ee68896..b27e31063c 100644 --- a/music21/metadata/testMetadata.py +++ b/music21/test/test_metadata.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- import re import unittest -from typing import Type +import typing as t + +from music21 import converter +from music21 import corpus from music21 import metadata +from music21.musicxml import testFiles as mTF class Test(unittest.TestCase): @@ -10,9 +14,6 @@ class Test(unittest.TestCase): maxDiff = None def testMetadataLoadCorpus(self): - from music21 import converter - from music21.musicxml import testFiles as mTF - c = converter.parse(mTF.mozartTrioK581Excerpt) md = c.metadata @@ -45,9 +46,6 @@ def testMetadataLoadCorpus(self): ) def testMetadataLoadCorpusBackwardCompatible(self): - from music21 import converter - from music21.musicxml import testFiles as mTF - c = converter.parse(mTF.mozartTrioK581Excerpt) md = c.metadata @@ -81,9 +79,6 @@ def testMetadataLoadCorpusBackwardCompatible(self): ) def testJSONSerializationMetadata(self): - from music21 import converter - from music21.musicxml import testFiles as mTF - md = metadata.Metadata( title='Concerto in F', date='2010', @@ -120,8 +115,6 @@ def testJSONSerializationMetadata(self): ) def testRichMetadata01(self): - from music21 import corpus - score = corpus.parse('jactatur') self.assertEqual(score.metadata.composer, 'Johannes Ciconia') @@ -151,8 +144,6 @@ def testRichMetadata01(self): self.assertEqual(str(richMetadata.timeSignatureFirst), '4/4') def testWorkIds(self): - from music21 import corpus - opus = corpus.parse('essenFolksong/teste') self.assertEqual(len(opus.scores), 8) @@ -172,7 +163,6 @@ def testWorkIds(self): ) def testMetadataSearch(self): - from music21 import corpus score = corpus.parse('ciconia') self.assertEqual( score.metadata.search( @@ -196,8 +186,6 @@ def testMetadataSearch(self): ) def testRichMetadata02(self): - from music21 import corpus - score = corpus.parse('bwv66.6') richMetadata = metadata.RichMetadata() richMetadata.merge(score.metadata) @@ -210,7 +198,7 @@ def checkUniqueNamedItem( uniqueName: str, namespaceName: str, contributorRole: str = None, - valueType: Type = metadata.Text): + valueType: t.Type = metadata.Text): if ':' not in namespaceName: # It's just the namespace because name == uniqueName, @@ -240,7 +228,7 @@ def checkUniqueNamedItem( itemTuple = md[namespaceName] self.assertEqual(itemTuple, tuple()) - if valueType is metadata.DateSingle: + if valueType is metadata.DatePrimitive: md[namespaceName] = ['1978/6/11'] self.assertEqual( getattr(md, uniqueName), @@ -313,9 +301,10 @@ def checkUniqueNamedItem( self.fail('internal test error: invalid valueType') - if valueType is metadata.DateSingle: - md.add(namespaceName, [metadata.DateBetween(['1978', '1980']), - metadata.DateSingle('1979/6/11/4:50:32')]) + if valueType is metadata.DatePrimitive: + md.add(namespaceName, + [metadata.DateBetween(['1978', '1980']), + metadata.DateSingle('1979/6/11/4:50:32')]) self.assertEqual( getattr(md, uniqueName), '1979/06/11, 1978/--/-- to 1980/--/--, 1979/06/11/04/50/032.00' @@ -448,15 +437,15 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem( 'dateAvailable', 'dcterms:available', - valueType=metadata.DateSingle + valueType=metadata.DatePrimitive ) self.checkUniqueNamedItem('bibliographicCitation', 'dcterms') self.checkUniqueNamedItem('conformsTo', 'dcterms') - self.checkUniqueNamedItem('dateCreated', 'dcterms:created', valueType=metadata.DateSingle) - self.checkUniqueNamedItem('otherDate', 'dcterms:date', valueType=metadata.DateSingle) - self.checkUniqueNamedItem('dateAccepted', 'dcterms', valueType=metadata.DateSingle) - self.checkUniqueNamedItem('dateCopyrighted', 'dcterms', valueType=metadata.DateSingle) - self.checkUniqueNamedItem('dateSubmitted', 'dcterms', valueType=metadata.DateSingle) + self.checkUniqueNamedItem('dateCreated', 'dcterms:created', valueType=metadata.DatePrimitive) + self.checkUniqueNamedItem('otherDate', 'dcterms:date', valueType=metadata.DatePrimitive) + self.checkUniqueNamedItem('dateAccepted', 'dcterms', valueType=metadata.DatePrimitive) + self.checkUniqueNamedItem('dateCopyrighted', 'dcterms', valueType=metadata.DatePrimitive) + self.checkUniqueNamedItem('dateSubmitted', 'dcterms', valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('description', 'dcterms') self.checkUniqueNamedItem('educationLevel', 'dcterms') self.checkUniqueNamedItem('extent', 'dcterms') @@ -471,7 +460,7 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem('isReferencedBy', 'dcterms') self.checkUniqueNamedItem('isReplacedBy', 'dcterms') self.checkUniqueNamedItem('isRequiredBy', 'dcterms') - self.checkUniqueNamedItem('dateIssued', 'dcterms:issued', valueType=metadata.DateSingle) + self.checkUniqueNamedItem('dateIssued', 'dcterms:issued', valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('isVersionOf', 'dcterms') self.checkUniqueNamedItem('language', 'dcterms') self.checkUniqueNamedItem('license', 'dcterms') @@ -479,7 +468,7 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem( 'dateModified', 'dcterms:modified', - valueType=metadata.DateSingle + valueType=metadata.DatePrimitive ) self.checkUniqueNamedItem('provenance', 'dcterms') self.checkUniqueNamedItem('publisher', 'dcterms', valueType=metadata.Contributor) @@ -494,7 +483,7 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem('tableOfContents', 'dcterms') self.checkUniqueNamedItem('title', 'dcterms') self.checkUniqueNamedItem('type', 'dcterms') - self.checkUniqueNamedItem('dateValid', 'dcterms:valid', valueType=metadata.DateSingle) + self.checkUniqueNamedItem('dateValid', 'dcterms:valid', valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('adapter', 'marcrel:ADP') self.checkUniqueNamedItem('analyst', 'marcrel:ANL') self.checkUniqueNamedItem('annotator', 'marcrel:ANN') @@ -588,7 +577,7 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem( 'dateFirstPublished', 'humdrum:PDT', - valueType=metadata.DateSingle + valueType=metadata.DatePrimitive ) self.checkUniqueNamedItem('publicationTitle', 'humdrum:PTL') self.checkUniqueNamedItem('placeFirstPublished', 'humdrum:PPP') @@ -626,7 +615,7 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem( 'electronicReleaseDate', 'humdrum:YER', - valueType=metadata.DateSingle + valueType=metadata.DatePrimitive ) self.checkUniqueNamedItem( 'fileFormat', From 20087f0a7cfb97529692076b2efb74b6a23b36ac Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 13:44:35 -1000 Subject: [PATCH 27/35] spanner star args --- music21/spanner.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/music21/spanner.py b/music21/spanner.py index 28fb4c4678..dd15ab4a7f 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -205,8 +205,11 @@ class Spanner(base.Music21Object): >>> sp1.completeStatus = True ''' - def __init__(self, *arguments, **keywords): - super().__init__() + def __init__(self, + *spannedElements: t.Union[base.Music21Object, + t.Sequence[base.Music21Object]], + **keywords): + super().__init__(**keywords) # store a Stream inside of Spanner from music21 import stream @@ -224,18 +227,13 @@ def __init__(self, *arguments, **keywords): self.spannerStorage.autoSort = False # add arguments as a list or single item - proc = [] - for arg in arguments: - if common.isListLike(arg): - proc += arg + proc: t.List[base.Music21Object] = [] + for spannedElement in spannedElements: + if isinstance(spannedElement, base.Music21Object): + proc.append(spannedElement) else: - proc.append(arg) - self.addSpannedElements(proc) - # if len(arguments) > 1: - # self.spannerStorage.append(arguments) - # elif len(arguments) == 1: # assume a list is first arg - # self.spannerStorage.append(c) - + proc += spannedElement + self.addSpannedElements(proc) # parameters that spanners need in loading and processing # local id is the id for the local area; used by musicxml self.idLocal = None @@ -618,7 +616,7 @@ class SpannerBundle(prebase.ProtoM21Object): ''' An advanced utility object for collecting and processing collections of Spanner objects. This is necessary because - often processing routines that happen at many different + often processing routines that happen at many levels still need access to the same collection of spanners. Because SpannerBundles are so commonly used with @@ -928,7 +926,7 @@ def setIdLocalByClass(self, className, maxId=6): The `maxId` parameter sets the largest number that is available for this class. In MusicXML it is 6. - Currently this method just iterates over the spanners of this class + Currently, this method just iterates over the spanners of this class and counts the number from 1-6 and then recycles numbers. It does not check whether more than 6 overlapping spanners of the same type exist, nor does it reset the count to 1 after all spanners of that @@ -1846,13 +1844,13 @@ def __init__(self, **keywords): super().__init__(*arguments, **keywords) - GLISSANDO_DEFAULT_LINETYPE = 'wavy' - self._lineType = GLISSANDO_DEFAULT_LINETYPE + GLISSANDO_DEFAULT_LINE_TYPE = 'wavy' + self._lineType = GLISSANDO_DEFAULT_LINE_TYPE self._slideType = 'chromatic' self.label = None - if lineType != GLISSANDO_DEFAULT_LINETYPE: + if lineType != GLISSANDO_DEFAULT_LINE_TYPE: self.lineType = lineType # use property if label is not None: self.label = label # use property From 4cb322858407fb5069263da3fe3185456d762562 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 13:52:58 -1000 Subject: [PATCH 28/35] lint and flake --- music21/metadata/__init__.py | 2 +- music21/test/__init__.py | 6 ++---- music21/test/test_metadata.py | 15 ++++++++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index b51efb8dfb..ba18e56c7e 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -2710,9 +2710,9 @@ def _isStandardUniqueName(self, uniqueName: str) -> bool: return True return False + # ----------------------------------------------------------------------------- # tests are in test/test_metadata - _DOC_ORDER: t.List[type] = [] diff --git a/music21/test/__init__.py b/music21/test/__init__.py index b369f97e53..47e5c35662 100644 --- a/music21/test/__init__.py +++ b/music21/test/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- - -_DOC_IGNORE_MODULE_OR_PACKAGE = True - from music21.test import test_base as base from music21.test import test_metadata as metadata from music21.test import test_pitch as pitch from music21.test import test_repeat as repeat + +_DOC_IGNORE_MODULE_OR_PACKAGE = True diff --git a/music21/test/test_metadata.py b/music21/test/test_metadata.py index b27e31063c..22364076ca 100644 --- a/music21/test/test_metadata.py +++ b/music21/test/test_metadata.py @@ -441,7 +441,9 @@ def testUniqueNameAccess(self): ) self.checkUniqueNamedItem('bibliographicCitation', 'dcterms') self.checkUniqueNamedItem('conformsTo', 'dcterms') - self.checkUniqueNamedItem('dateCreated', 'dcterms:created', valueType=metadata.DatePrimitive) + self.checkUniqueNamedItem('dateCreated', + 'dcterms:created', + valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('otherDate', 'dcterms:date', valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('dateAccepted', 'dcterms', valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('dateCopyrighted', 'dcterms', valueType=metadata.DatePrimitive) @@ -460,7 +462,9 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem('isReferencedBy', 'dcterms') self.checkUniqueNamedItem('isReplacedBy', 'dcterms') self.checkUniqueNamedItem('isRequiredBy', 'dcterms') - self.checkUniqueNamedItem('dateIssued', 'dcterms:issued', valueType=metadata.DatePrimitive) + self.checkUniqueNamedItem('dateIssued', + 'dcterms:issued', + valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('isVersionOf', 'dcterms') self.checkUniqueNamedItem('language', 'dcterms') self.checkUniqueNamedItem('license', 'dcterms') @@ -483,7 +487,9 @@ def testUniqueNameAccess(self): self.checkUniqueNamedItem('tableOfContents', 'dcterms') self.checkUniqueNamedItem('title', 'dcterms') self.checkUniqueNamedItem('type', 'dcterms') - self.checkUniqueNamedItem('dateValid', 'dcterms:valid', valueType=metadata.DatePrimitive) + self.checkUniqueNamedItem('dateValid', + 'dcterms:valid', + valueType=metadata.DatePrimitive) self.checkUniqueNamedItem('adapter', 'marcrel:ADP') self.checkUniqueNamedItem('analyst', 'marcrel:ANL') self.checkUniqueNamedItem('annotator', 'marcrel:ANN') @@ -633,9 +639,8 @@ def testUniqueNameAccess(self): valueType=int ) -# ----------------------------------------------------------------------------- - +# ----------------------------------------------------------------------------- if __name__ == '__main__': import music21 music21.mainTest(Test, 'noDocTest') From 5e78fde1fe9f08635e4f57668777931661dbf9b9 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 16:00:35 -1000 Subject: [PATCH 29/35] fix all but bugs going other way --- music21/_version.py | 2 +- music21/analysis/discrete.py | 14 +- music21/analysis/patel.py | 24 +- music21/analysis/reduction.py | 8 +- music21/base.py | 15 +- music21/braille/segment.py | 2 +- music21/chord/__init__.py | 2 + music21/common/decorators.py | 35 +- music21/common/stringTools.py | 13 +- music21/derivation.py | 4 +- music21/dynamics.py | 29 +- music21/environment.py | 4 +- music21/expressions.py | 18 +- music21/features/base.py | 16 +- music21/features/jSymbolic.py | 472 +++++++++++++------------- music21/features/native.py | 84 ++--- music21/figuredBass/segment.py | 4 +- music21/graph/__init__.py | 19 +- music21/graph/plot.py | 100 +++--- music21/graph/primitives.py | 45 ++- music21/interval.py | 75 ++-- music21/key.py | 10 +- music21/layout.py | 41 +-- music21/metadata/__init__.py | 26 +- music21/metadata/primitives.py | 8 +- music21/note.py | 35 +- music21/noteworthy/binaryTranslate.py | 2 +- music21/pitch.py | 2 +- music21/scale/intervalNetwork.py | 55 ++- music21/scale/test_intervalNetwork.py | 58 +++- music21/search/segment.py | 17 +- music21/spanner.py | 53 ++- music21/stream/base.py | 35 +- 33 files changed, 699 insertions(+), 628 deletions(-) diff --git a/music21/_version.py b/music21/_version.py index 5a22573d8e..7ddb9bef43 100644 --- a/music21/_version.py +++ b/music21/_version.py @@ -41,7 +41,7 @@ Changing this number invalidates old pickles -- do it if the old pickles create a problem. ''' -__version_info__ = (8, 0, 0, 'a10') # can be 3-tuple or 4+-tuple: (7, 0, 5, 'a2') +__version_info__ = (8, 0, 0, 'a11') # can be 3-tuple or 4+-tuple: (7, 0, 5, 'a2') v = '.'.join(str(x) for x in __version_info__[0:3]) if len(__version_info__) > 3 and __version_info__[3]: # type: ignore diff --git a/music21/analysis/discrete.py b/music21/analysis/discrete.py index ca377ad708..ad257095c3 100644 --- a/music21/analysis/discrete.py +++ b/music21/analysis/discrete.py @@ -22,6 +22,8 @@ (for algorithmic key detection) and :class:`music21.analysis.discrete.Ambitus` (for pitch range analysis) provide examples. ''' +from __future__ import annotations + # TODO: make an analysis.base for the Discrete and analyzeStream aspects, then create # range and key modules in analysis @@ -1326,10 +1328,10 @@ def analyzeStream( # this synonym is being added for compatibility method = 'span' - match: t.Optional[t.Callable] = analysisClassFromMethodName(method) + analysisClassName: t.Optional[t.Type[DiscreteAnalysis]] = analysisClassFromMethodName(method) - if match is not None: - obj = match() # NOTE: Cuthbert, this was previously analysisClassName()? - out of scope + if analysisClassName is not None: + obj = analysisClassName() # environLocal.printDebug(['analysis method used:', obj]) return obj.getSolution(streamObj) @@ -1338,7 +1340,7 @@ def analyzeStream( # noinspection SpellCheckingInspection -def analysisClassFromMethodName(method: str): +def analysisClassFromMethodName(method: str) -> t.Optional[t.Type[DiscreteAnalysis]]: ''' Returns an analysis class given a method name, or None if none can be found @@ -1359,7 +1361,7 @@ def analysisClassFromMethodName(method: str): >>> print(repr(acfmn('unknown-format'))) None ''' - analysisClasses = [ + analysisClasses: t.List[t.Type[DiscreteAnalysis]] = [ Ambitus, KrumhanslSchmuckler, AardenEssen, @@ -1367,7 +1369,7 @@ def analysisClassFromMethodName(method: str): BellmanBudge, TemperleyKostkaPayne, ] - match = None + match: t.Optional[t.Type[DiscreteAnalysis]] = None for analysisClass in analysisClasses: # this is a very loose matching, as there are few classes now if (method.lower() in analysisClass.__name__.lower() diff --git a/music21/analysis/patel.py b/music21/analysis/patel.py index e464662ada..2ed7dade3f 100644 --- a/music21/analysis/patel.py +++ b/music21/analysis/patel.py @@ -59,35 +59,31 @@ def nPVI(streamForAnalysis): final = summation * 100 / (totalElements - 1) return final -def melodicIntervalVariability(streamForAnalysis, *skipArgs, **skipKeywords): +def melodicIntervalVariability(streamForAnalysis, **skipKeywords): ''' - gives the Melodic Interval Variability (MIV) for a Stream, + Gives the Melodic Interval Variability (MIV) for a Stream, as defined by Aniruddh D. Patel in "Music, Language, and the Brain" p. 223, as 100 x the coefficient of variation (standard deviation/mean) of the interval size (measured in semitones) between consecutive elements. + The multiplication by 100x exists to put it in the same range as nPVI. - the 100x is designed to put it in the same range as nPVI + Keywords are passed on to + Stream.findConsecutiveNotes() via Stream.melodicIntervals for + determining how to find consecutive intervals. - - this method takes the same arguments of skipArgs and skipKeywords as - Stream.melodicIntervals() for determining how to find consecutive - intervals. - - - - >>> s2 = converter.parse('tinynotation: 4/4 C4 D E F# G#').flatten().notesAndRests.stream() + >>> s2 = converter.parse('tinynotation: 4/4 C4 D E F# G#')[note.Note].stream() >>> analysis.patel.melodicIntervalVariability(s2) 0.0 - >>> s3 = converter.parse('tinynotation: 4/4 C4 D E F G C').flatten().notesAndRests.stream() + >>> s3 = converter.parse('tinynotation: 4/4 C4 D E F G C')[note.Note].stream() >>> analysis.patel.melodicIntervalVariability(s3) 85.266688... - >>> s4 = corpus.parse('bwv66.6').parts[0].flatten().notesAndRests.stream() + >>> s4 = corpus.parse('bwv66.6').parts[0][note.GeneralNote].stream() >>> analysis.patel.melodicIntervalVariability(s4) 65.287... ''' s = streamForAnalysis # shorter - intervalStream = s.melodicIntervals(skipArgs, skipKeywords) + intervalStream = s.melodicIntervals(**skipKeywords) totalElements = len(intervalStream) if totalElements < 2: raise PatelException('need at least three notes to have ' diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index 5ccf835924..d90d51b62d 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -113,7 +113,7 @@ def _reprInternal(self): def __getitem__(self, key): return self._parameters[key] - def _parseSpecification(self, spec): + def _parseSpecification(self, spec: str): # start with the defaults self._parameters = copy.deepcopy(self._defaultParameters) spec = spec.strip() @@ -133,7 +133,7 @@ def _parseSpecification(self, spec): self._parameters[attr] = value self._isParsed = True - def isParsed(self): + def isParsed(self) -> bool: return self._isParsed def getNoteAndTextExpression(self): @@ -199,7 +199,7 @@ class ScoreReduction: ''' An object to reduce a score. ''' - def __init__(self, *args, **keywords): + def __init__(self, **keywords): # store a list of one or more reductions self._reductiveNotes = {} self._reductiveVoices = [] @@ -464,7 +464,7 @@ class PartReduction: ''' def __init__(self, srcScore=None, - *args, + *, partGroups: t.Optional[t.List[t.Dict[str, t.Any]]] = None, fillByMeasure: bool = True, segmentByTarget: bool = True, diff --git a/music21/base.py b/music21/base.py index 652ecf9063..c3f971927e 100644 --- a/music21/base.py +++ b/music21/base.py @@ -28,7 +28,7 @@ >>> music21.VERSION_STR -'8.0.0a10' +'8.0.0a11' Alternatively, after doing a complete import, these classes are available under the module "base": @@ -1160,11 +1160,11 @@ def getSpannerSites(self, if obj is None: # pragma: no cover continue if spannerClassList is None: - post.append(obj.spannerParent) + post.append(obj.client) else: for spannerClass in spannerClassList: - if spannerClass in obj.spannerParent.classSet: - post.append(obj.spannerParent) + if spannerClass in obj.client.classSet: + post.append(obj.client) break return post @@ -2364,7 +2364,7 @@ def offset(self) -> OffsetQL: thus the place where `.offset` looks to find its number. >>> m2 = stream.Measure() - >>> m2.insert(3.0/5, n1) + >>> m2.insert(3/5, n1) >>> m2.number = 5 >>> n1.offset Fraction(3, 5) @@ -2385,20 +2385,17 @@ def offset(self) -> OffsetQL: >>> n1.offset 10.0 - The property can also set the offset for the object if no container has been set: - >>> n1 = note.Note() >>> n1.id = 'hi' - >>> n1.offset = 20/3. + >>> n1.offset = 20/3 >>> n1.offset Fraction(20, 3) >>> float(n1.offset) 6.666... - >>> s1 = stream.Stream() >>> s1.append(n1) >>> n1.offset diff --git a/music21/braille/segment.py b/music21/braille/segment.py index 8690c6c151..6f1bfa4bfe 100644 --- a/music21/braille/segment.py +++ b/music21/braille/segment.py @@ -209,7 +209,7 @@ def __init__(self, *listElements): >>> bg.numRepeats 0 ''' - self.internalList = list(*listElement) + self.internalList = list(*listElements) setGroupingGlobals() self.keySignature = GROUPING_GLOBALS['keySignature'] diff --git a/music21/chord/__init__.py b/music21/chord/__init__.py index e009fa6cfa..f334f1caeb 100644 --- a/music21/chord/__init__.py +++ b/music21/chord/__init__.py @@ -3597,6 +3597,8 @@ def isTriad(self) -> bool: False >>> incorrectlySpelled.pitches[1].getEnharmonic(inPlace=True) + >>> incorrectlySpelled + >>> incorrectlySpelled.isTriad() True diff --git a/music21/common/decorators.py b/music21/common/decorators.py index 7da0cf3a2e..fee23a4b68 100644 --- a/music21/common/decorators.py +++ b/music21/common/decorators.py @@ -26,27 +26,27 @@ def optional_arg_decorator(fn): a decorator for decorators. Allows them to either have or not have arguments. ''' @wraps(fn) - def wrapped_decorator(*args, **keywords): - is_bound_method = hasattr(args[0], fn.__name__) if args else False + def wrapped_decorator(*arguments, **keywords): + is_bound_method = hasattr(arguments[0], fn.__name__) if arguments else False klass = None if is_bound_method: - klass = args[0] - args = args[1:] + klass = arguments[0] + arguments = arguments[1:] # If no arguments were passed... - if len(args) == 1 and not keywords and callable(args[0]): + if len(arguments) == 1 and not keywords and callable(arguments[0]): if is_bound_method: - return fn(klass, args[0]) + return fn(klass, arguments[0]) else: - return fn(args[0]) + return fn(arguments[0]) else: def real_decorator(toBeDecorated): if is_bound_method: - return fn(klass, toBeDecorated, *args, **keywords) + return fn(klass, toBeDecorated, *arguments, **keywords) else: - return fn(toBeDecorated, *args, **keywords) + return fn(toBeDecorated, *arguments, **keywords) return real_decorator return wrapped_decorator @@ -129,11 +129,12 @@ def deprecated(method, startDate=None, removeDate=None, message=None): 'message': m} @wraps(method) - def func_wrapper(*args, **keywords): - if len(args) > 1 and args[1] in ('_ipython_canary_method_should_not_exist_', - '_repr_mimebundle_', - '_is_coroutine', - ): + def func_wrapper(*arguments, **keywords): + if len(arguments) > 1 and arguments[1] in ( + '_ipython_canary_method_should_not_exist_', + '_repr_mimebundle_', + '_is_coroutine' + ): # false positive from IPython for StreamIterator.__getattr__ # can remove after v9. falsePositive = True @@ -147,7 +148,7 @@ def func_wrapper(*args, **keywords): exceptions21.Music21DeprecationWarning, stacklevel=2) callInfo['calledAlready'] = True - return method(*args, **keywords) + return method(*arguments, **keywords) return func_wrapper @@ -175,11 +176,11 @@ def cacheMethod(method): funcName = method.__name__ @wraps(method) - def inner(instance, *args, **keywords): + def inner(instance, *arguments, **keywords): if funcName in instance._cache: return instance._cache[funcName] - instance._cache[funcName] = method(instance, *args, **keywords) + instance._cache[funcName] = method(instance, *arguments, **keywords) return instance._cache[funcName] return inner diff --git a/music21/common/stringTools.py b/music21/common/stringTools.py index 0bc05a8f52..57f4301f89 100644 --- a/music21/common/stringTools.py +++ b/music21/common/stringTools.py @@ -262,10 +262,11 @@ def getMd5(value=None) -> str: def formatStr(msg, - *args, - format: t.Optional[str] = None, # pylint: disable=redefined-builtin + *rest_of_message, **keywords) -> str: ''' + DEPRECATED: do not use. May be removed at any time. + Format one or more data elements into string suitable for printing straight to stderr or other outputs @@ -274,8 +275,7 @@ def formatStr(msg, test 1 2 3 ''' - - msg = [msg] + list(args) + msg = [msg, *rest_of_message] for i in range(len(msg)): x = msg[i] if isinstance(x, bytes): @@ -288,10 +288,7 @@ def formatStr(msg, msg[i] = x.decode('utf-8') except AttributeError: msg[i] = '' - if format == 'block': - return '\n*** '.join(msg) + '\n' - else: # catch all others - return ' '.join(msg) + '\n' + return ' '.join(msg) + '\n' def stripAccents(inputString: str) -> str: diff --git a/music21/derivation.py b/music21/derivation.py index 797065d048..5a83722178 100644 --- a/music21/derivation.py +++ b/music21/derivation.py @@ -52,8 +52,8 @@ def derivationMethod(function): from via 'allGreen'> ''' @functools.wraps(function) - def wrapper(self, *args, **keywords): - result = function(self, *args, **keywords) + def wrapper(self, *arguments, **keywords): + result = function(self, *arguments, **keywords) result.derivation.origin = self result.derivation.method = function.__name__ return result diff --git a/music21/dynamics.py b/music21/dynamics.py index 420b152ec6..4eb48f048e 100644 --- a/music21/dynamics.py +++ b/music21/dynamics.py @@ -355,11 +355,12 @@ def _setVolumeScalar(self, value): # ------------------------------------------------------------------------------ class DynamicWedge(spanner.Spanner): - '''Common base-class for Crescendo and Diminuendo. + ''' + Common base-class for Crescendo and Diminuendo. ''' - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, *spannedElements, **keywords): + super().__init__(*spannedElements, **keywords) self.type = None # crescendo or diminuendo self.placement = 'below' # can above or below, after musicxml @@ -368,7 +369,8 @@ def __init__(self, *arguments, **keywords): class Crescendo(DynamicWedge): - '''A spanner crescendo wedge. + ''' + A spanner crescendo wedge. >>> from music21 import dynamics >>> d = dynamics.Crescendo() @@ -381,13 +383,14 @@ class Crescendo(DynamicWedge): 'crescendo' ''' - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, *spannedElements, **keywords): + super().__init__(*spannedElements, **keywords) self.type = 'crescendo' class Diminuendo(DynamicWedge): - '''A spanner diminuendo wedge. + ''' + A spanner diminuendo wedge. >>> from music21 import dynamics >>> d = dynamics.Diminuendo() @@ -396,8 +399,8 @@ class Diminuendo(DynamicWedge): 20 ''' - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, *spannedElements, **keywords): + super().__init__(*spannedElements, **keywords) self.type = 'diminuendo' # ------------------------------------------------------------------------------ @@ -412,7 +415,8 @@ def testSingle(self): a.show() def testBasic(self): - '''present each dynamic in a single measure + ''' + present each dynamic in a single measure ''' from music21 import stream a = stream.Stream() @@ -429,7 +433,8 @@ def testBasic(self): class Test(unittest.TestCase): def testCopyAndDeepcopy(self): - '''Test copying all objects defined in this module + ''' + Test copying all objects defined in this module ''' import copy import sys @@ -511,7 +516,6 @@ def testDynamicsPositionB(self): d = Dynamic('mf') d.style.absoluteY = 20 m.insert(o, d) - # s.show() @@ -522,4 +526,3 @@ def testDynamicsPositionB(self): if __name__ == '__main__': import music21 music21.mainTest(Test) - diff --git a/music21/environment.py b/music21/environment.py index 03db684519..ab00c7df14 100644 --- a/music21/environment.py +++ b/music21/environment.py @@ -1059,7 +1059,7 @@ def formatToApp(self, m21Format): def formatToKey(self, m21Format): return envSingleton().formatToKey(m21Format) - def printDebug(self, msg, statusLevel=common.DEBUG_USER, debugFormat=None): + def printDebug(self, msg, statusLevel=common.DEBUG_USER): ''' Format one or more data elements into string, and print it to stderr. The first arg can be a list of strings or a string; lists are @@ -1071,7 +1071,7 @@ def printDebug(self, msg, statusLevel=common.DEBUG_USER, debugFormat=None): if msg[0] != self.modNameParent and self.modNameParent is not None: msg = [self.modNameParent + ':'] + msg # pass list to common.formatStr - msg = common.formatStr(*msg, format=debugFormat) + msg = common.formatStr(*msg) sys.stderr.write(msg) def read(self, filePath=None): diff --git a/music21/expressions.py b/music21/expressions.py index e4bed3077b..2ea1f79944 100644 --- a/music21/expressions.py +++ b/music21/expressions.py @@ -1440,8 +1440,8 @@ class TrillExtension(spanner.Spanner): # We will try to avoid "continue". # N.B. this extension always includes a trill symbol - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, *spannedElements, **keywords): + super().__init__(*spannedElements, **keywords) self._placement = None # can above or below or None, after musicxml def _getPlacement(self): @@ -1487,8 +1487,8 @@ class TremoloSpanner(spanner.Spanner): # musicxml defines a "start", "stop", and a "continue" type. # We will try to avoid using the "continue" type. - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, *spannedElements, **keywords): + super().__init__(*spannedElements, **keywords) self.placement = None self.measured = True self._numberOfMarks = 3 @@ -1564,11 +1564,11 @@ class ArpeggioMarkSpanner(spanner.Spanner): > ''' - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) - arpeggioType: t.Optional[str] = keywords.get('arpeggioType', None) - if arpeggioType is None: - arpeggioType = 'normal' + def __init__(self, + *spannedElements, + arpeggioType: str = 'normal', + **keywords): + super().__init__(*spannedElements, **keywords) if arpeggioType not in ('normal', 'up', 'down', 'non-arpeggio'): raise ValueError( 'Arpeggio type must be "normal", "up", "down", or "non-arpeggio"' diff --git a/music21/features/base.py b/music21/features/base.py index e6fc1b2aff..2a5ce05c57 100644 --- a/music21/features/base.py +++ b/music21/features/base.py @@ -139,7 +139,7 @@ class FeatureExtractor: Usage of a DataInstance offers significant performance advantages, as common forms of the Stream are cached for easy processing. ''' - def __init__(self, dataOrStream=None, *arguments, **keywords): + def __init__(self, dataOrStream=None, **keywords): self.stream = None # the original Stream, or None self.data: t.Optional[DataInstance] = None # a DataInstance object: use to get data self.setData(dataOrStream) @@ -179,8 +179,8 @@ def setData(self, dataOrStream): self.data = dataOrStream def getAttributeLabels(self): - '''Return a list of string in a form that is appropriate for data storage. - + ''' + Return a list of string in a form that is appropriate for data storage. >>> fe = features.jSymbolic.AmountOfArpeggiationFeature() >>> fe.getAttributeLabels() @@ -236,13 +236,15 @@ def prepareFeature(self): self.feature.prepareVectors() # will vector with necessary zeros def process(self): - '''Do processing necessary, storing result in _feature. + ''' + Do processing necessary, storing result in _feature. ''' # do work in subclass, calling on self.data pass def extract(self, source=None): - '''Extract the feature and return the result. + ''' + Extract the feature and return the result. ''' if source is not None: self.stream = source @@ -293,10 +295,8 @@ class StreamForms: of the stream which is the main power of this routine, making it simple to add additional feature extractors at low additional time cost. - ''' - - def __init__(self, streamObj, prepareStream=True): + def __init__(self, streamObj: stream.Stream, prepareStream=True): self.stream = streamObj if self.stream is not None: if prepareStream: diff --git a/music21/features/jSymbolic.py b/music21/features/jSymbolic.py index 32aa88544a..1832df9fde 100644 --- a/music21/features/jSymbolic.py +++ b/music21/features/jSymbolic.py @@ -51,8 +51,8 @@ class MelodicIntervalHistogramFeature(featuresModule.FeatureExtractor): ''' id = 'M1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Melodic Interval Histogram' self.description = ('A features array with bins corresponding to ' @@ -80,8 +80,8 @@ class AverageMelodicIntervalFeature(featuresModule.FeatureExtractor): ''' id = 'M2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Melodic Interval' self.description = 'Average melodic interval (in semitones).' @@ -113,8 +113,8 @@ class MostCommonMelodicIntervalFeature(featuresModule.FeatureExtractor): ''' id = 'M3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Melodic Interval' self.description = 'Melodic interval with the highest frequency.' @@ -143,8 +143,8 @@ class DistanceBetweenMostCommonMelodicIntervalsFeature( ''' id = 'M4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Distance Between Most Common Melodic Intervals' self.description = ('Absolute value of the difference between the ' @@ -180,8 +180,8 @@ class MostCommonMelodicIntervalPrevalenceFeature( ''' id = 'M5' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Melodic Interval Prevalence' self.description = 'Fraction of melodic intervals that belong to the most common interval.' @@ -212,8 +212,8 @@ class RelativeStrengthOfMostCommonIntervalsFeature( ''' id = 'M6' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Relative Strength of Most Common Intervals' self.description = ('Fraction of melodic intervals that belong ' @@ -250,8 +250,8 @@ class NumberOfCommonMelodicIntervalsFeature(featuresModule.FeatureExtractor): ''' id = 'M7' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Number of Common Melodic Intervals' self.description = ('Number of melodic intervals that represent ' @@ -288,8 +288,8 @@ class AmountOfArpeggiationFeature(featuresModule.FeatureExtractor): ''' id = 'M8' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Amount of Arpeggiation' self.description = ('Fraction of horizontal intervals that are repeated notes, ' @@ -328,8 +328,8 @@ class RepeatedNotesFeature(featuresModule.FeatureExtractor): ''' id = 'M9' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Repeated Notes' self.description = 'Fraction of notes that are repeated melodically.' @@ -366,8 +366,8 @@ class ChromaticMotionFeature(featuresModule.FeatureExtractor): ''' id = 'm10' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Chromatic Motion' self.description = 'Fraction of melodic intervals corresponding to a semi-tone.' @@ -401,8 +401,8 @@ class StepwiseMotionFeature(featuresModule.FeatureExtractor): ''' id = 'M11' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Stepwise Motion' self.description = ('Fraction of melodic intervals that corresponded ' @@ -437,8 +437,8 @@ class MelodicThirdsFeature(featuresModule.FeatureExtractor): ''' id = 'M12' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Melodic Thirds' self.description = 'Fraction of melodic intervals that are major or minor thirds.' @@ -472,8 +472,8 @@ class MelodicFifthsFeature(featuresModule.FeatureExtractor): ''' id = 'M13' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Melodic Fifths' self.description = 'Fraction of melodic intervals that are perfect fifths.' @@ -507,8 +507,8 @@ class MelodicTritonesFeature(featuresModule.FeatureExtractor): ''' id = 'M14' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Melodic Tritones' self.description = 'Fraction of melodic intervals that are tritones.' @@ -542,8 +542,8 @@ class MelodicOctavesFeature(featuresModule.FeatureExtractor): ''' id = 'M15' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Melodic Octaves' self.description = 'Fraction of melodic intervals that are octaves.' @@ -578,8 +578,8 @@ class DirectionOfMotionFeature(featuresModule.FeatureExtractor): ''' id = 'm17' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Direction of Motion' self.description = 'Fraction of melodic intervals that are rising rather than falling.' @@ -636,8 +636,8 @@ class DurationOfMelodicArcsFeature(featuresModule.FeatureExtractor): ''' id = 'M18' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Duration of Melodic Arcs' self.description = ('Average number of notes that separate melodic ' @@ -726,8 +726,8 @@ class SizeOfMelodicArcsFeature(featuresModule.FeatureExtractor): ''' id = 'M19' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Size of Melodic Arcs' self.description = ('Average span (in semitones) between melodic peaks ' @@ -818,8 +818,8 @@ class MostCommonPitchPrevalenceFeature(featuresModule.FeatureExtractor): ''' id = 'P1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Pitch Prevalence' self.description = 'Fraction of Note Ons corresponding to the most common pitch.' @@ -852,8 +852,8 @@ class MostCommonPitchClassPrevalenceFeature(featuresModule.FeatureExtractor): ''' id = 'P2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Pitch Class Prevalence' self.description = 'Fraction of Note Ons corresponding to the most common pitch class.' @@ -886,8 +886,8 @@ class RelativeStrengthOfTopPitchesFeature(featuresModule.FeatureExtractor): ''' id = 'P3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Relative Strength of Top Pitches' self.description = ('The frequency of the 2nd most common pitch ' @@ -921,8 +921,8 @@ class RelativeStrengthOfTopPitchClassesFeature(featuresModule.FeatureExtractor): ''' id = 'P4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Relative Strength of Top Pitch Classes' self.description = ('The frequency of the 2nd most common pitch class ' @@ -961,8 +961,8 @@ class IntervalBetweenStrongestPitchesFeature(featuresModule.FeatureExtractor): ''' id = 'P5' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Interval Between Strongest Pitches' self.description = ('Absolute value of the difference between ' @@ -995,8 +995,8 @@ class IntervalBetweenStrongestPitchClassesFeature( ''' id = 'P6' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Interval Between Strongest Pitch Classes' self.description = ('Absolute value of the difference between the pitch ' @@ -1030,8 +1030,8 @@ class NumberOfCommonPitchesFeature(featuresModule.FeatureExtractor): ''' id = 'P7' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Number of Common Pitches' self.description = ('Number of pitches that account individually ' @@ -1062,8 +1062,8 @@ class PitchVarietyFeature(featuresModule.FeatureExtractor): ''' id = 'P8' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Pitch Variety' self.description = 'Number of pitches used at least once.' @@ -1092,8 +1092,8 @@ class PitchClassVarietyFeature(featuresModule.FeatureExtractor): ''' id = 'P9' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Pitch Class Variety' self.description = 'Number of pitch classes used at least once.' @@ -1122,8 +1122,8 @@ class RangeFeature(featuresModule.FeatureExtractor): ''' id = 'P10' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Range' self.description = 'Difference between highest and lowest pitches.' @@ -1153,8 +1153,8 @@ class MostCommonPitchFeature(featuresModule.FeatureExtractor): ''' id = 'P11' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Pitch' self.description = 'Bin label of the most common pitch.' @@ -1184,8 +1184,8 @@ class PrimaryRegisterFeature(featuresModule.FeatureExtractor): ''' id = 'P12' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Primary Register' self.description = 'Average MIDI pitch.' @@ -1214,8 +1214,8 @@ class ImportanceOfBassRegisterFeature(featuresModule.FeatureExtractor): ''' id = 'P13' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Importance of Bass Register' self.description = 'Fraction of Note Ons between MIDI pitches 0 and 54.' @@ -1250,8 +1250,8 @@ class ImportanceOfMiddleRegisterFeature(featuresModule.FeatureExtractor): ''' id = 'P14' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Importance of Middle Register' self.description = 'Fraction of Note Ons between MIDI pitches 55 and 72.' @@ -1286,8 +1286,8 @@ class ImportanceOfHighRegisterFeature(featuresModule.FeatureExtractor): ''' id = 'P15' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Importance of High Register' self.description = 'Fraction of Note Ons between MIDI pitches 73 and 127.' @@ -1322,8 +1322,8 @@ class MostCommonPitchClassFeature(featuresModule.FeatureExtractor): ''' id = 'P16' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Pitch Class' self.description = 'Bin label of the most common pitch class.' @@ -1347,8 +1347,8 @@ class DominantSpreadFeature(featuresModule.FeatureExtractor): ''' id = 'P17' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Dominant Spread' self.description = ('Largest number of consecutive pitch classes separated by ' @@ -1370,8 +1370,8 @@ class StrongTonalCentresFeature(featuresModule.FeatureExtractor): ''' id = 'P18' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Strong Tonal Centres' self.description = ('Number of peaks in the fifths pitch histogram that each account ' @@ -1408,8 +1408,8 @@ class BasicPitchHistogramFeature(featuresModule.FeatureExtractor): ''' id = 'P19' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Basic Pitch Histogram' self.description = ('A features array with bins corresponding to the ' @@ -1441,8 +1441,8 @@ class PitchClassDistributionFeature(featuresModule.FeatureExtractor): ''' id = 'P20' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Pitch Class Distribution' self.description = ('A feature array with 12 entries where the first holds ' @@ -1487,8 +1487,8 @@ class FifthsPitchHistogramFeature(featuresModule.FeatureExtractor): ''' id = 'P21' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Fifths Pitch Histogram' self.description = ('A feature array with bins corresponding to the values of the ' @@ -1533,8 +1533,8 @@ class QualityFeature(featuresModule.FeatureExtractor): ''' id = 'P22' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Quality' self.description = ''' @@ -1572,8 +1572,8 @@ class GlissandoPrevalenceFeature(featuresModule.FeatureExtractor): ''' id = 'P23' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Glissando Prevalence' self.description = ('Number of Note Ons that have at least one MIDI Pitch Bend ' @@ -1597,8 +1597,8 @@ class AverageRangeOfGlissandosFeature(featuresModule.FeatureExtractor): ''' id = 'P24' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Range Of Glissandos' self.description = ('Average range of MIDI Pitch Bends, where "range" is ' @@ -1624,8 +1624,8 @@ class VibratoPrevalenceFeature(featuresModule.FeatureExtractor): ''' id = 'P25' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Vibrato Prevalence' self.description = ('Number of notes for which Pitch Bend messages change ' @@ -1649,9 +1649,9 @@ class PrevalenceOfMicrotonesFeature(featuresModule.FeatureExtractor): ''' id = 'P26' - def __init__(self, dataOrStream=None, *arguments, **keywords): + def __init__(self, dataOrStream=None, **keywords): super().__init__(dataOrStream=dataOrStream, - *arguments, **keywords) + **keywords) self.name = 'Prevalence Of Microtones' self.description = ('Number of Note Ons that are preceded by isolated MIDI Pitch ' @@ -1688,8 +1688,8 @@ class StrongestRhythmicPulseFeature(featuresModule.FeatureExtractor): id = 'R1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Strongest Rhythmic Pulse' self.description = 'Bin label of the beat bin with the highest frequency.' @@ -1722,8 +1722,8 @@ class SecondStrongestRhythmicPulseFeature(featuresModule.FeatureExtractor): ''' id = 'R2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Second Strongest Rhythmic Pulse' self.description = ('Bin label of the beat bin of the peak ' @@ -1762,8 +1762,8 @@ class HarmonicityOfTwoStrongestRhythmicPulsesFeature( ''' id = 'R3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Harmonicity of Two Strongest Rhythmic Pulses' self.description = ('The bin label of the higher (in terms of bin label) of the ' @@ -1794,8 +1794,8 @@ class StrengthOfStrongestRhythmicPulseFeature(featuresModule.FeatureExtractor): ''' id = 'R4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Strength of Strongest Rhythmic Pulse' self.description = 'Frequency of the beat bin with the highest frequency.' @@ -1821,8 +1821,8 @@ class StrengthOfSecondStrongestRhythmicPulseFeature( ''' id = 'R5' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Strength of Second Strongest Rhythmic Pulse' self.description = ('Frequency of the beat bin of the peak ' @@ -1858,8 +1858,8 @@ class StrengthRatioOfTwoStrongestRhythmicPulsesFeature( ''' id = 'R6' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Strength Ratio of Two Strongest Rhythmic Pulses' self.description = ('The frequency of the higher (in terms of frequency) of the two ' @@ -1893,8 +1893,8 @@ class CombinedStrengthOfTwoStrongestRhythmicPulsesFeature( ''' id = 'R7' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Combined Strength of Two Strongest Rhythmic Pulses' self.description = ('The sum of the frequencies of the two beat bins ' @@ -1923,8 +1923,8 @@ class NumberOfStrongPulsesFeature(featuresModule.FeatureExtractor): ''' id = 'R8' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Number of Strong Pulses' self.description = 'Number of beat peaks with normalized frequencies over 0.1.' @@ -1944,8 +1944,8 @@ class NumberOfModeratePulsesFeature(featuresModule.FeatureExtractor): ''' id = 'R9' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Number of Moderate Pulses' self.description = 'Number of beat peaks with normalized frequencies over 0.01.' @@ -1966,8 +1966,8 @@ class NumberOfRelativelyStrongPulsesFeature(featuresModule.FeatureExtractor): ''' id = 'R10' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Number of Relatively Strong Pulses' self.description = '''Number of beat peaks with frequencies at least 30% as high as @@ -1987,8 +1987,8 @@ class RhythmicLoosenessFeature(featuresModule.FeatureExtractor): ''' id = 'R11' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Rhythmic Looseness' self.description = '''Average width of beat histogram peaks (in beats per minute). @@ -2016,10 +2016,10 @@ class PolyrhythmsFeature(featuresModule.FeatureExtractor): ''' id = 'R12' - def __init__(self, dataOrStream=None, *arguments, **keywords): + def __init__(self, dataOrStream=None, **keywords): featuresModule.FeatureExtractor.__init__(self, dataOrStream=dataOrStream, - *arguments, **keywords) + **keywords) self.name = 'Polyrhythms' self.description = ''' @@ -2045,8 +2045,8 @@ class RhythmicVariabilityFeature(featuresModule.FeatureExtractor): ''' id = 'R13' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Rhythmic Variability' self.description = 'Standard deviation of the bin values (except the first 40 empty ones).' @@ -2069,8 +2069,8 @@ class BeatHistogramFeature(featuresModule.FeatureExtractor): ''' id = 'R14' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Beat Histogram' self.description = ('A feature array with entries corresponding to the ' @@ -2101,8 +2101,8 @@ class NoteDensityFeature(featuresModule.FeatureExtractor): ''' id = 'R15' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Note Density' self.description = 'Average number of notes per second.' @@ -2142,8 +2142,8 @@ class AverageNoteDurationFeature(featuresModule.FeatureExtractor): id = 'R17' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Note Duration' self.description = 'Average duration of notes in seconds.' @@ -2180,8 +2180,8 @@ class VariabilityOfNoteDurationFeature(featuresModule.FeatureExtractor): ''' id = 'R18' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Variability of Note Duration' self.description = 'Standard deviation of note durations in seconds.' @@ -2211,8 +2211,8 @@ class MaximumNoteDurationFeature(featuresModule.FeatureExtractor): id = 'R19' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Maximum Note Duration' self.description = 'Duration of the longest note (in seconds).' @@ -2242,8 +2242,8 @@ class MinimumNoteDurationFeature(featuresModule.FeatureExtractor): ''' id = 'R20' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Minimum Note Duration' self.description = 'Duration of the shortest note (in seconds).' @@ -2276,8 +2276,8 @@ class StaccatoIncidenceFeature(featuresModule.FeatureExtractor): id = 'R21' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Staccato Incidence' self.description = ('Number of notes with durations of less than a 10th ' @@ -2311,8 +2311,8 @@ class AverageTimeBetweenAttacksFeature(featuresModule.FeatureExtractor): id = 'R22' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Time Between Attacks' self.description = 'Average time in seconds between Note On events (regardless of channel).' @@ -2352,8 +2352,8 @@ class VariabilityOfTimeBetweenAttacksFeature(featuresModule.FeatureExtractor): id = 'R23' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Variability of Time Between Attacks' self.description = ('Standard deviation of the times, in seconds, ' @@ -2396,8 +2396,8 @@ class AverageTimeBetweenAttacksForEachVoiceFeature( ''' id = 'R24' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Time Between Attacks For Each Voice' self.description = ('Average of average times in seconds between Note On events ' @@ -2454,8 +2454,8 @@ class AverageVariabilityOfTimeBetweenAttacksForEachVoiceFeature( id = 'R25' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Variability of Time Between Attacks For Each Voice' self.description = ('Average standard deviation, in seconds, of time between ' @@ -2502,9 +2502,9 @@ def process(self): # Not implemented in jSymbolic # # ''' -# def __init__(self, dataOrStream=None, *arguments, **keywords): +# def __init__(self, dataOrStream=None, **keywords): # super().__init__(dataOrStream=dataOrStream, -# *arguments, **keywords) +# **keywords) # # self.name = 'Incidence Of Complete Rests' # self.description = ('Total amount of time in seconds in which no notes are sounding' @@ -2517,9 +2517,9 @@ def process(self): # Not implemented in jSymbolic # # ''' -# def __init__(self, dataOrStream=None, *arguments, **keywords): +# def __init__(self, dataOrStream=None, **keywords): # super().__init__(dataOrStream=dataOrStream, -# *arguments, **keywords) +# **keywords) # # self.name = 'Maximum Complete Rest Duration' # self.description = ('Maximum amount of time in seconds in which no notes ' @@ -2532,9 +2532,9 @@ def process(self): # Not implemented in jSymbolic # # ''' -# def __init__(self, dataOrStream=None, *arguments, **keywords): +# def __init__(self, dataOrStream=None, **keywords): # super().__init__(dataOrStream=dataOrStream, -# *arguments, **keywords) +# **keywords) # # self.name = 'Average Rest Duration Per Voice' # self.description = ('Average, in seconds, of the average amounts of time in each ' @@ -2548,9 +2548,9 @@ def process(self): # Not implemented in jSymbolic # # ''' -# def __init__(self, dataOrStream=None, *arguments, **keywords): +# def __init__(self, dataOrStream=None, **keywords): # super().__init__(dataOrStream=dataOrStream, -# *arguments, **keywords) +# **keywords) # # self.name = 'Average Variability Of Rest Durations Across Voices' # self.description = ('Standard deviation, in seconds, of the average amounts of time ' @@ -2575,8 +2575,8 @@ class InitialTempoFeature(featuresModule.FeatureExtractor): id = 'R30' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Initial Tempo' self.description = 'Tempo in beats per minute at the start of the recording.' @@ -2612,8 +2612,8 @@ class InitialTimeSignatureFeature(featuresModule.FeatureExtractor): id = 'R31' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Initial Time Signature' self.description = ('A feature array with two elements. ' @@ -2660,8 +2660,8 @@ class CompoundOrSimpleMeterFeature(featuresModule.FeatureExtractor): id = 'R32' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Compound Or Simple Meter' self.description = ('Set to 1 if the initial meter is compound ' @@ -2708,8 +2708,8 @@ class TripleMeterFeature(featuresModule.FeatureExtractor): id = 'R33' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Triple Meter' self.description = ('Set to 1 if numerator of initial time signature is 3, ' @@ -2749,8 +2749,8 @@ class QuintupleMeterFeature(featuresModule.FeatureExtractor): id = 'R34' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Quintuple Meter' self.description = ('Set to 1 if numerator of initial time signature is 5, ' @@ -2789,8 +2789,8 @@ class ChangesOfMeterFeature(featuresModule.FeatureExtractor): ''' id = 'R35' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Changes of Meter' self.description = ('Set to 1 if the time signature is changed one or more ' @@ -2822,8 +2822,8 @@ class DurationFeature(featuresModule.FeatureExtractor): ''' id = 'R36' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Duration' self.description = 'The total duration in seconds of the music.' @@ -2856,8 +2856,8 @@ class OverallDynamicRangeFeature(featuresModule.FeatureExtractor): ''' id = 'D1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Overall Dynamic Range' self.description = 'The maximum loudness minus the minimum loudness value.' @@ -2877,8 +2877,8 @@ class VariationOfDynamicsFeature(featuresModule.FeatureExtractor): ''' id = 'D2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Variation of Dynamics' self.description = 'Standard deviation of loudness levels of all notes.' @@ -2898,8 +2898,8 @@ class VariationOfDynamicsInEachVoiceFeature(featuresModule.FeatureExtractor): ''' id = 'D3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Variation of Dynamics In Each Voice' self.description = ('The average of the standard deviations of loudness ' @@ -2922,8 +2922,8 @@ class AverageNoteToNoteDynamicsChangeFeature(featuresModule.FeatureExtractor): id = 'D4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Note To Note Dynamics Change' self.description = ('Average change of loudness from one note to the next note ' @@ -2956,8 +2956,8 @@ class MaximumNumberOfIndependentVoicesFeature(featuresModule.FeatureExtractor): id = 'T1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Maximum Number of Independent Voices' self.description = ('Maximum number of different channels in which notes ' @@ -2999,8 +2999,8 @@ class AverageNumberOfIndependentVoicesFeature(featuresModule.FeatureExtractor): ''' id = 'T2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Average Number of Independent Voices' self.description = ('Average number of different channels in which notes have ' @@ -3040,8 +3040,8 @@ class VariabilityOfNumberOfIndependentVoicesFeature( ''' id = 'T3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Variability of Number of Independent Voices' self.description = ('Standard deviation of number of different channels ' @@ -3080,8 +3080,8 @@ class VoiceEqualityNumberOfNotesFeature(featuresModule.FeatureExtractor): ''' id = 'T4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Voice Equality - Number of Notes' self.description = ('Standard deviation of the total number of Note Ons ' @@ -3101,8 +3101,8 @@ class VoiceEqualityNoteDurationFeature(featuresModule.FeatureExtractor): ''' id = 'T5' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Voice Equality - Note Duration' self.description = ('Standard deviation of the total duration of notes in seconds ' @@ -3122,8 +3122,8 @@ class VoiceEqualityDynamicsFeature(featuresModule.FeatureExtractor): ''' id = 'T6' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Voice Equality - Dynamics' self.description = ('Standard deviation of the average volume of notes ' @@ -3143,8 +3143,8 @@ class VoiceEqualityMelodicLeapsFeature(featuresModule.FeatureExtractor): ''' id = 'T7' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Voice Equality - Melodic Leaps' self.description = '''Standard deviation of the average melodic leap in MIDI pitches @@ -3162,8 +3162,8 @@ class VoiceEqualityRangeFeature(featuresModule.FeatureExtractor): ''' id = 'T8' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Voice Equality - Range' self.description = '''Standard deviation of the differences between the @@ -3183,8 +3183,8 @@ class ImportanceOfLoudestVoiceFeature(featuresModule.FeatureExtractor): ''' id = 'T9' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Importance of Loudest Voice' self.description = '''Difference between the average loudness of the loudest channel @@ -3204,8 +3204,8 @@ class RelativeRangeOfLoudestVoiceFeature(featuresModule.FeatureExtractor): ''' id = 'T10' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Relative Range of Loudest Voice' self.description = '''Difference between the highest note and the lowest note @@ -3226,8 +3226,8 @@ class RangeOfHighestLineFeature(featuresModule.FeatureExtractor): ''' id = 'T12' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Range of Highest Line' self.description = '''Difference between the highest note and the lowest note @@ -3248,8 +3248,8 @@ class RelativeNoteDensityOfHighestLineFeature(featuresModule.FeatureExtractor): ''' id = 'T13' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Relative Note Density of Highest Line' self.description = '''Number of Note Ons in the channel with the highest average @@ -3270,8 +3270,8 @@ class MelodicIntervalsInLowestLineFeature(featuresModule.FeatureExtractor): ''' id = 'T15' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Melodic Intervals in Lowest Line' self.description = '''Average melodic interval in semitones of the channel @@ -3290,8 +3290,8 @@ class VoiceSeparationFeature(featuresModule.FeatureExtractor): ''' id = 'T20' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Voice Separation' self.description = '''Average separation in semi-tones between the average pitches of @@ -3341,8 +3341,8 @@ class PitchedInstrumentsPresentFeature(featuresModule.FeatureExtractor): ''' id = 'I1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Pitched Instruments Present' self.description = '''Which pitched General MIDI Instruments are present. @@ -3386,8 +3386,8 @@ class UnpitchedInstrumentsPresentFeature(featuresModule.FeatureExtractor): id = 'I2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Unpitched Instruments Present' self.description = '''Which unpitched MIDI Percussion Key Map instruments are present. @@ -3432,8 +3432,8 @@ class NotePrevalenceOfPitchedInstrumentsFeature( ''' id = 'I3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Note Prevalence of Pitched Instruments' self.description = ('The fraction of (pitched) notes played by each ' @@ -3476,8 +3476,8 @@ class NotePrevalenceOfUnpitchedInstrumentsFeature( ''' id = 'I4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Note Prevalence of Unpitched Instruments' self.description = '''The fraction of (unpitched) notes played by each General MIDI @@ -3509,8 +3509,8 @@ class TimePrevalenceOfPitchedInstrumentsFeature( ''' id = 'I5' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Time Prevalence of Pitched Instruments' self.description = ('The fraction of the total time of the recording in which a note ' @@ -3542,8 +3542,8 @@ class VariabilityOfNotePrevalenceOfPitchedInstrumentsFeature( ''' id = 'I6' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Variability of Note Prevalence of Pitched Instruments' self.description = ('Standard deviation of the fraction of Note Ons played ' @@ -3591,8 +3591,8 @@ class VariabilityOfNotePrevalenceOfUnpitchedInstrumentsFeature( ''' id = 'I7' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Variability of Note Prevalence of Unpitched Instruments' self.description = ( @@ -3620,8 +3620,8 @@ class NumberOfPitchedInstrumentsFeature(featuresModule.FeatureExtractor): ''' id = 'I8' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Number of Pitched Instruments' self.description = ('Total number of General MIDI patches that are used to ' @@ -3656,8 +3656,8 @@ class NumberOfUnpitchedInstrumentsFeature(featuresModule.FeatureExtractor): id = 'I9' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Number of Unpitched Instruments' self.description = ('Number of distinct MIDI Percussion Key Map patches that were ' @@ -3678,8 +3678,8 @@ class PercussionPrevalenceFeature(featuresModule.FeatureExtractor): id = 'I10' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Percussion Prevalence' self.description = ('Total number of Note Ons corresponding to unpitched percussion ' @@ -3696,8 +3696,8 @@ class InstrumentFractionFeature(featuresModule.FeatureExtractor): look at the proportional usage of an Instrument ''' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) # subclasses must define self._targetPrograms = [] @@ -3736,8 +3736,8 @@ class StringKeyboardFractionFeature(InstrumentFractionFeature): id = 'I11' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'String Keyboard Fraction' self.description = ('Fraction of all Note Ons belonging to string keyboard patches ' @@ -3765,8 +3765,8 @@ class AcousticGuitarFractionFeature(InstrumentFractionFeature): id = 'I12' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Acoustic Guitar Fraction' self.description = ('Fraction of all Note Ons belonging to acoustic guitar patches ' @@ -3791,8 +3791,8 @@ class ElectricGuitarFractionFeature(InstrumentFractionFeature): id = 'I13' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Electric Guitar Fraction' self.description = ('Fraction of all Note Ons belonging to ' @@ -3820,8 +3820,8 @@ class ViolinFractionFeature(InstrumentFractionFeature): id = 'I14' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Violin Fraction' self.description = ('Fraction of all Note Ons belonging to violin patches ' @@ -3849,8 +3849,8 @@ class SaxophoneFractionFeature(InstrumentFractionFeature): ''' id = 'I15' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Saxophone Fraction' self.description = ('Fraction of all Note Ons belonging to saxophone patches ' @@ -3880,8 +3880,8 @@ class BrassFractionFeature(InstrumentFractionFeature): id = 'I16' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Brass Fraction' self.description = ('Fraction of all Note Ons belonging to brass patches ' @@ -3911,8 +3911,8 @@ class WoodwindsFractionFeature(InstrumentFractionFeature): id = 'I17' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Woodwinds Fraction' self.description = ('Fraction of all Note Ons belonging to woodwind patches ' @@ -3940,8 +3940,8 @@ class OrchestralStringsFractionFeature(InstrumentFractionFeature): id = 'I18' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Orchestral Strings Fraction' self.description = ('Fraction of all Note Ons belonging to orchestral strings patches ' @@ -3963,8 +3963,8 @@ class StringEnsembleFractionFeature(InstrumentFractionFeature): id = 'I19' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'String Ensemble Fraction' self.description = ('Fraction of all Note Ons belonging to string ensemble patches ' @@ -3991,8 +3991,8 @@ class ElectricInstrumentFractionFeature(InstrumentFractionFeature): ''' id = 'I20' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Electric Instrument Fraction' self.description = ('Fraction of all Note Ons belonging to electric instrument patches ' diff --git a/music21/features/native.py b/music21/features/native.py index f71d2841ea..660af2ed7a 100644 --- a/music21/features/native.py +++ b/music21/features/native.py @@ -89,8 +89,8 @@ class QualityFeature(featuresModule.FeatureExtractor): ''' id = 'P22' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Quality' self.description = ''' @@ -170,8 +170,8 @@ class TonalCertainty(featuresModule.FeatureExtractor): ''' id = 'K1' # TODO: need id - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Tonal Certainty' self.description = ('A floating point magnitude value that suggest tonal ' @@ -202,8 +202,8 @@ class FirstBeatAttackPrevalence(featuresModule.FeatureExtractor): ''' id = 'MP1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'First Beat Attack Prevalence' self.description = ('Fraction of first beats of a measure that have notes ' @@ -225,8 +225,8 @@ class UniqueNoteQuarterLengths(featuresModule.FeatureExtractor): ''' id = 'QL1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Unique Note Quarter Lengths' self.description = 'The number of unique note quarter lengths.' @@ -254,8 +254,8 @@ class MostCommonNoteQuarterLength(featuresModule.FeatureExtractor): ''' id = 'QL2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Note Quarter Length' self.description = 'The value of the most common quarter length.' @@ -285,8 +285,8 @@ class MostCommonNoteQuarterLengthPrevalence(featuresModule.FeatureExtractor): ''' id = 'QL3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Note Quarter Length Prevalence' self.description = 'Fraction of notes that have the most common quarter length.' @@ -320,8 +320,8 @@ class RangeOfNoteQuarterLengths(featuresModule.FeatureExtractor): ''' id = 'QL4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Range of Note Quarter Lengths' self.description = 'Difference between the longest and shortest quarter lengths.' @@ -359,8 +359,8 @@ class UniquePitchClassSetSimultaneities(featuresModule.FeatureExtractor): ''' id = 'CS1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Unique Pitch Class Set Simultaneities' self.description = 'Number of unique pitch class simultaneities.' @@ -389,8 +389,8 @@ class UniqueSetClassSimultaneities(featuresModule.FeatureExtractor): ''' id = 'CS2' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Unique Set Class Simultaneities' self.description = 'Number of unique set class simultaneities.' @@ -420,8 +420,8 @@ class MostCommonPitchClassSetSimultaneityPrevalence( ''' id = 'CS3' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Pitch Class Set Simultaneity Prevalence' self.description = ('Fraction of all pitch class simultaneities that are ' @@ -465,8 +465,8 @@ class MostCommonSetClassSimultaneityPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS4' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Most Common Set Class Simultaneity Prevalence' self.description = ('Fraction of all set class simultaneities that ' @@ -506,8 +506,8 @@ class MajorTriadSimultaneityPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS5' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Major Triad Simultaneity Prevalence' self.description = 'Percentage of all simultaneities that are major triads.' @@ -538,8 +538,8 @@ class MinorTriadSimultaneityPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS6' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Minor Triad Simultaneity Prevalence' self.description = 'Percentage of all simultaneities that are minor triads.' @@ -570,8 +570,8 @@ class DominantSeventhSimultaneityPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS7' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Dominant Seventh Simultaneity Prevalence' self.description = 'Percentage of all simultaneities that are dominant seventh.' @@ -602,8 +602,8 @@ class DiminishedTriadSimultaneityPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS8' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Diminished Triad Simultaneity Prevalence' self.description = 'Percentage of all simultaneities that are diminished triads.' @@ -641,8 +641,8 @@ class TriadSimultaneityPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS9' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Triad Simultaneity Prevalence' self.description = 'Proportion of all simultaneities that form triads.' @@ -673,8 +673,8 @@ class DiminishedSeventhSimultaneityPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS10' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Diminished Seventh Simultaneity Prevalence' self.description = 'Percentage of all simultaneities that are diminished seventh chords.' @@ -716,8 +716,8 @@ class IncorrectlySpelledTriadPrevalence(featuresModule.FeatureExtractor): ''' id = 'CS11' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Incorrectly Spelled Triad Prevalence' self.description = 'Percentage of all triads that are spelled incorrectly.' @@ -777,8 +777,8 @@ class ChordBassMotionFeature(featuresModule.FeatureExtractor): ''' id = 'CS12' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Chord Bass Motion' self.description = ('12-element vector showing the fraction of chords that move ' @@ -852,8 +852,8 @@ class LandiniCadence(featuresModule.FeatureExtractor): ''' id = 'MC1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Ends With Landini Melodic Contour' self.description = ('Boolean that indicates the presence of a Landini-like ' @@ -921,8 +921,8 @@ class LanguageFeature(featuresModule.FeatureExtractor): ''' id = 'TX1' - def __init__(self, dataOrStream=None, *arguments, **keywords): - super().__init__(dataOrStream=dataOrStream, *arguments, **keywords) + def __init__(self, dataOrStream=None, **keywords): + super().__init__(dataOrStream=dataOrStream, **keywords) self.name = 'Language Feature' self.description = ('Language of the lyrics of the piece given as a numeric ' diff --git a/music21/figuredBass/segment.py b/music21/figuredBass/segment.py index 1f5fd6cb19..8e86253eb0 100644 --- a/music21/figuredBass/segment.py +++ b/music21/figuredBass/segment.py @@ -766,7 +766,9 @@ def allCorrectConsecutivePossibilities(self, segmentB): if not (self._maxPitch == segmentB._maxPitch): raise SegmentException('Two segments with unequal maxPitch cannot be compared.') self._specialResolutionRuleChecking = _compileRules( - self.specialResolutionRules(self.fbRules), 3) + self.specialResolutionRules(self.fbRules), + 3 + ) for (resolutionMethod, args) in self._specialResolutionRuleChecking[True]: return resolutionMethod(segmentB, *args) return self._resolveOrdinarySegment(segmentB) diff --git a/music21/graph/__init__.py b/music21/graph/__init__.py index d3dc300e97..7f31bf81f9 100644 --- a/music21/graph/__init__.py +++ b/music21/graph/__init__.py @@ -213,21 +213,20 @@ def testPlotChordsC(self): s.append(sc.getChord('f4', 'g5', quarterLength=3)) s.append(note.Note('c5', quarterLength=3)) - for args in [ - ('histogram', 'pitch'), - ('histogram', 'pitchclass'), - ('histogram', 'quarterlength'), + for plotType, xValue, yValue in [ + ('histogram', 'pitch', None), + ('histogram', 'pitchclass', None), + ('histogram', 'quarterlength', None), ('scatter', 'pitch', 'quarterlength'), ('scatter', 'pitchspace', 'offset'), ('scatter', 'pitch', 'offset'), - ('scatter', 'dynamics'), - ('bar', 'pitch'), - ('bar', 'pc'), + ('scatter', 'dynamics', None), + ('bar', 'pitch', None), + ('bar', 'pc', None), ('weighted', 'pc', 'duration'), - ('weighted', 'dynamics'), + ('weighted', 'dynamics', None), ]: - # s.plot(*args, doneAction='write') - s.plot(*args, doneAction=None) + s.plot(plotType, xValue=xValue, yValue=yValue, doneAction=None) def testHorizontalInstrumentationB(self): from music21 import corpus diff --git a/music21/graph/plot.py b/music21/graph/plot.py index ad1b77b615..c7e33abede 100644 --- a/music21/graph/plot.py +++ b/music21/graph/plot.py @@ -65,7 +65,7 @@ class derived from Graph. ''' axesClasses: t.Dict[str, t.Type[axis.Axis]] = {'x': axis.Axis, 'y': axis.Axis} - def __init__(self, streamObj=None, recurse=True, *args, **keywords): + def __init__(self, streamObj=None, recurse=True, **keywords): # if not isinstance(streamObj, music21.stream.Stream): if streamObj is not None and not hasattr(streamObj, 'elements'): # pragma: no cover raise PlotStreamException(f'non-stream provided as argument: {streamObj}') @@ -391,8 +391,8 @@ def id(self): # ------------------------------------------------------------------------------ class PlotStream(primitives.Graph, PlotStreamMixin): - def __init__(self, streamObj=None, *args, **keywords): - primitives.Graph.__init__(self, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + primitives.Graph.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) self.axisX = axis.OffsetAxis(self, 'x') @@ -406,8 +406,8 @@ class Scatter(primitives.GraphScatter, PlotStreamMixin): Base class for 2D scatter plots. ''' - def __init__(self, streamObj=None, *args, **keywords): - primitives.GraphScatter.__init__(self, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + primitives.GraphScatter.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) @@ -430,8 +430,8 @@ class ScatterPitchSpaceQuarterLength(Scatter): 'y': axis.PitchSpaceAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) self.axisX.useLogScale = True # need more space for pitch axis labels if 'figureSize' not in keywords: @@ -461,8 +461,8 @@ class ScatterPitchClassQuarterLength(ScatterPitchSpaceQuarterLength): 'y': axis.PitchClassAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) if 'title' not in keywords: self.title = 'Pitch Class by Quarter Length Scatter' @@ -486,8 +486,8 @@ class ScatterPitchClassOffset(Scatter): 'y': axis.PitchClassAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) # need more space for pitch axis labels if 'figureSize' not in keywords: @@ -520,8 +520,8 @@ class ScatterPitchSpaceDynamicSymbol(Scatter): 'y': axis.DynamicsAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) self.axisX.showEnharmonic = False # need more space for pitch axis labels @@ -559,8 +559,8 @@ class Histogram(primitives.GraphHistogram, PlotStreamMixin): 'y': axis.CountingAxis, } - def __init__(self, streamObj=None, *args, **keywords): - primitives.GraphHistogram.__init__(self, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + primitives.GraphHistogram.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) if 'alpha' not in keywords: @@ -635,8 +635,8 @@ class HistogramPitchSpace(Histogram): 'x': axis.PitchSpaceAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) self.axisX.showEnharmonic = False # need more space for pitch axis labels if 'figureSize' not in keywords: @@ -666,8 +666,8 @@ class HistogramPitchClass(Histogram): 'x': axis.PitchClassAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) self.axisX.showEnharmonic = False if 'title' not in keywords: self.title = 'Pitch Class Histogram' @@ -694,8 +694,8 @@ class HistogramQuarterLength(Histogram): 'x': axis.QuarterLengthAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) self.axisX = axis.QuarterLengthAxis(self, 'x') self.axisX.useLogScale = False if 'title' not in keywords: @@ -717,8 +717,8 @@ class ScatterWeighted(primitives.GraphScatterWeighted, PlotStreamMixin): 'z': axis.CountingAxis, } - def __init__(self, streamObj=None, *args, **keywords): - primitives.GraphScatterWeighted.__init__(self, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + primitives.GraphScatterWeighted.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) self.axisZ.countAxes = ('x', 'y') @@ -743,9 +743,9 @@ class ScatterWeightedPitchSpaceQuarterLength(ScatterWeighted): 'y': axis.PitchSpaceAxis, } - def __init__(self, streamObj=None, *args, **keywords): + def __init__(self, streamObj=None, **keywords): super().__init__( - streamObj, *args, **keywords) + streamObj, **keywords) # need more space for pitch axis labels if 'figureSize' not in keywords: self.figureSize = (7, 7) @@ -775,9 +775,9 @@ class ScatterWeightedPitchClassQuarterLength(ScatterWeighted): 'y': axis.PitchClassAxis, } - def __init__(self, streamObj=None, *args, **keywords): + def __init__(self, streamObj=None, **keywords): super().__init__( - streamObj, *args, **keywords) + streamObj, **keywords) # need more space for pitch axis labels if 'figureSize' not in keywords: @@ -809,9 +809,9 @@ class ScatterWeightedPitchSpaceDynamicSymbol(ScatterWeighted): 'y': axis.DynamicsAxis, } - def __init__(self, streamObj=None, *args, **keywords): + def __init__(self, streamObj=None, **keywords): super().__init__( - streamObj, *args, **keywords) + streamObj, **keywords) self.axisX.showEnharmonic = False @@ -856,7 +856,7 @@ class WindowedAnalysis(primitives.GraphColorGrid, PlotStreamMixin): axesClasses: t.Dict[str, t.Type[axis.Axis]] = {'x': axis.OffsetAxis} processorClassDefault: t.Type[discrete.DiscreteAnalysis] = discrete.KrumhanslSchmuckler - def __init__(self, streamObj=None, *args, **keywords): + def __init__(self, streamObj=None, **keywords): self.processorClass = self.processorClassDefault # a discrete processor class. self._processor = None @@ -867,7 +867,7 @@ def __init__(self, streamObj=None, *args, **keywords): self.windowType = 'overlap' self.compressLegend = True - primitives.GraphColorGrid.__init__(self, *args, **keywords) + primitives.GraphColorGrid.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) self.axisX = axis.OffsetAxis(self, 'x') @@ -880,7 +880,7 @@ def processor(self): self._processor = self.processorClass(self.streamObj) # pylint: disable=not-callable return self._processor - def run(self, *args, **keywords): + def run(self, **keywords): ''' actually create the graph... ''' @@ -1034,11 +1034,11 @@ class HorizontalBar(primitives.GraphHorizontalBar, PlotStreamMixin): 'y': axis.PitchSpaceAxis, } - def __init__(self, streamObj=None, *args, colorByPart=False, **keywords): + def __init__(self, streamObj=None, *, colorByPart=False, **keywords): self.colorByPart = colorByPart self._partsToColor: t.Dict[stream.Part, str] = {} - primitives.GraphHorizontalBar.__init__(self, *args, **keywords) + primitives.GraphHorizontalBar.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) self.axisY.hideUnused = False @@ -1146,8 +1146,8 @@ class HorizontalBarPitchClassOffset(HorizontalBar): 'y': axis.PitchClassAxis, } - def __init__(self, streamObj=None, *args, colorByPart=False, **keywords): - super().__init__(streamObj, *args, colorByPart=colorByPart, **keywords) + def __init__(self, streamObj=None, *, colorByPart=False, **keywords): + super().__init__(streamObj, colorByPart=colorByPart, **keywords) self.axisY = axis.PitchClassAxis(self, 'y') self.axisY.hideUnused = False @@ -1173,8 +1173,8 @@ class HorizontalBarPitchSpaceOffset(HorizontalBar): :width: 600 ''' - def __init__(self, streamObj=None, *args, colorByPart=False, **keywords): - super().__init__(streamObj, *args, colorByPart=colorByPart, **keywords) + def __init__(self, streamObj=None, *, colorByPart=False, **keywords): + super().__init__(streamObj, colorByPart=colorByPart, **keywords) if 'figureSize' not in keywords: self.figureSize = (10, 6) @@ -1196,13 +1196,13 @@ class HorizontalBarWeighted(primitives.GraphHorizontalBarWeighted, PlotStreamMix 'segmentByTarget', ) - def __init__(self, streamObj=None, *args, **keywords): + def __init__(self, streamObj=None, **keywords): self.fillByMeasure = False self.segmentByTarget = True self.normalizeByPart = False self.partGroups = None - primitives.GraphHorizontalBarWeighted.__init__(self, *args, **keywords) + primitives.GraphHorizontalBarWeighted.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) def extractData(self): @@ -1274,8 +1274,8 @@ class Dolan(HorizontalBarWeighted): ''' - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) # self.fy = lambda n: n.pitch.pitchClass # self.fyTicks = self.ticksPitchClassUsage @@ -1368,8 +1368,8 @@ class Plot3DBars(primitives.Graph3DBars, PlotStreamMixin): 'z': axis.CountingAxis, } - def __init__(self, streamObj=None, *args, **keywords): - primitives.Graph3DBars.__init__(self, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + primitives.Graph3DBars.__init__(self, **keywords) PlotStreamMixin.__init__(self, streamObj, **keywords) self.axisZ.countAxes = ('x', 'y') @@ -1396,8 +1396,8 @@ class Plot3DBarsPitchSpaceQuarterLength(Plot3DBars): 'y': axis.PitchSpaceAxis, } - def __init__(self, streamObj=None, *args, **keywords): - super().__init__(streamObj, *args, **keywords) + def __init__(self, streamObj=None, **keywords): + super().__init__(streamObj, **keywords) # need more space for pitch axis labels if 'figureSize' not in keywords: @@ -1421,8 +1421,8 @@ class MultiStream(primitives.GraphGroupedVerticalBar, PlotStreamMixin): ''' axesClasses: t.Dict[str, t.Type[axis.Axis]] = {} - def __init__(self, streamList, labelList=None, *args, **keywords): - primitives.GraphGroupedVerticalBar.__init__(self, *args, **keywords) + def __init__(self, streamList, labelList=None, **keywords): + primitives.GraphGroupedVerticalBar.__init__(self, **keywords) PlotStreamMixin.__init__(self, None) if labelList is None: @@ -1468,11 +1468,11 @@ class Features(MultiStream): ''' format = 'features' - def __init__(self, streamList, featureExtractors, labelList=None, *args, **keywords): + def __init__(self, streamList, featureExtractors, labelList=None, **keywords): if labelList is None: labelList = [] - super().__init__(streamList, labelList, *args, **keywords) + super().__init__(streamList, labelList, **keywords) self.featureExtractors = featureExtractors diff --git a/music21/graph/primitives.py b/music21/graph/primitives.py index 09d161cf58..48b4da3c06 100644 --- a/music21/graph/primitives.py +++ b/music21/graph/primitives.py @@ -125,7 +125,7 @@ class Graph(prebase.ProtoM21Object): 'xTickLabelVerticalAlignment', ) - def __init__(self, *args, **keywords): + def __init__(self, **keywords): extm = getExtendedModules() self.plt = extm.plt # wrapper to matplotlib.pyplot @@ -600,11 +600,11 @@ class GraphNetworkxGraph(Graph): 'networkxGraph', 'hideLeftBottomSpines', ) - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.networkxGraph = None self.hideLeftBottomSpines = True - super().__init__(*args, **keywords) + super().__init__(**keywords) extm = getExtendedModules() @@ -695,9 +695,9 @@ class GraphColorGrid(Graph): figureSizeDefault = (9, 6) keywordConfigurables = Graph.keywordConfigurables + ('hideLeftBottomSpines',) - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.hideLeftBottomSpines = True - super().__init__(*args, **keywords) + super().__init__(**keywords) def renderSubplot(self, subplot): # do not need a grid for the outer container @@ -802,10 +802,10 @@ class GraphColorGridLegend(Graph): figureSizeDefault = (5, 1.5) keywordConfigurables = Graph.keywordConfigurables + ('hideLeftBottomSpines',) - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.hideLeftBottomSpines = True - super().__init__(*args, **keywords) + super().__init__(**keywords) if 'title' not in keywords: self.title = 'Legend' @@ -968,11 +968,11 @@ class GraphHorizontalBar(Graph): 'margin', ) - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.barSpace = 8 self.margin = 2 - super().__init__(*args, **keywords) + super().__init__(**keywords) if 'alpha' not in keywords: self.alpha = 0.6 @@ -1077,11 +1077,11 @@ class GraphHorizontalBarWeighted(Graph): 'margin', ) - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.barSpace = 8 self.margin = 0.25 # was 8; determines space between channels - super().__init__(*args, **keywords) + super().__init__(**keywords) # this default alpha is used if not specified per bar if 'alpha' not in keywords: @@ -1220,11 +1220,11 @@ class GraphScatterWeighted(Graph): keywordConfigurables = Graph.keywordConfigurables + ('maxDiameter', 'minDiameter') - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.maxDiameter = 1.25 self.minDiameter = 0.25 - super().__init__(*args, **keywords) + super().__init__(**keywords) if 'alpha' not in keywords: self.alpha = 0.6 @@ -1422,12 +1422,9 @@ class GraphHistogram(Graph): graphType = 'histogram' keywordConfigurables = Graph.keywordConfigurables + ('binWidth',) - def __init__(self, *args, **keywords): - self.binWidth = 0.8 - super().__init__(*args, **keywords) - - if 'alpha' not in keywords: - self.alpha = 0.8 + def __init__(self, *, binWidth: float = 0.8, alpha: float = 0.8, **keywords): + self.binWidth = binWidth + super().__init__(alpha=alpha, **keywords) def renderSubplot(self, subplot): self.figure.subplots_adjust(left=0.15) @@ -1472,12 +1469,12 @@ class GraphGroupedVerticalBar(Graph): keywordConfigurables = Graph.keywordConfigurables + ( 'binWidth', 'roundDigits', 'groupLabelHeight',) - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.binWidth = 1 self.roundDigits = 1 self.groupLabelHeight = 0.0 - super().__init__(*args, **keywords) + super().__init__(**keywords) def labelBars(self, subplot, rects): # attach some text labels @@ -1570,10 +1567,8 @@ class Graph3DBars(Graph): graphType = '3DBars' axisKeys = ('x', 'y', 'z') - def __init__(self, *args, **keywords): - super().__init__(*args, **keywords) - if 'alpha' not in keywords: - self.alpha = 0.8 + def __init__(self, *, alpha: float = 0.8, **keywords): + super().__init__(alpha=alpha, **keywords) if 'colors' not in keywords: self.colors = ['#ff0000', '#00ff00', '#6666ff'] diff --git a/music21/interval.py b/music21/interval.py index 4b5a1afece..fcda5d05ba 100644 --- a/music21/interval.py +++ b/music21/interval.py @@ -2772,7 +2772,7 @@ class Interval(IntervalBase): >>> aInterval.semiSimpleName 'P8' - An interval can also be specified directly + An interval can also be specified directly: >>> aInterval = interval.Interval('m3') >>> aInterval @@ -2791,6 +2791,8 @@ class Interval(IntervalBase): >>> aInterval.isStep False + Some ways of creating half-steps. + >>> aInterval = interval.Interval('half') >>> aInterval @@ -2878,14 +2880,21 @@ class Interval(IntervalBase): True ''' def __init__(self, - *arguments, + arg0: t.Union[str, + int, + float, + 'music21.pitch.Pitch', + 'music21.note.Note', + None] = None, + arg1: t.Union['music21.pitch.Pitch', 'music21.note.Note', None] = None, + *, diatonic: t.Optional[DiatonicInterval] = None, chromatic: t.Optional[ChromaticInterval] = None, - noteStart: t.Optional['music21.note.Note'] = None, - noteEnd: t.Optional['music21.note.Note'] = None, + noteStart: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None, + noteEnd: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None, name: t.Optional[str] = None, **keywords): - # requires either (1) a string ('P5' etc.) or + # requires either (1) a string ('P5' etc.) or int/float # (2) named arguments: # (2a) either both of # diatonic = DiatonicInterval object @@ -2902,45 +2911,53 @@ def __init__(self, self.chromatic: t.Optional[ChromaticInterval] = chromatic # these can be accessed through noteStart and noteEnd properties - self._noteStart = noteStart - self._noteEnd = noteEnd + self._noteStart: t.Optional['music21.note.Note'] = None + self._noteEnd: t.Optional['music21.note.Note'] = None + for attr, possible in (('_noteStart', noteStart), ('_noteEnd', noteEnd)): + forcedNote: t.Optional['music21.note.Note'] + if possible is None: + forcedNote = None + elif hasattr(possible, 'classes') and 'Pitch' in possible.classes: + from music21 import note + forcedNote = note.Note(pitch=possible, duration=self.duration) + self.duration.client = self + else: + forcedNote = t.cast('music21.note.Note', possible) + setattr(self, attr, forcedNote) self.type = '' # harmonic or melodic self.implicitDiatonic = False # is this basically a ChromaticInterval object in disguise? - if len(arguments) == 1 and isinstance(arguments[0], str) or name is not None: + if (arg1 is None and isinstance(arg0, str)) or name is not None: # convert common string representations if name is None: - name = arguments[0] + name = arg0 dInterval, cInterval = _stringToDiatonicChromatic(name) self.diatonic = dInterval self.chromatic = cInterval # if we get a first argument that is a number, treat it as a chromatic # interval creation argument - elif len(arguments) == 1 and common.isNum(arguments[0]): - self.chromatic = ChromaticInterval(arguments[0]) + elif arg1 is None and chromatic is None and common.isNum(arg0): + self.chromatic = ChromaticInterval(arg0) # permit pitches instead of Notes # this requires importing note, which is a bit circular, but necessary - elif (len(arguments) == 2 - and hasattr(arguments[0], 'classes') - and hasattr(arguments[1], 'classes') - and 'Pitch' in arguments[0].classes - and 'Pitch' in arguments[1].classes): + elif (hasattr(arg0, 'classes') + and hasattr(arg1, 'classes') + and 'Pitch' in arg0.classes + and 'Pitch' in arg1.classes): from music21 import note - self._noteStart = note.Note() - self._noteStart.pitch = arguments[0] - self._noteEnd = note.Note() - self._noteEnd.pitch = arguments[1] - - elif (len(arguments) == 2 - and hasattr(arguments[0], 'isNote') - and hasattr(arguments[1], 'isNote') - and arguments[0].isNote is True - and arguments[1].isNote is True): - self._noteStart = arguments[0] - self._noteEnd = arguments[1] + self._noteStart = note.Note(pitch=arg0, duration=self.duration) + self._noteEnd = note.Note(pitch=arg1, duration=self.duration) + self.duration.client = self + + elif (hasattr(arg0, 'isNote') + and hasattr(arg1, 'isNote') + and arg0.isNote is True + and arg1.isNote is True): + self._noteStart = arg0 + self._noteEnd = arg1 # catch case where only one Note is provided if (self.diatonic is None and self.chromatic is None @@ -3296,7 +3313,7 @@ def _diatonicIntervalCentShift(self): return cCents - dCents def transposePitch(self, - p, + p: 'music21.pitch.Pitch', *, reverse=False, maxAccidental=4, diff --git a/music21/key.py b/music21/key.py index ffeb3ba966..0c8d08589e 100644 --- a/music21/key.py +++ b/music21/key.py @@ -1141,7 +1141,7 @@ def deriveByDegree(self, degree, pitchRef): return ret - def _tonalCertaintyCorrelationCoefficient(self, *args, **keywords): + def _tonalCertaintyCorrelationCoefficient(self): # possible measures: if not self.alternateInterpretations: raise KeySignatureException( @@ -1169,10 +1169,7 @@ def _tonalCertaintyCorrelationCoefficient(self, *args, **keywords): # estimate range as 2, normalize between zero and 1 return (absMagnitude * 1) + (leaderSpan * 2) - def tonalCertainty(self, - method='correlationCoefficient', - *args, - **keywords) -> float: + def tonalCertainty(self, method='correlationCoefficient') -> float: ''' Provide a measure of tonal ambiguity for Key determined with one of many methods. @@ -1216,8 +1213,7 @@ def tonalCertainty(self, [] ''' if method == 'correlationCoefficient': - return self._tonalCertaintyCorrelationCoefficient( - args, keywords) + return self._tonalCertaintyCorrelationCoefficient() else: raise ValueError(f'Unknown method: {method}') diff --git a/music21/layout.py b/music21/layout.py index ba63153a2f..1ac02f76f0 100644 --- a/music21/layout.py +++ b/music21/layout.py @@ -120,9 +120,6 @@ class LayoutBase(base.Music21Object): ''' classSortOrder = -10 - def __init__(self, *args, **keywords): - super().__init__(**keywords) - def _reprInternal(self): return '' @@ -134,7 +131,6 @@ class ScoreLayout(LayoutBase): PageLayout objects may be found on Measure or Part Streams. - >>> pl = layout.PageLayout(pageNumber=4, leftMargin=234, rightMargin=124, ... pageHeight=4000, pageWidth=3000, isNew=True) >>> pl.pageNumber @@ -157,7 +153,7 @@ class ScoreLayout(LayoutBase): # for each page are working together. def __init__(self, - *args, + *, scalingMillimeters: t.Union[int, float, None] = None, scalingTenths: t.Union[int, float, None] = None, musicFont: t.Optional[str] = None, @@ -222,15 +218,13 @@ class PageLayout(LayoutBase): True This object represents both and - elements in musicxml - - ## TODO -- make sure that the first pageLayout and systemLayout - for each page are working together. - + elements in musicxml. ''' + # TODO -- make sure that the first pageLayout and systemLayout + # for each page are working together. def __init__(self, - *args, + *, pageNumber: t.Optional[int] = None, leftMargin: t.Union[int, float, None] = None, rightMargin: t.Union[int, float, None] = None, @@ -278,9 +272,8 @@ class SystemLayout(LayoutBase): >>> sl.isNew True ''' - def __init__(self, - *args, + *, leftMargin: t.Union[int, float, None] = None, rightMargin: t.Union[int, float, None] = None, distance: t.Union[int, float, None] = None, @@ -365,7 +358,7 @@ class StaffLayout(LayoutBase): ''', } def __init__(self, - *args, + *, distance: t.Union[int, float, None] = None, staffNumber: t.Union[int, float, None] = None, staffSize: t.Union[int, float, None] = None, @@ -432,13 +425,13 @@ class StaffGroup(spanner.Spanner): :width: 400 ''' def __init__(self, - *arguments, + *spannedElements, name: t.Optional[str] = None, barTogether: t.Literal[True, False, None, 'Mensurstrich'] = True, abbreviation: t.Optional[str] = None, symbol: t.Literal['bracket', 'line', 'grace', 'square'] = None, **keywords): - super().__init__(*arguments, **keywords) + super().__init__(*spannedElements, **keywords) self.name = name or abbreviation # if this group has a name self.abbreviation = abbreviation @@ -754,8 +747,8 @@ class LayoutScore(stream.Opus): it is much faster as it uses a cache. ''' - def __init__(self, *args, **keywords): - super().__init__(*args, **keywords) + def __init__(self, givenElements=None, **keywords): + super().__init__(givenElements, **keywords) self.scoreLayout = None self.measureStart = None self.measureEnd = None @@ -1514,8 +1507,8 @@ class Page(stream.Opus): belongs on a single notated page. ''' - def __init__(self, *args, **keywords): - super().__init__(*args, **keywords) + def __init__(self, givenElements=None, **keywords): + super().__init__(givenElements, **keywords) self.pageNumber = 1 self.measureStart = None self.measureEnd = None @@ -1553,8 +1546,8 @@ class System(stream.Score): Attribute systemNumbering says at what point the numbering of systems resets. It can be either "Score" (default), "Opus", or "Page". ''' - def __init__(self, *args, **keywords): - super().__init__(*args, **keywords) + def __init__(self, givenElements=None, **keywords): + super().__init__(givenElements, **keywords) self.systemNumber = 0 self.pageNumber = 0 @@ -1579,8 +1572,8 @@ class Staff(stream.Part): belongs on a single Staff. ''' - def __init__(self, *args, **keywords): - super().__init__(*args, **keywords) + def __init__(self, givenElements=None, **keywords): + super().__init__(givenElements, **keywords) self.staffNumber = 1 # number in this system NOT GLOBAL self.scoreStaffNumber = 0 diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index ba18e56c7e..1e905a1fb0 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -236,21 +236,27 @@ class Metadata(base.Music21Object): # INITIALIZER # - def __init__(self, *args, **keywords): - super().__init__() - - self._contents: t.Dict[str, t.List[ValueType]] = {} - - # TODO: check pickling, etc. + def __init__(self, **keywords): + m21BaseKeywords = {} + myKeywords = {} # We allow the setting of metadata values (attribute-style) via **keywords. # Any keywords that are uniqueNames, grandfathered workIds, or grandfathered # workId abbreviations can be set this way. - for attr in keywords: + for attr, value in keywords.items(): if attr in properties.ALL_LEGAL_ATTRIBUTES: - setattr(self, attr, keywords[attr]) + myKeywords[attr] = value + else: + m21BaseKeywords[attr] = value + + super().__init__(**m21BaseKeywords) + self._contents: t.Dict[str, t.List[ValueType]] = {} + + for attr, value in myKeywords.items(): + setattr(self, attr, value) self['software'] = [defaults.software] + # TODO: check pickling, etc. # ----------------------------------------------------------------------------- # Public APIs @@ -2402,8 +2408,8 @@ class RichMetadata(Metadata): # INITIALIZER # - def __init__(self, *args, **keywords): - super().__init__(*args, **keywords) + def __init__(self, **keywords): + super().__init__(**keywords) self.ambitus = None self.keySignatureFirst = None self.keySignatures = [] diff --git a/music21/metadata/primitives.py b/music21/metadata/primitives.py index d3bbc1deee..f347fd3eac 100644 --- a/music21/metadata/primitives.py +++ b/music21/metadata/primitives.py @@ -73,7 +73,6 @@ class Date(prebase.ProtoM21Object): >>> a = metadata.Date(year='1843?') >>> a.yearError 'uncertain' - ''' # CLASS VARIABLES # @@ -84,7 +83,7 @@ class Date(prebase.ProtoM21Object): # INITIALIZER # - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.year = None self.month = None self.day = None @@ -1009,7 +1008,7 @@ class Contributor(prebase.ProtoM21Object): # INITIALIZER # def __init__(self, - *args, + *, name: t.Union[str, Text, None] = None, names: t.Iterable[t.Union[str, Text]] = (), role: t.Union[str, Text, None] = None, @@ -1349,8 +1348,7 @@ class Imprint(prebase.ProtoM21Object): r''' An object representation of imprint, or publication. ''' - def __init__(self, *args, **keywords): - self.args = args + def __init__(self, **keywords): self.keywords = keywords # !!!PUB: Publication status. diff --git a/music21/note.py b/music21/note.py index 1d5c17e30a..94477b6fc5 100644 --- a/music21/note.py +++ b/music21/note.py @@ -36,7 +36,7 @@ from music21 import style from music21 import tie from music21 import volume -from music21.common.types import StepName +from music21.common.types import StepName, OffsetQLIn from music21 import environment environLocal = environment.Environment('note') @@ -565,12 +565,13 @@ class GeneralNote(base.Music21Object): } def __init__(self, + *, duration: t.Optional[Duration] = None, lyric: t.Union[None, str, Lyric] = None, **keywords ): if duration is None: - # ensure music21base not automatically create a duration. + # ensure music21base not automatically create a zero duration before we can. if not keywords or ('type' not in keywords and 'quarterLength' not in keywords): tempDuration = Duration(1.0) else: @@ -1895,7 +1896,6 @@ class Rest(GeneralNote): However, the property :attr:`~music21.stream.Stream.notesAndRests` of Streams gets rests as well. - >>> r = note.Rest() >>> r.isRest True @@ -1921,6 +1921,14 @@ class Rest(GeneralNote): >>> r2 = note.Rest(type='whole') >>> r2.duration.quarterLength 4.0 + + Or they can just be specified in without a type, and they'll be evaluated automatically + + >>> r3, r4 = note.Rest('half'), note.Rest(2.0) + >>> r3 == r4 + True + >>> r3.duration.quarterLength + 2.0 ''' isRest = True name = 'rest' @@ -1942,8 +1950,6 @@ class Rest(GeneralNote): update automatically to match the time signature context; and is True. Does not work yet -- functions as True. - # TODO: get it to work. - "auto" is the default, where if the rest value happens to match the current time signature context, then display it as a whole rest, centered, etc. otherwise will display normally. @@ -1952,10 +1958,21 @@ class Rest(GeneralNote): ''', } - def __init__(self, **keywords): + def __init__(self, + length: t.Union[str, OffsetQLIn, None] = None, + *, + stepShift: int = 0, + fullMeasure: t.Literal[True, False, 'auto', 'always'] = 'auto', + **keywords): + if length is not None: + if isinstance(length, str) and 'type' not in keywords: + keywords['type'] = length + elif 'quarterLength' not in keywords: + keywords['quarterLength'] = length super().__init__(**keywords) - self.stepShift = 0 # display line - self.fullMeasure = 'auto' # see docs; True, False, 'always', + self.stepShift = stepShift # display line + # TODO: fullMeasure=='always' does not work properly + self.fullMeasure = fullMeasure # see docs; True, False, 'always', def _reprInternal(self): duration_name = self.duration.fullName.lower() @@ -1980,7 +1997,7 @@ def __eq__(self, other): >>> r1 != r2 False - >>> r2.duration.quarterLength = 4.0/3 + >>> r2.duration.quarterLength = 4/3 >>> r1 == r2 False >>> r1 == note.Note() diff --git a/music21/noteworthy/binaryTranslate.py b/music21/noteworthy/binaryTranslate.py index 33ab51c832..b26401006b 100644 --- a/music21/noteworthy/binaryTranslate.py +++ b/music21/noteworthy/binaryTranslate.py @@ -105,7 +105,7 @@ class NWCConverter: >>> nwcc.staves [] ''' - def __init__(self, *args, **keywords): + def __init__(self, **keywords): self.fileContents = None self.parsePosition = 0 self.version = 200 diff --git a/music21/pitch.py b/music21/pitch.py index 864ddc1b62..04f23f606b 100644 --- a/music21/pitch.py +++ b/music21/pitch.py @@ -4116,7 +4116,6 @@ def getEnharmonic(self: PitchType, *, inPlace=False) -> t.Optional[PitchType]: However, isEnharmonic() for A## and B certainly returns True. - >>> p = pitch.Pitch('d#') >>> print(p.getEnharmonic()) E- @@ -4178,6 +4177,7 @@ def getEnharmonic(self: PitchType, *, inPlace=False) -> t.Optional[PitchType]: post.getHigherEnharmonic(inPlace=True) if inPlace: + self.informClient() return None else: return post diff --git a/music21/scale/intervalNetwork.py b/music21/scale/intervalNetwork.py index dfe6cb02af..00c4e4bbe0 100644 --- a/music21/scale/intervalNetwork.py +++ b/music21/scale/intervalNetwork.py @@ -975,7 +975,7 @@ def nodeIdToEdgeDirections(self, nId): raise IntervalNetworkException('failed to match any edges', nObj) return collection - def degreeModulus(self, degree): + def degreeModulus(self, degree: int) -> int: ''' Return the degree modulus degreeMax - degreeMin. @@ -1009,6 +1009,7 @@ def degreeModulus(self, degree): def nodeNameToNodes(self, nodeId: t.Union[Node, int, Terminus, None], + *, equateTermini=True, permitDegreeModuli=True): ''' @@ -1131,7 +1132,12 @@ def getNext(self, nodeStart, direction): postNode = [self.nodes[nId] for nId in postNodeId] return postEdge, postNode - def processAlteredNodes(self, alteredDegrees, n, p, direction): + def processAlteredNodes(self, + alteredDegrees, + n, + p, + *, + direction): ''' Return an altered pitch for given node, if an alteration is specified in the alteredDegrees dictionary @@ -1173,6 +1179,7 @@ def getUnalteredPitch( self, pitchObj, nodeObj, + *, direction=Direction.BI, alteredDegrees=None ) -> pitch.Pitch: @@ -1196,6 +1203,7 @@ def nextPitch( pitchReference: t.Union[pitch.Pitch, str], nodeName: t.Union[Node, int, Terminus, None], pitchOrigin: t.Union[pitch.Pitch, str], + *, direction: Direction = Direction.ASCENDING, stepSize=1, alteredDegrees=None, @@ -1208,23 +1216,35 @@ def nextPitch( The `nodeName` parameter may be a :class:`~music21.scale.intervalNetwork.Node` object, a node degree, a Terminus Enum, or a None (indicating Terminus.LOW). - The `stepSize` parameter can be configured to permit different sized steps - in the specified direction. >>> edgeList = ['M2', 'M2', 'm2', 'M2', 'M2', 'M2', 'm2'] >>> net = scale.intervalNetwork.IntervalNetwork() >>> net.fillBiDirectedEdges(edgeList) - >>> net.nextPitch('g', 1, 'f#5', scale.Direction.ASCENDING) + >>> net.nextPitch('g', 1, 'f#5', direction=scale.Direction.ASCENDING) - >>> net.nextPitch('g', 1, 'f#5', scale.Direction.DESCENDING) + >>> net.nextPitch('g', 1, 'f#5', direction=scale.Direction.DESCENDING) - >>> net.nextPitch('g', 1, 'f#5', scale.Direction.ASCENDING, 2) # two steps + + The `stepSize` parameter can be configured to permit different sized steps + in the specified direction. + + >>> net.nextPitch('g', 1, 'f#5', + ... direction=scale.Direction.ASCENDING, + ... stepSize=2) + + Altered degrees can be given to temporarily change the pitches returned + without affecting the network as a whole. + >>> alteredDegrees = {2: {'direction': scale.Direction.BI, ... 'interval': interval.Interval('-a1')}} - >>> net.nextPitch('g', 1, 'g2', scale.Direction.ASCENDING, alteredDegrees=alteredDegrees) + >>> net.nextPitch('g', 1, 'g2', + ... direction=scale.Direction.ASCENDING, + ... alteredDegrees=alteredDegrees) - >>> net.nextPitch('g', 1, 'a-2', scale.Direction.ASCENDING, alteredDegrees=alteredDegrees) + >>> net.nextPitch('g', 1, 'a-2', + ... direction=scale.Direction.ASCENDING, + ... alteredDegrees=alteredDegrees) ''' if pitchOrigin is None: @@ -1341,6 +1361,7 @@ def _getCacheKey( pitchReference: pitch.Pitch, minPitch: t.Optional[pitch.Pitch], maxPitch: t.Optional[pitch.Pitch], + *, includeFirst: bool, reverse: t.Optional[bool] = None, # only meaningful for descending ) -> CacheKey: @@ -1370,6 +1391,7 @@ def realizeAscending( nodeId: t.Union[Node, int, Terminus, None] = None, minPitch: t.Union[pitch.Pitch, str, None] = None, maxPitch: t.Union[pitch.Pitch, str, None] = None, + *, alteredDegrees=None, fillMinMaxIfNone=False ) -> t.Tuple[t.List[pitch.Pitch], t.List[t.Union[Terminus, int]]]: @@ -1534,6 +1556,7 @@ def realizeDescending( nodeId: t.Union[Node, int, Terminus, None] = None, minPitch: t.Union[pitch.Pitch, str, None] = None, maxPitch: t.Union[pitch.Pitch, str, None] = None, + *, alteredDegrees=None, includeFirst=False, fillMinMaxIfNone=False, @@ -1637,10 +1660,10 @@ def realizeDescending( if self.deterministic: ck = self._getCacheKey(nodeObj, pitchRef, - minPitchObj, - maxPitchObj, - includeFirst, - reverse, + minPitch=minPitchObj, + maxPitch=maxPitchObj, + includeFirst=includeFirst, + reverse=reverse, ) if ck in self._descendingCache: return self._descendingCache[ck] @@ -2307,7 +2330,6 @@ def sortTerminusLowThenIntThenTerminusHigh(a): return g def plot(self, - *args, **keywords): ''' Given a method and keyword configuration arguments, create and display a plot. @@ -2336,6 +2358,7 @@ def getRelativeNodeId( pitchReference: t.Union[pitch.Pitch, str], nodeId: t.Union[Node, int, Terminus, None], pitchTarget: t.Union[pitch.Pitch, note.Note, str], + *, comparisonAttribute: str = 'ps', direction: Direction = Direction.ASCENDING, alteredDegrees=None @@ -2364,9 +2387,9 @@ def getRelativeNodeId( 0 >>> net.getRelativeNodeId('a', 1, 'c#4') 1 - >>> net.getRelativeNodeId('a', 1, 'c4', comparisonAttribute = 'step') + >>> net.getRelativeNodeId('a', 1, 'c4', comparisonAttribute='step') 1 - >>> net.getRelativeNodeId('a', 1, 'c', comparisonAttribute = 'step') + >>> net.getRelativeNodeId('a', 1, 'c', comparisonAttribute='step') 1 >>> net.getRelativeNodeId('a', 1, 'b-4') is None True diff --git a/music21/scale/test_intervalNetwork.py b/music21/scale/test_intervalNetwork.py index ec509fdbdf..2ebeaabcde 100644 --- a/music21/scale/test_intervalNetwork.py +++ b/music21/scale/test_intervalNetwork.py @@ -500,19 +500,31 @@ def testNextPitch(self): net.fillMelodicMinor() # ascending from known pitches - self.assertEqual(str(net.nextPitch('c4', 1, 'g4', Direction.ASCENDING)), 'A4') - self.assertEqual(str(net.nextPitch('c4', 1, 'a4', Direction.ASCENDING)), 'B4') - self.assertEqual(str(net.nextPitch('c4', 1, 'b4', Direction.ASCENDING)), 'C5') + self.assertEqual(str(net.nextPitch('c4', 1, 'g4', direction=Direction.ASCENDING)), + 'A4') + self.assertEqual(str(net.nextPitch('c4', 1, 'a4', direction=Direction.ASCENDING)), + 'B4') + self.assertEqual(str(net.nextPitch('c4', 1, 'b4', direction=Direction.ASCENDING)), + 'C5') # descending - self.assertEqual(str(net.nextPitch('c4', 1, 'c5', Direction.DESCENDING)), 'B-4') - self.assertEqual(str(net.nextPitch('c4', 1, 'b-4', Direction.DESCENDING)), 'A-4') - self.assertEqual(str(net.nextPitch('c4', 1, 'a-4', Direction.DESCENDING)), 'G4') + self.assertEqual(str(net.nextPitch('c4', 1, 'c5', direction=Direction.DESCENDING)), + 'B-4') + self.assertEqual(str(net.nextPitch('c4', 1, 'b-4', direction=Direction.DESCENDING)), + 'A-4') + self.assertEqual(str(net.nextPitch('c4', 1, 'a-4', + direction=Direction.DESCENDING)), + 'G4') # larger degree sizes - self.assertEqual(str(net.nextPitch('c4', 1, 'c5', Direction.DESCENDING, stepSize=2)), + self.assertEqual(str(net.nextPitch('c4', 1, 'c5', + direction=Direction.DESCENDING, + stepSize=2)), 'A-4') - self.assertEqual(str(net.nextPitch('c4', 1, 'a4', Direction.ASCENDING, stepSize=2)), 'C5') + self.assertEqual(str(net.nextPitch('c4', 1, 'a4', + direction=Direction.ASCENDING, + stepSize=2)), + 'C5') # moving from a non-scale degree @@ -520,34 +532,41 @@ def testNextPitch(self): self.assertEqual( str( net.nextPitch( - 'c4', 1, 'c#4', Direction.ASCENDING, getNeighbor=Direction.ASCENDING + 'c4', 1, 'c#4', + direction=Direction.ASCENDING, + getNeighbor=Direction.ASCENDING ) ), 'E-4' ) # if we get the descending neighbor, we move from c to d - self.assertEqual(str(net.nextPitch('c4', 1, 'c#4', Direction.ASCENDING, + self.assertEqual(str(net.nextPitch('c4', 1, 'c#4', + direction=Direction.ASCENDING, getNeighbor=Direction.DESCENDING)), 'D4') # if on a- and get ascending neighbor, move from a to b- - self.assertEqual(str(net.nextPitch('c4', 1, 'a-', Direction.ASCENDING, + self.assertEqual(str(net.nextPitch('c4', 1, 'a-', + direction=Direction.ASCENDING, getNeighbor=Direction.ASCENDING)), 'B4') # if on a- and get descending neighbor, move from g to a - self.assertEqual(str(net.nextPitch('c4', 1, 'a-', Direction.ASCENDING, + self.assertEqual(str(net.nextPitch('c4', 1, 'a-', + direction=Direction.ASCENDING, getNeighbor=Direction.DESCENDING)), 'A4') # if on b, ascending neighbor, move from c to b- - self.assertEqual(str(net.nextPitch('c4', 1, 'b3', Direction.DESCENDING, + self.assertEqual(str(net.nextPitch('c4', 1, 'b3', + direction=Direction.DESCENDING, getNeighbor=Direction.ASCENDING)), 'B-3') # if on c-4, use mode derivation instead of neighbor, move from b4 to c4 - self.assertEqual(str(net.nextPitch('c4', 1, 'c-4', Direction.ASCENDING)), + self.assertEqual(str(net.nextPitch('c4', 1, 'c-4', + direction=Direction.ASCENDING)), 'C4') self.assertEqual( @@ -565,8 +584,13 @@ def testNextPitch(self): (6, Terminus.HIGH) ) - self.assertEqual(net.getNeighborNodeIds( - pitchReference='c4', nodeName=1, pitchTarget='b-'), (4, 6)) + self.assertEqual( + net.getNeighborNodeIds( + pitchReference='c4', + nodeName=1, + pitchTarget='b-'), + (4, 6) + ) self.assertEqual( net.getNeighborNodeIds( @@ -590,7 +614,7 @@ def testNextPitch(self): 'c4', 1, 'b4', - Direction.DESCENDING, + direction=Direction.DESCENDING, getNeighbor=Direction.DESCENDING)), 'A-4') diff --git a/music21/search/segment.py b/music21/search/segment.py index 64a0bee4a1..e5addbda11 100644 --- a/music21/search/segment.py +++ b/music21/search/segment.py @@ -45,6 +45,7 @@ # noinspection SpellCheckingInspection def translateMonophonicPartToSegments( inputStream, + *, segmentLengths=30, overlap=12, algorithm=None, @@ -113,7 +114,7 @@ def translateMonophonicPartToSegments( # noinspection SpellCheckingInspection -def indexScoreParts(scoreFile, *args, **keywords): +def indexScoreParts(scoreFile, **keywords): r''' Creates segment and measure lists for each part of a score Returns list of dictionaries of segment and measure lists @@ -129,7 +130,7 @@ def indexScoreParts(scoreFile, *args, **keywords): indexedList = [] for part in scoreFileParts: segmentList, measureList = translateMonophonicPartToSegments( - part, *args, **keywords) + part, **keywords) indexedList.append({ 'segmentList': segmentList, 'measureList': measureList, @@ -137,7 +138,7 @@ def indexScoreParts(scoreFile, *args, **keywords): return indexedList -def _indexSingleMulticore(filePath, *args, failFast=False, **keywords): +def _indexSingleMulticore(filePath, failFast=False, **keywords): ''' Index one path in the context of multicore. ''' @@ -147,7 +148,7 @@ def _indexSingleMulticore(filePath, *args, failFast=False, **keywords): shortFp = filePath.name try: - indexOutput = indexOnePath(filePath, *args, **keywords) + indexOutput = indexOnePath(filePath, **keywords) except Exception as e: # pylint: disable=broad-except if not failFast: print(f'Failed on parse/index for, {filePath}: {e}') @@ -163,8 +164,8 @@ def _giveUpdatesMulticore(numRun, totalRun, latestOutput): # noinspection SpellCheckingInspection def indexScoreFilePaths(scoreFilePaths, + *, giveUpdates=False, - *args, runMulticore=True, **keywords): # noinspection PyShadowingNames @@ -193,7 +194,7 @@ def indexScoreFilePaths(scoreFilePaths, else: updateFunction = None - indexFunc = partial(_indexSingleMulticore, *args, **keywords) + indexFunc = partial(_indexSingleMulticore, **keywords) for i in range(len(scoreFilePaths)): if not isinstance(scoreFilePaths[i], pathlib.Path): @@ -224,7 +225,7 @@ def indexScoreFilePaths(scoreFilePaths, return scoreDict -def indexOnePath(filePath, *args, **keywords): +def indexOnePath(filePath, **keywords): ''' Index a single path. Returns a scoreDictEntry ''' @@ -236,7 +237,7 @@ def indexOnePath(filePath, *args, **keywords): else: scoreObj = converter.parse(filePath) - scoreDictEntry = indexScoreParts(scoreObj, *args, **keywords) + scoreDictEntry = indexScoreParts(scoreObj, **keywords) return scoreDictEntry diff --git a/music21/spanner.py b/music21/spanner.py index dd15ab4a7f..c29d8189f8 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -64,7 +64,6 @@ class Spanner(base.Music21Object): as Elliott Carter uses in his second string quartet (he marks them with an arrow). - >>> class CarterAccelerandoSign(spanner.Spanner): ... pass >>> n1 = note.Note('C4') @@ -98,7 +97,6 @@ class Spanner(base.Music21Object): - (2) we can get a stream of spanners (equiv. to getElementsByClass(spanner.Spanner)) by calling the .spanner property on the stream. @@ -107,7 +105,6 @@ class Spanner(base.Music21Object): ... print(thisSpanner) > - (3) we can get the spanner by looking at the list getSpannerSites() on any object that has a spanner: @@ -176,23 +173,21 @@ class Spanner(base.Music21Object): (the Carter example would not print an arrow since that element has no corresponding musicxml representation). - - Implementation notes: + *Implementation notes:* The elements that are included in a spanner are stored in a Stream subclass called :class:`~music21.stream.SpannerStorage` found as the `.spannerStorage` attribute. That Stream has an - attribute called `spannerParent` which links to the original spanner. + attribute called `client` which links to the original spanner. Thus, `spannerStorage` is smart enough to know where it's stored, but it makes deleting/garbage-collecting a spanner a tricky operation: Ex. Prove that the spannedElement Stream is linked to container via - `spannerParent`: + `client`: - >>> sp1.spannerStorage.spannerParent is sp1 + >>> sp1.spannerStorage.client is sp1 True - Spanners have a `.completeStatus` attribute which can be used to find out if all spanned elements have been added yet. It's up to the processing agent to set this, but it could be useful in deciding where to append a spanner. @@ -219,7 +214,7 @@ def __init__(self, # directly # TODO: Move here! along with VariantStorage to variant. - self.spannerStorage = stream.SpannerStorage(spannerParent=self) + self.spannerStorage = stream.SpannerStorage(client=self) # we do not want to auto sort based on offset or class, as # both are meaningless inside this Stream (and only have meaning @@ -231,9 +226,10 @@ def __init__(self, for spannedElement in spannedElements: if isinstance(spannedElement, base.Music21Object): proc.append(spannedElement) - else: + elif spannedElement is not None: proc += spannedElement - self.addSpannedElements(proc) + self.addSpannedElements(proc) + # parameters that spanners need in loading and processing # local id is the id for the local area; used by musicxml self.idLocal = None @@ -410,8 +406,7 @@ def addSpannedElements( self, spannedElements: t.Union[t.Sequence[base.Music21Object], base.Music21Object], - *arguments, - **keywords + *otherElements: base.Music21Object, ): ''' Associate one or more elements with this Spanner. @@ -436,11 +431,11 @@ def addSpannedElements( # add mypy disables because isListLike() performs type-narrowing if not common.isListLike(spannedElements): spannedElements = [spannedElements] # type: ignore[list-item] - if arguments: + if otherElements: # copy spannedElements = spannedElements[:] # type: ignore[index] - # assume all other arguments are spanners - spannedElements += arguments # type: ignore[operator] + # assume all other arguments are music21 objects + spannedElements += otherElements # type: ignore[operator] for c in spannedElements: # type: ignore[union-attr] if c is None: continue @@ -1129,8 +1124,8 @@ class Slur(Spanner): Slurs have `.placement` options ('above' or 'below') and `.lineType` ('dashed' or None) ''' - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, *spannedElements, **keywords): + super().__init__(*spannedElements, **keywords) self.placement = None # can above or below, after musicxml self.lineType = None # can be 'dashed' or None @@ -1167,8 +1162,8 @@ class MultiMeasureRest(Spanner): ''', } - def __init__(self, *arguments, **keywords): - super().__init__(*arguments, **keywords) + def __init__(self, *spannedElements, **keywords): + super().__init__(*spannedElements, **keywords) self._overriddenNumber = None self.useSymbols = keywords.get('useSymbols', defaults.multiMeasureRestUseSymbols) self.maxSymbols = keywords.get('maxSymbols', defaults.multiMeasureRestMaxSymbols) @@ -1279,11 +1274,11 @@ class RepeatBracket(Spanner): '1, 2, 3, 7' ''' def __init__(self, - *arguments, + *spannedElements, number: t.Optional[int] = None, overrideDisplay: t.Optional[str] = None, **keywords): - super().__init__(*arguments, **keywords) + super().__init__(*spannedElements, **keywords) self._number: t.Optional[int] = None # store a range, inclusive of the single number assignment @@ -1477,12 +1472,12 @@ class Ottava(Spanner): validOttavaTypes = ('8va', '8vb', '15ma', '15mb', '22da', '22db') def __init__(self, - *arguments, + *spannedElements, type: str = '8va', # pylint: disable=redefined-builtin transposing: bool = True, placement: t.Literal['above', 'below'] = 'above', **keywords): - super().__init__(*arguments, **keywords) + super().__init__(*spannedElements, **keywords) self._type = None # can be 8va, 8vb, 15ma, 15mb self.type = type @@ -1674,7 +1669,7 @@ class Line(Spanner): def __init__( self, - *arguments, + *spannedElements, lineType: str = 'solid', tick: str = 'down', startTick: str = 'down', @@ -1683,7 +1678,7 @@ def __init__( endHeight: t.Optional[t.Union[int, float]] = None, **keywords ): - super().__init__(*arguments, **keywords) + super().__init__(*spannedElements, **keywords) DEFAULT_TICK = 'down' self._endTick = DEFAULT_TICK # can ne up/down/arrow/both/None @@ -1838,11 +1833,11 @@ class Glissando(Spanner): validSlideTypes = ('chromatic', 'continuous', 'diatonic', 'white', 'black') def __init__(self, - *arguments, + *spannedElements, lineType: str = 'wavy', label: t.Optional[str] = None, **keywords): - super().__init__(*arguments, **keywords) + super().__init__(*spannedElements, **keywords) GLISSANDO_DEFAULT_LINE_TYPE = 'wavy' self._lineType = GLISSANDO_DEFAULT_LINE_TYPE diff --git a/music21/stream/base.py b/music21/stream/base.py index 3b1ec2a78e..b76827cfb9 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -316,7 +316,7 @@ def __init__(self, appendOrInsert: t.Literal['append', 'insert', 'offsets'] = 'offsets', **keywords): # restrictClass: t.Type[M21ObjType] = base.Music21Object, - super().__init__(self, **keywords) + super().__init__(**keywords) self.streamStatus = streamStatus.StreamStatus(self) self._unlinkedDuration = None @@ -3322,7 +3322,12 @@ def _reprTextLine(self, *, addEndTimes=False, useMixedNumerals=False) -> str: # -------------------------------------------------------------------------- # display methods; in the same manner as show() and write() - def plot(self, *args, **keywords): + def plot(self, + plotFormat: str, + xValue: t.Optional[str] = None, + yValue: t.Optional[str] = None, + zValue: t.Optional[str] = None, + **keywords): ''' Given a method and keyword configuration arguments, create and display a plot. @@ -3343,9 +3348,14 @@ def plot(self, *args, **keywords): # import is here to avoid import of matplotlib problems from music21 import graph # first ordered arg can be method type - return graph.plotStream(self, *args, **keywords) + return graph.plotStream(self, + plotFormat, + xValue=xValue, + yValue=yValue, + zValue=zValue, + **keywords) - def analyze(self, *args, **keywords): + def analyze(self, method: str, **keywords): ''' Runs a particular analytical method on the contents of the stream to find its ambitus (range) or key. @@ -3359,7 +3369,6 @@ def analyze(self, *args, **keywords): Example: - >>> s = corpus.parse('bach/bwv66.6') >>> s.analyze('ambitus') @@ -3409,7 +3418,7 @@ def analyze(self, *args, **keywords): from music21.analysis import discrete # pass this stream to the analysis procedure - return discrete.analyzeStream(self, *args, **keywords) + return discrete.analyzeStream(self, method, **keywords) # -------------------------------------------------------------------------- # methods that act on individual elements without requiring @@ -10089,7 +10098,7 @@ def findConsecutiveNotes( returnList.pop() # removes the last-added element return returnList - def melodicIntervals(self, *skipArgs, **skipKeywords): + def melodicIntervals(self, **skipKeywords): ''' Returns a Stream of :class:`~music21.interval.Interval` objects between Notes (and by default, Chords) that follow each other in a stream. @@ -10113,7 +10122,6 @@ def melodicIntervals(self, *skipArgs, **skipKeywords): Returns an empty Stream if there are not at least two elements found by findConsecutiveNotes. - >>> s1 = converter.parse("tinynotation: 3/4 c4 d' r b b'", makeNotation=False) >>> #_DOCS_SHOW s1.show() @@ -14210,16 +14218,15 @@ class SpannerStorage(Stream): object's .sites and find any and all locations that are SpannerStorage objects. - A `spannerParent` keyword argument must be - provided by the Spanner in creation. + A `client` keyword argument must be provided by the Spanner in creation. - TODO v7: rename spannerParent to client. + Changed in v8: spannerParent is renamed client. ''' - def __init__(self, *arguments, spannerParent=None, **keywords): + def __init__(self, givenElements=None, *, client: 'music21.spanner.Spanner', **keywords): # No longer need store as weakref since Py2.3 and better references - self.spannerParent = spannerParent - super().__init__(*arguments, **keywords) + self.client = client + super().__init__(givenElements, **keywords) # must provide a keyword argument with a reference to the spanner # parent could name spannerContainer or other? From 88978c687ca70ecb918c19f88c0ff2de03cdf340 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Tue, 16 Aug 2022 22:44:37 -1000 Subject: [PATCH 30/35] Converting Interval to use Pitches not Note --- music21/interval.py | 181 ++++++++++++++++++++++---------------------- 1 file changed, 90 insertions(+), 91 deletions(-) diff --git a/music21/interval.py b/music21/interval.py index fcda5d05ba..524345c364 100644 --- a/music21/interval.py +++ b/music21/interval.py @@ -2890,6 +2890,8 @@ def __init__(self, *, diatonic: t.Optional[DiatonicInterval] = None, chromatic: t.Optional[ChromaticInterval] = None, + pitchStart: t.Optional['music21.pitch.Pitch'] = None, + pitchEnd: t.Optional['music21.pitch.Pitch'] = None, noteStart: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None, noteEnd: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None, name: t.Optional[str] = None, @@ -2910,20 +2912,21 @@ def __init__(self, self.diatonic: t.Optional[DiatonicInterval] = diatonic self.chromatic: t.Optional[ChromaticInterval] = chromatic - # these can be accessed through noteStart and noteEnd properties - self._noteStart: t.Optional['music21.note.Note'] = None - self._noteEnd: t.Optional['music21.note.Note'] = None - for attr, possible in (('_noteStart', noteStart), ('_noteEnd', noteEnd)): - forcedNote: t.Optional['music21.note.Note'] - if possible is None: - forcedNote = None - elif hasattr(possible, 'classes') and 'Pitch' in possible.classes: - from music21 import note - forcedNote = note.Note(pitch=possible, duration=self.duration) - self.duration.client = self + # these can be accessed through pitchStart and pitchEnd properties + self._pitchStart: t.Optional['music21.pitch.Pitch'] = None + self._pitchEnd: t.Optional['music21.pitch.Pitch'] = None + for attr, possiblePitch, possibleNote in (('_pitchStart', pitchStart, noteStart), + ('_pitchEnd', pitchEnd, noteEnd)): + forcedPitch: t.Optional['music21.pitch.Pitch'] + if possiblePitch is None and possibleNote is None: + forcedPitch = None else: - forcedNote = t.cast('music21.note.Note', possible) - setattr(self, attr, forcedNote) + possible = possiblePitch if possiblePitch is not None else possibleNote + if hasattr(possible, 'classes') and 'Pitch' in possible.classes: + forcedPitch = t.cast('music21.pitch.Pitch', possible) + else: + forcedPitch = t.cast('music21.note.Note', possible).pitch + setattr(self, attr, forcedPitch) self.type = '' # harmonic or melodic self.implicitDiatonic = False # is this basically a ChromaticInterval object in disguise? @@ -2947,30 +2950,28 @@ def __init__(self, and hasattr(arg1, 'classes') and 'Pitch' in arg0.classes and 'Pitch' in arg1.classes): - from music21 import note - self._noteStart = note.Note(pitch=arg0, duration=self.duration) - self._noteEnd = note.Note(pitch=arg1, duration=self.duration) - self.duration.client = self + self._pitchStart = t.cast('music21.pitch.Pitch', arg0) + self._pitchEnd = t.cast('music21.pitch.Pitch', arg1) elif (hasattr(arg0, 'isNote') and hasattr(arg1, 'isNote') and arg0.isNote is True and arg1.isNote is True): - self._noteStart = arg0 - self._noteEnd = arg1 + self._pitchStart = t.cast('music21.pitch.Note', arg0).pitch + self._pitchEnd = t.cast('music21.pitch.Note', arg1).pitch # catch case where only one Note is provided if (self.diatonic is None and self.chromatic is None - and ((self._noteStart is not None and self._noteEnd is None) - or (self._noteEnd is not None and self._noteStart is None))): + and ((self._pitchStart is not None and self._pitchEnd is None) + or (self._pitchEnd is not None and self._pitchStart is None))): raise IntervalException( - 'either both the starting and the ending note must be ' + 'either both the starting and the ending pitch must be ' + 'given or neither can be given. You cannot have one without the other.' ) - if self._noteStart is not None and self._noteEnd is not None: - genericInterval = notesToGeneric(self._noteStart, self._noteEnd) - chromaticInterval = notesToChromatic(self._noteStart, self._noteEnd) + if self._pitchStart is not None and self._noteEnd is not None: + genericInterval = notesToGeneric(self._pitchStart, self._noteEnd) + chromaticInterval = notesToChromatic(self._pitchStart, self._noteEnd) diatonicInterval = intervalsToDiatonic(genericInterval, chromaticInterval) if self.diatonic is not None and diatonicInterval.name != self.diatonic.name: raise IntervalException( @@ -2998,9 +2999,9 @@ def __init__(self, self.chromatic = self.diatonic.getChromatic() if self.diatonic is not None: - if self._noteStart is not None and self._noteEnd is None: - self.noteStart = self._noteStart # this sets noteEnd by property - elif self._noteEnd is not None and self._noteStart is None: + if self._pitchStart is not None and self._noteEnd is None: + self.noteStart = self._pitchStart # this sets noteEnd by property + elif self._noteEnd is not None and self._pitchStart is None: self.noteEnd = self._noteEnd # this sets noteStart by property @@ -3284,8 +3285,8 @@ def cents(self): >>> aInterval.cents 400.0 - >>> n1 = note.Note('C4') - >>> n2 = note.Note('D4') + >>> n1 = pitch.Pitch('C4') + >>> n2 = pitch.Pitch('D4') >>> n2.pitch.microtone = 30 >>> microtoneInterval = interval.Interval(noteStart=n1, noteEnd=n2) >>> microtoneInterval.cents @@ -3481,11 +3482,11 @@ def _diatonicTransposePitch(self, p, maxAccidental, *, inPlace=False): def reverse(self): ''' Return an reversed version of this interval. - If :class:`~music21.note.Note` objects are stored as + If :class:`~music21.pitch.Pitch` objects are stored as `noteStart` and `noteEnd`, these notes are reversed. - >>> n1 = note.Note('c3') - >>> n2 = note.Note('g3') + >>> n1 = pitch.Pitch('c3') + >>> n2 = pitch.Pitch('g3') >>> aInterval = interval.Interval(noteStart=n1, noteEnd=n2) >>> aInterval @@ -3499,90 +3500,89 @@ def reverse(self): >>> aInterval.reverse() ''' - if self._noteStart is not None and self._noteEnd is not None: - return Interval(noteStart=self._noteEnd, noteEnd=self._noteStart) + if self._pitchStart is not None and self._noteEnd is not None: + return Interval(noteStart=self._noteEnd, noteEnd=self._pitchStart) else: return Interval(diatonic=self.diatonic.reverse(), chromatic=self.chromatic.reverse()) - def _getNoteStart(self): + def _getPitchStart(self): ''' - returns self._noteStart + returns self._pitchStart ''' - return self._noteStart + return self._pitchStart - def _setNoteStart(self, n): + def _setPitchStart(self, p): ''' Assuming that this interval is defined, - we can set a new start note (_noteStart) and - automatically have the end note (_noteEnd). + we can set a new start Pitch (_pitchStart) and + automatically set the end Pitch (_pitchEnd). ''' # this is based on the procedure found in transposePitch() and # transposeNote() but offers a more object-oriented approach - pitch2 = self.transposePitch(n.pitch) - self._noteStart = n + pitch2 = self.transposePitch(p) + self._pitchStart = p # prefer to copy the existing noteEnd if it exists, or noteStart if not - self._noteEnd = copy.deepcopy(self._noteEnd or self._noteStart) - self._noteEnd.pitch = pitch2 + self._pitchEnd = pitch2 - noteStart = property(_getNoteStart, _setNoteStart, + pitchStart = property(_getPitchStart, _setPitchStart, doc=''' - Assuming this Interval has been defined, set the start note to a new value; - this will adjust the value of the end note (`noteEnd`). + Assuming this Interval has been defined, set the start pitch to a new value; + this will adjust the value of the end pitch (`pitchEnd`). >>> aInterval = interval.Interval('M3') - >>> aInterval.noteStart = note.Note('c4') - >>> aInterval.noteEnd.nameWithOctave + >>> aInterval.pitchStart = pitch.Pitch('c4') + >>> aInterval.pitchEnd.nameWithOctave 'E4' - >>> n1 = note.Note('c3') - >>> n2 = note.Note('g#3') + >>> n1 = pitch.Pitch('c3') + >>> n2 = pitch.Pitch('g#3') >>> aInterval = interval.Interval(n1, n2) >>> aInterval.name 'A5' - >>> aInterval.noteStart = note.Note('g4') - >>> aInterval.noteEnd.nameWithOctave + >>> aInterval.pitchStart = pitch.Pitch('g4') + >>> aInterval.pitchEnd.nameWithOctave 'D#5' >>> aInterval = interval.Interval('-M3') - >>> aInterval.noteStart = note.Note('c4') - >>> aInterval.noteEnd.nameWithOctave + >>> aInterval.pitchStart = pitch.Pitch('c4') + >>> aInterval.pitchEnd.nameWithOctave 'A-3' >>> aInterval = interval.Interval('M-2') - >>> aInterval.noteStart = note.Note('A#3') - >>> aInterval.noteEnd.nameWithOctave + >>> aInterval.pitchStart = pitch.Pitch('A#3') + >>> aInterval.pitchEnd.nameWithOctave 'G#3' >>> aInterval = interval.Interval('h') >>> aInterval.directedName 'm2' - >>> aInterval.noteStart = note.Note('F#3') - >>> aInterval.noteEnd.nameWithOctave + >>> aInterval.pitchStart = pitch.Pitch('F#3') + >>> aInterval.pitchEnd.nameWithOctave 'G3' ''') def _setNoteEnd(self, n): ''' Assuming that this interval is defined, we can - set a new end note (_noteEnd) and automatically have the start note (_noteStart). + set a new end note (_pitchEnd) and automatically have the start note (_noteStart). ''' # this is based on the procedure found in transposePitch() but offers # a more object-oriented approach pitch1 = self.transposePitch(n.pitch, reverse=True) - self._noteEnd = n - # prefer to copy the noteStart if it exists, or noteEnd if not - self._noteStart = copy.deepcopy(self._noteStart or self._noteEnd) + self._pitchEnd = n + # prefer to copy the noteStart if it exists, or pitchEnd if not + self._noteStart = copy.deepcopy(self._noteStart or self._pitchEnd) self._noteStart.pitch = pitch1 def _getNoteEnd(self): ''' - returns self._noteEnd + returns self._pitchEnd ''' - return self._noteEnd + return self._pitchEnd - noteEnd = property(_getNoteEnd, _setNoteEnd, + pitchEnd = property(_getNoteEnd, _setNoteEnd, doc=''' Assuming this Interval has been defined, set the end note to a new value; this will adjust @@ -3590,26 +3590,26 @@ def _getNoteEnd(self): >>> aInterval = interval.Interval('M3') - >>> aInterval.noteEnd = note.Note('E4') + >>> aInterval.pitchEnd = pitch.Pitch('E4') >>> aInterval.noteStart.nameWithOctave 'C4' >>> aInterval = interval.Interval('m2') - >>> aInterval.noteEnd = note.Note('A#3') + >>> aInterval.pitchEnd = pitch.Pitch('A#3') >>> aInterval.noteStart.nameWithOctave 'G##3' - >>> n1 = note.Note('G#3') - >>> n2 = note.Note('C3') + >>> n1 = pitch.Pitch('G#3') + >>> n2 = pitch.Pitch('C3') >>> aInterval = interval.Interval(n1, n2) >>> aInterval.directedName # downward augmented fifth 'A-5' - >>> aInterval.noteEnd = note.Note('C4') + >>> aInterval.pitchEnd = pitch.Pitch('C4') >>> aInterval.noteStart.nameWithOctave 'G#4' >>> aInterval = interval.Interval('M3') - >>> aInterval.noteEnd = note.Note('A-3') + >>> aInterval.pitchEnd = pitch.Pitch('A-3') >>> aInterval.noteStart.nameWithOctave 'F-3' ''') @@ -3654,7 +3654,7 @@ def getWrittenHigherNote(note1, note2): def getAbsoluteHigherNote(note1, note2): ''' - Given two :class:`~music21.note.Note` objects, + Given two :class:`~music21.note.Note` or :class:`~music21.pitch.Pitch` objects, returns the higher note based on actual frequency. If both pitches are the same, returns the first note given. @@ -3681,15 +3681,15 @@ def getWrittenLowerNote(note1, note2): the same, or the first note if pitch is also the same. - >>> aNote = note.Note('c#3') - >>> bNote = note.Note('d--3') + >>> aNote = pitch.Pitch('c#3') + >>> bNote = pitch.Pitch('d--3') >>> interval.getWrittenLowerNote(aNote, bNote) - + - >>> aNote = note.Note('c#3') - >>> bNote = note.Note('d-3') + >>> aNote = pitch.Pitch('c#3') + >>> bNote = pitch.Pitch('d-3') >>> interval.getWrittenLowerNote(aNote, bNote) - + ''' (p1, p2) = (_extractPitch(note1), _extractPitch(note2)) @@ -3705,15 +3705,15 @@ def getWrittenLowerNote(note1, note2): def getAbsoluteLowerNote(note1, note2): ''' - Given two :class:`~music21.note.Note` objects, returns - the lower note based on actual pitch. + Given two :class:`~music21.note.Note` or :class:`~music21.pitch.Pitch` objects, returns + the lower pitch based on actual pitch. If both pitches are the same, returns the first note given. - >>> aNote = note.Note('c#3') - >>> bNote = note.Note('d--3') + >>> aNote = pitch.Pitch('c#3') + >>> bNote = pitch.Pitch('d--3') >>> interval.getAbsoluteLowerNote(aNote, bNote) - + ''' chromatic = notesToChromatic(note1, note2) semitones = chromatic.semitones @@ -3811,9 +3811,6 @@ def notesToInterval(n1, n2=None): Works equally well with :class:`~music21.pitch.Pitch` objects. - N.B.: MOVE TO PRIVATE USE. Use: interval.Interval(noteStart=aNote, noteEnd=bNote) instead. - Do not remove because used in interval.Interval()! - >>> aNote = note.Note('c4') >>> bNote = note.Note('g5') >>> aInterval = interval.notesToInterval(aNote, bNote) @@ -3851,7 +3848,9 @@ def notesToInterval(n1, n2=None): >>> interval.notesToInterval(pitch.Pitch('e##4'), pitch.Pitch('f--5')) ''' - # note to self: what's going on with the Note() representation in help? + # N.B.: MOVE TO PRIVATE USE. Use: interval.Interval(noteStart=aNote, noteEnd=bNote) instead. + # Do not remove because used in interval.Interval()! + if n2 is None: # this is not done in the constructor originally because of looping problems # with tinyNotationNote @@ -3865,8 +3864,8 @@ def notesToInterval(n1, n2=None): gInt = notesToGeneric(n1, n2) cInt = notesToChromatic(n1, n2) intObj = intervalFromGenericAndChromatic(gInt, cInt) - intObj._noteStart = n1 # use private so as not to trigger resetting behavior - intObj._noteEnd = n2 + intObj._pitchStart = n1.pitch # use private so as not to trigger resetting behavior + intObj._pitchEnd = n2.pitch return intObj From ac2745b5770188b4b773a7486bee037d8b343671 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Wed, 17 Aug 2022 13:00:50 -1000 Subject: [PATCH 31/35] Leave interval rewrite for another PR That was getting way too big to add to this one. --- music21/interval.py | 222 ++++++++++++++++++++------------------------ 1 file changed, 103 insertions(+), 119 deletions(-) diff --git a/music21/interval.py b/music21/interval.py index 524345c364..4b5a1afece 100644 --- a/music21/interval.py +++ b/music21/interval.py @@ -2772,7 +2772,7 @@ class Interval(IntervalBase): >>> aInterval.semiSimpleName 'P8' - An interval can also be specified directly: + An interval can also be specified directly >>> aInterval = interval.Interval('m3') >>> aInterval @@ -2791,8 +2791,6 @@ class Interval(IntervalBase): >>> aInterval.isStep False - Some ways of creating half-steps. - >>> aInterval = interval.Interval('half') >>> aInterval @@ -2880,23 +2878,14 @@ class Interval(IntervalBase): True ''' def __init__(self, - arg0: t.Union[str, - int, - float, - 'music21.pitch.Pitch', - 'music21.note.Note', - None] = None, - arg1: t.Union['music21.pitch.Pitch', 'music21.note.Note', None] = None, - *, + *arguments, diatonic: t.Optional[DiatonicInterval] = None, chromatic: t.Optional[ChromaticInterval] = None, - pitchStart: t.Optional['music21.pitch.Pitch'] = None, - pitchEnd: t.Optional['music21.pitch.Pitch'] = None, - noteStart: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None, - noteEnd: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None, + noteStart: t.Optional['music21.note.Note'] = None, + noteEnd: t.Optional['music21.note.Note'] = None, name: t.Optional[str] = None, **keywords): - # requires either (1) a string ('P5' etc.) or int/float + # requires either (1) a string ('P5' etc.) or # (2) named arguments: # (2a) either both of # diatonic = DiatonicInterval object @@ -2912,66 +2901,59 @@ def __init__(self, self.diatonic: t.Optional[DiatonicInterval] = diatonic self.chromatic: t.Optional[ChromaticInterval] = chromatic - # these can be accessed through pitchStart and pitchEnd properties - self._pitchStart: t.Optional['music21.pitch.Pitch'] = None - self._pitchEnd: t.Optional['music21.pitch.Pitch'] = None - for attr, possiblePitch, possibleNote in (('_pitchStart', pitchStart, noteStart), - ('_pitchEnd', pitchEnd, noteEnd)): - forcedPitch: t.Optional['music21.pitch.Pitch'] - if possiblePitch is None and possibleNote is None: - forcedPitch = None - else: - possible = possiblePitch if possiblePitch is not None else possibleNote - if hasattr(possible, 'classes') and 'Pitch' in possible.classes: - forcedPitch = t.cast('music21.pitch.Pitch', possible) - else: - forcedPitch = t.cast('music21.note.Note', possible).pitch - setattr(self, attr, forcedPitch) + # these can be accessed through noteStart and noteEnd properties + self._noteStart = noteStart + self._noteEnd = noteEnd self.type = '' # harmonic or melodic self.implicitDiatonic = False # is this basically a ChromaticInterval object in disguise? - if (arg1 is None and isinstance(arg0, str)) or name is not None: + if len(arguments) == 1 and isinstance(arguments[0], str) or name is not None: # convert common string representations if name is None: - name = arg0 + name = arguments[0] dInterval, cInterval = _stringToDiatonicChromatic(name) self.diatonic = dInterval self.chromatic = cInterval # if we get a first argument that is a number, treat it as a chromatic # interval creation argument - elif arg1 is None and chromatic is None and common.isNum(arg0): - self.chromatic = ChromaticInterval(arg0) + elif len(arguments) == 1 and common.isNum(arguments[0]): + self.chromatic = ChromaticInterval(arguments[0]) # permit pitches instead of Notes # this requires importing note, which is a bit circular, but necessary - elif (hasattr(arg0, 'classes') - and hasattr(arg1, 'classes') - and 'Pitch' in arg0.classes - and 'Pitch' in arg1.classes): - self._pitchStart = t.cast('music21.pitch.Pitch', arg0) - self._pitchEnd = t.cast('music21.pitch.Pitch', arg1) - - elif (hasattr(arg0, 'isNote') - and hasattr(arg1, 'isNote') - and arg0.isNote is True - and arg1.isNote is True): - self._pitchStart = t.cast('music21.pitch.Note', arg0).pitch - self._pitchEnd = t.cast('music21.pitch.Note', arg1).pitch + elif (len(arguments) == 2 + and hasattr(arguments[0], 'classes') + and hasattr(arguments[1], 'classes') + and 'Pitch' in arguments[0].classes + and 'Pitch' in arguments[1].classes): + from music21 import note + self._noteStart = note.Note() + self._noteStart.pitch = arguments[0] + self._noteEnd = note.Note() + self._noteEnd.pitch = arguments[1] + + elif (len(arguments) == 2 + and hasattr(arguments[0], 'isNote') + and hasattr(arguments[1], 'isNote') + and arguments[0].isNote is True + and arguments[1].isNote is True): + self._noteStart = arguments[0] + self._noteEnd = arguments[1] # catch case where only one Note is provided if (self.diatonic is None and self.chromatic is None - and ((self._pitchStart is not None and self._pitchEnd is None) - or (self._pitchEnd is not None and self._pitchStart is None))): + and ((self._noteStart is not None and self._noteEnd is None) + or (self._noteEnd is not None and self._noteStart is None))): raise IntervalException( - 'either both the starting and the ending pitch must be ' + 'either both the starting and the ending note must be ' + 'given or neither can be given. You cannot have one without the other.' ) - if self._pitchStart is not None and self._noteEnd is not None: - genericInterval = notesToGeneric(self._pitchStart, self._noteEnd) - chromaticInterval = notesToChromatic(self._pitchStart, self._noteEnd) + if self._noteStart is not None and self._noteEnd is not None: + genericInterval = notesToGeneric(self._noteStart, self._noteEnd) + chromaticInterval = notesToChromatic(self._noteStart, self._noteEnd) diatonicInterval = intervalsToDiatonic(genericInterval, chromaticInterval) if self.diatonic is not None and diatonicInterval.name != self.diatonic.name: raise IntervalException( @@ -2999,9 +2981,9 @@ def __init__(self, self.chromatic = self.diatonic.getChromatic() if self.diatonic is not None: - if self._pitchStart is not None and self._noteEnd is None: - self.noteStart = self._pitchStart # this sets noteEnd by property - elif self._noteEnd is not None and self._pitchStart is None: + if self._noteStart is not None and self._noteEnd is None: + self.noteStart = self._noteStart # this sets noteEnd by property + elif self._noteEnd is not None and self._noteStart is None: self.noteEnd = self._noteEnd # this sets noteStart by property @@ -3285,8 +3267,8 @@ def cents(self): >>> aInterval.cents 400.0 - >>> n1 = pitch.Pitch('C4') - >>> n2 = pitch.Pitch('D4') + >>> n1 = note.Note('C4') + >>> n2 = note.Note('D4') >>> n2.pitch.microtone = 30 >>> microtoneInterval = interval.Interval(noteStart=n1, noteEnd=n2) >>> microtoneInterval.cents @@ -3314,7 +3296,7 @@ def _diatonicIntervalCentShift(self): return cCents - dCents def transposePitch(self, - p: 'music21.pitch.Pitch', + p, *, reverse=False, maxAccidental=4, @@ -3482,11 +3464,11 @@ def _diatonicTransposePitch(self, p, maxAccidental, *, inPlace=False): def reverse(self): ''' Return an reversed version of this interval. - If :class:`~music21.pitch.Pitch` objects are stored as + If :class:`~music21.note.Note` objects are stored as `noteStart` and `noteEnd`, these notes are reversed. - >>> n1 = pitch.Pitch('c3') - >>> n2 = pitch.Pitch('g3') + >>> n1 = note.Note('c3') + >>> n2 = note.Note('g3') >>> aInterval = interval.Interval(noteStart=n1, noteEnd=n2) >>> aInterval @@ -3500,89 +3482,90 @@ def reverse(self): >>> aInterval.reverse() ''' - if self._pitchStart is not None and self._noteEnd is not None: - return Interval(noteStart=self._noteEnd, noteEnd=self._pitchStart) + if self._noteStart is not None and self._noteEnd is not None: + return Interval(noteStart=self._noteEnd, noteEnd=self._noteStart) else: return Interval(diatonic=self.diatonic.reverse(), chromatic=self.chromatic.reverse()) - def _getPitchStart(self): + def _getNoteStart(self): ''' - returns self._pitchStart + returns self._noteStart ''' - return self._pitchStart + return self._noteStart - def _setPitchStart(self, p): + def _setNoteStart(self, n): ''' Assuming that this interval is defined, - we can set a new start Pitch (_pitchStart) and - automatically set the end Pitch (_pitchEnd). + we can set a new start note (_noteStart) and + automatically have the end note (_noteEnd). ''' # this is based on the procedure found in transposePitch() and # transposeNote() but offers a more object-oriented approach - pitch2 = self.transposePitch(p) - self._pitchStart = p + pitch2 = self.transposePitch(n.pitch) + self._noteStart = n # prefer to copy the existing noteEnd if it exists, or noteStart if not - self._pitchEnd = pitch2 + self._noteEnd = copy.deepcopy(self._noteEnd or self._noteStart) + self._noteEnd.pitch = pitch2 - pitchStart = property(_getPitchStart, _setPitchStart, + noteStart = property(_getNoteStart, _setNoteStart, doc=''' - Assuming this Interval has been defined, set the start pitch to a new value; - this will adjust the value of the end pitch (`pitchEnd`). + Assuming this Interval has been defined, set the start note to a new value; + this will adjust the value of the end note (`noteEnd`). >>> aInterval = interval.Interval('M3') - >>> aInterval.pitchStart = pitch.Pitch('c4') - >>> aInterval.pitchEnd.nameWithOctave + >>> aInterval.noteStart = note.Note('c4') + >>> aInterval.noteEnd.nameWithOctave 'E4' - >>> n1 = pitch.Pitch('c3') - >>> n2 = pitch.Pitch('g#3') + >>> n1 = note.Note('c3') + >>> n2 = note.Note('g#3') >>> aInterval = interval.Interval(n1, n2) >>> aInterval.name 'A5' - >>> aInterval.pitchStart = pitch.Pitch('g4') - >>> aInterval.pitchEnd.nameWithOctave + >>> aInterval.noteStart = note.Note('g4') + >>> aInterval.noteEnd.nameWithOctave 'D#5' >>> aInterval = interval.Interval('-M3') - >>> aInterval.pitchStart = pitch.Pitch('c4') - >>> aInterval.pitchEnd.nameWithOctave + >>> aInterval.noteStart = note.Note('c4') + >>> aInterval.noteEnd.nameWithOctave 'A-3' >>> aInterval = interval.Interval('M-2') - >>> aInterval.pitchStart = pitch.Pitch('A#3') - >>> aInterval.pitchEnd.nameWithOctave + >>> aInterval.noteStart = note.Note('A#3') + >>> aInterval.noteEnd.nameWithOctave 'G#3' >>> aInterval = interval.Interval('h') >>> aInterval.directedName 'm2' - >>> aInterval.pitchStart = pitch.Pitch('F#3') - >>> aInterval.pitchEnd.nameWithOctave + >>> aInterval.noteStart = note.Note('F#3') + >>> aInterval.noteEnd.nameWithOctave 'G3' ''') def _setNoteEnd(self, n): ''' Assuming that this interval is defined, we can - set a new end note (_pitchEnd) and automatically have the start note (_noteStart). + set a new end note (_noteEnd) and automatically have the start note (_noteStart). ''' # this is based on the procedure found in transposePitch() but offers # a more object-oriented approach pitch1 = self.transposePitch(n.pitch, reverse=True) - self._pitchEnd = n - # prefer to copy the noteStart if it exists, or pitchEnd if not - self._noteStart = copy.deepcopy(self._noteStart or self._pitchEnd) + self._noteEnd = n + # prefer to copy the noteStart if it exists, or noteEnd if not + self._noteStart = copy.deepcopy(self._noteStart or self._noteEnd) self._noteStart.pitch = pitch1 def _getNoteEnd(self): ''' - returns self._pitchEnd + returns self._noteEnd ''' - return self._pitchEnd + return self._noteEnd - pitchEnd = property(_getNoteEnd, _setNoteEnd, + noteEnd = property(_getNoteEnd, _setNoteEnd, doc=''' Assuming this Interval has been defined, set the end note to a new value; this will adjust @@ -3590,26 +3573,26 @@ def _getNoteEnd(self): >>> aInterval = interval.Interval('M3') - >>> aInterval.pitchEnd = pitch.Pitch('E4') + >>> aInterval.noteEnd = note.Note('E4') >>> aInterval.noteStart.nameWithOctave 'C4' >>> aInterval = interval.Interval('m2') - >>> aInterval.pitchEnd = pitch.Pitch('A#3') + >>> aInterval.noteEnd = note.Note('A#3') >>> aInterval.noteStart.nameWithOctave 'G##3' - >>> n1 = pitch.Pitch('G#3') - >>> n2 = pitch.Pitch('C3') + >>> n1 = note.Note('G#3') + >>> n2 = note.Note('C3') >>> aInterval = interval.Interval(n1, n2) >>> aInterval.directedName # downward augmented fifth 'A-5' - >>> aInterval.pitchEnd = pitch.Pitch('C4') + >>> aInterval.noteEnd = note.Note('C4') >>> aInterval.noteStart.nameWithOctave 'G#4' >>> aInterval = interval.Interval('M3') - >>> aInterval.pitchEnd = pitch.Pitch('A-3') + >>> aInterval.noteEnd = note.Note('A-3') >>> aInterval.noteStart.nameWithOctave 'F-3' ''') @@ -3654,7 +3637,7 @@ def getWrittenHigherNote(note1, note2): def getAbsoluteHigherNote(note1, note2): ''' - Given two :class:`~music21.note.Note` or :class:`~music21.pitch.Pitch` objects, + Given two :class:`~music21.note.Note` objects, returns the higher note based on actual frequency. If both pitches are the same, returns the first note given. @@ -3681,15 +3664,15 @@ def getWrittenLowerNote(note1, note2): the same, or the first note if pitch is also the same. - >>> aNote = pitch.Pitch('c#3') - >>> bNote = pitch.Pitch('d--3') + >>> aNote = note.Note('c#3') + >>> bNote = note.Note('d--3') >>> interval.getWrittenLowerNote(aNote, bNote) - + - >>> aNote = pitch.Pitch('c#3') - >>> bNote = pitch.Pitch('d-3') + >>> aNote = note.Note('c#3') + >>> bNote = note.Note('d-3') >>> interval.getWrittenLowerNote(aNote, bNote) - + ''' (p1, p2) = (_extractPitch(note1), _extractPitch(note2)) @@ -3705,15 +3688,15 @@ def getWrittenLowerNote(note1, note2): def getAbsoluteLowerNote(note1, note2): ''' - Given two :class:`~music21.note.Note` or :class:`~music21.pitch.Pitch` objects, returns - the lower pitch based on actual pitch. + Given two :class:`~music21.note.Note` objects, returns + the lower note based on actual pitch. If both pitches are the same, returns the first note given. - >>> aNote = pitch.Pitch('c#3') - >>> bNote = pitch.Pitch('d--3') + >>> aNote = note.Note('c#3') + >>> bNote = note.Note('d--3') >>> interval.getAbsoluteLowerNote(aNote, bNote) - + ''' chromatic = notesToChromatic(note1, note2) semitones = chromatic.semitones @@ -3811,6 +3794,9 @@ def notesToInterval(n1, n2=None): Works equally well with :class:`~music21.pitch.Pitch` objects. + N.B.: MOVE TO PRIVATE USE. Use: interval.Interval(noteStart=aNote, noteEnd=bNote) instead. + Do not remove because used in interval.Interval()! + >>> aNote = note.Note('c4') >>> bNote = note.Note('g5') >>> aInterval = interval.notesToInterval(aNote, bNote) @@ -3848,9 +3834,7 @@ def notesToInterval(n1, n2=None): >>> interval.notesToInterval(pitch.Pitch('e##4'), pitch.Pitch('f--5')) ''' - # N.B.: MOVE TO PRIVATE USE. Use: interval.Interval(noteStart=aNote, noteEnd=bNote) instead. - # Do not remove because used in interval.Interval()! - + # note to self: what's going on with the Note() representation in help? if n2 is None: # this is not done in the constructor originally because of looping problems # with tinyNotationNote @@ -3864,8 +3848,8 @@ def notesToInterval(n1, n2=None): gInt = notesToGeneric(n1, n2) cInt = notesToChromatic(n1, n2) intObj = intervalFromGenericAndChromatic(gInt, cInt) - intObj._pitchStart = n1.pitch # use private so as not to trigger resetting behavior - intObj._pitchEnd = n2.pitch + intObj._noteStart = n1 # use private so as not to trigger resetting behavior + intObj._noteEnd = n2 return intObj From 76de5fae8b5b92b0227c862ffd63cba603a24867 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Wed, 17 Aug 2022 13:34:46 -1000 Subject: [PATCH 32/35] mypy, flake, lint --- music21/braille/test.py | 5 ++++- music21/duration.py | 2 +- music21/expressions.py | 2 +- music21/features/base.py | 17 +++++++++-------- music21/instrument.py | 11 ++++++----- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/music21/braille/test.py b/music21/braille/test.py index b4a7f45309..5a3c010f28 100644 --- a/music21/braille/test.py +++ b/music21/braille/test.py @@ -2706,7 +2706,10 @@ def test_example13_2(self): def xtest_example13_3(self): # Problem: How to plug in wedges into music21? - bm = converter.parse('tinynotation: a1 a1 a1 a1', 'c').flatten() + bm = converter.parse('tinynotation: 4/4 a1 a1 a1 a1').flatten() + commonTime = bm[meter.TimeSignature].first() + if commonTime is not None: # it is not None, but for typing + commonTime.symbol = 'common' bm.makeNotation(inPlace=True, cautionaryNotImmediateRepeat=False) ml = bm.getElementsByClass(stream.Measure) ml[-1].rightBarline = None diff --git a/music21/duration.py b/music21/duration.py index 2f017e9f54..f76527f250 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -3102,7 +3102,7 @@ def __init__(self, newComponents = [] for c in self.components: newComponents.append(DurationTuple(c.type, c.dots, 0.0)) - self.components = newComponents # set new components + self.components = tuple(newComponents) # set new components # make time is encoded in musicxml as divisions; here it can # be encoded as a duration; but should it be the duration suggested by the grace? diff --git a/music21/expressions.py b/music21/expressions.py index 2ea1f79944..1f3eb95ea8 100644 --- a/music21/expressions.py +++ b/music21/expressions.py @@ -1565,7 +1565,7 @@ class ArpeggioMarkSpanner(spanner.Spanner): > ''' def __init__(self, - *spannedElements, + *spannedElements, arpeggioType: str = 'normal', **keywords): super().__init__(*spannedElements, **keywords) diff --git a/music21/features/base.py b/music21/features/base.py index 2a5ce05c57..d9adea2b89 100644 --- a/music21/features/base.py +++ b/music21/features/base.py @@ -11,15 +11,16 @@ # ------------------------------------------------------------------------------ from __future__ import annotations +from collections import Counter +from collections.abc import KeysView import os import pathlib import pickle import typing as t import unittest -from collections import Counter - from music21 import common +from music21.common.types import StreamType from music21 import converter from music21 import corpus from music21 import exceptions21 @@ -307,13 +308,13 @@ def __init__(self, streamObj: stream.Stream, prepareStream=True): self.prepared = None # basic data storage is a dictionary - self.forms = {} + self.forms: t.Dict[str, stream.Stream] = {} - def keys(self): + def keys(self) -> KeysView[str]: # will only return forms that are established return self.forms.keys() - def _prepareStream(self, streamObj): + def _prepareStream(self, streamObj: StreamType) -> StreamType: ''' Common routines done on Streams prior to processing. Returns a new Stream @@ -323,7 +324,7 @@ def _prepareStream(self, streamObj): streamObj = streamObj.stripTies(inPlace=False) return streamObj - def __getitem__(self, key): + def __getitem__(self, key: str) -> stream.Stream: ''' Get a form of this Stream, using a cached version if available. ''' @@ -356,7 +357,7 @@ def __getitem__(self, key): return prepared - def _getIntervalHistogram(self, algorithm='midi'): + def _getIntervalHistogram(self, algorithm='midi') -> t.List[int]: # note that this does not optimize and cache part presentations histo = [0] * 128 # if we have parts, must add one at a time @@ -384,7 +385,7 @@ def _getIntervalHistogram(self, algorithm='midi'): return histo # ---------------------------------------------------------------------------- - def formPartitionByInstrument(self, prepared): + def formPartitionByInstrument(self, prepared: stream.Stream): from music21 import instrument return instrument.partitionByInstrument(prepared) diff --git a/music21/instrument.py b/music21/instrument.py index 6841a6e88e..542334b8be 100644 --- a/music21/instrument.py +++ b/music21/instrument.py @@ -2089,7 +2089,7 @@ def instrumentFromMidiProgram(number: int) -> Instrument: return inst -def partitionByInstrument(streamObj): +def partitionByInstrument(streamObj: 'music21.stream.Stream') -> 'music21.stream.Stream': # noinspection PyShadowingNames ''' Given a single Stream, or a Score or similar multi-part structure, @@ -2212,6 +2212,8 @@ def partitionByInstrument(streamObj): {0.0} {4.0} + Changes in v8: returns the original stream if there are no instruments. + TODO: parts should be in Score Order. Coincidence that this almost works. TODO: use proper recursion to make a copy of the stream. TODO: final barlines should be aligned. @@ -2232,12 +2234,11 @@ def partitionByInstrument(streamObj): sub.extendDuration('Instrument', inPlace=True) # first, find all unique instruments - instrumentIterator = s.recurse().getElementsByClass(Instrument) + instrumentIterator = s[Instrument] if not instrumentIterator: - # TODO(msc): v7 return s. - return None # no partition is available + return s # no partition is available - names = OrderedDict() # store unique names + names: t.OrderedDict[str, t.Dict[str, t.Any]] = OrderedDict() # store unique names for instrumentObj in instrumentIterator: # matching here by instrument name if instrumentObj.instrumentName not in names: From e8928cf290186bf9bd6e1452120d538bcd0d162e Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Wed, 17 Aug 2022 13:41:15 -1000 Subject: [PATCH 33/35] two more lints --- music21/features/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/music21/features/base.py b/music21/features/base.py index d9adea2b89..66a903886e 100644 --- a/music21/features/base.py +++ b/music21/features/base.py @@ -350,7 +350,7 @@ def __getitem__(self, key: str) -> stream.Stream: prepared = self.keysToMethods[lastKey](self, prepared) elif lastKey.startswith('getElementsByClass('): classToGet: str = lastKey[len('getElementsByClass('):-1] - prepared = prepared.getElementsByClass(classToGet) + prepared = prepared.getElementsByClass(classToGet).stream() else: raise AttributeError(f'no such attribute: {lastKey} in {key}') self.forms[subKey] = prepared @@ -361,8 +361,8 @@ def _getIntervalHistogram(self, algorithm='midi') -> t.List[int]: # note that this does not optimize and cache part presentations histo = [0] * 128 # if we have parts, must add one at a time - if self.prepared.hasPartLikeStreams(): - parts = self.prepared.parts + if isinstance(self.prepared, stream.Score): + parts = list(self.prepared.parts) else: parts = [self.prepared] # emulate a list for p in parts: From 90f481f10fdf059033bdddc41d25052cbae78c6b Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Wed, 17 Aug 2022 14:02:33 -1000 Subject: [PATCH 34/35] more mypy --- music21/features/base.py | 8 +++++++- music21/stream/base.py | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/music21/features/base.py b/music21/features/base.py index 66a903886e..42aa0466e0 100644 --- a/music21/features/base.py +++ b/music21/features/base.py @@ -361,6 +361,7 @@ def _getIntervalHistogram(self, algorithm='midi') -> t.List[int]: # note that this does not optimize and cache part presentations histo = [0] * 128 # if we have parts, must add one at a time + parts: t.List[stream.Stream] if isinstance(self.prepared, stream.Score): parts = list(self.prepared.parts) else: @@ -370,8 +371,13 @@ def _getIntervalHistogram(self, algorithm='midi') -> t.List[int]: # noNone means that we will see all connections, even w/ a gap post = p.findConsecutiveNotes(skipRests=True, - skipChords=True, skipGaps=True, noNone=True) + skipChords=True, + skipGaps=True, + noNone=True) for i, n in enumerate(post): + if t.TYPE_CHECKING: + assert isinstance(n, note.Note) + if i < len(post) - 1: # if not last iNext = i + 1 nNext = post[iNext] diff --git a/music21/stream/base.py b/music21/stream/base.py index f6a4498c97..5507e0d910 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -9945,7 +9945,9 @@ def pitches(self) -> t.List[pitch.Pitch]: # -------------------------------------------------------------------------- # interval routines - + # TODO: override routine to show that if noNone is True that there is no None + # and if noNone and skipChords is True, then it can be typed as list[note.Note] + def findConsecutiveNotes( self, *, From 31ac8f2b9abafd5e98e6105b9f3bfd22d6524ae7 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Wed, 17 Aug 2022 14:20:42 -1000 Subject: [PATCH 35/35] findConsecutiveNotes overload typing --- music21/features/base.py | 4 +-- music21/stream/base.py | 66 ++++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/music21/features/base.py b/music21/features/base.py index 42aa0466e0..9620ed4aa2 100644 --- a/music21/features/base.py +++ b/music21/features/base.py @@ -374,10 +374,8 @@ def _getIntervalHistogram(self, algorithm='midi') -> t.List[int]: skipChords=True, skipGaps=True, noNone=True) - for i, n in enumerate(post): - if t.TYPE_CHECKING: - assert isinstance(n, note.Note) + for i, n in enumerate(post): if i < len(post) - 1: # if not last iNext = i + 1 nNext = post[iNext] diff --git a/music21/stream/base.py b/music21/stream/base.py index 5507e0d910..d4246be25d 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -9945,21 +9945,67 @@ def pitches(self) -> t.List[pitch.Pitch]: # -------------------------------------------------------------------------- # interval routines - # TODO: override routine to show that if noNone is True that there is no None - # and if noNone and skipChords is True, then it can be typed as list[note.Note] - + @overload + def findConsecutiveNotes( + self, + *, + skipRests: bool = False, + skipChords: t.Literal[False] = False, + skipUnisons: bool = False, + skipOctaves: bool = False, + skipGaps: bool = False, + getOverlaps: bool = False, + noNone: t.Literal[True], + **keywords + ) -> t.List[note.NotRest]: + return [] + + @overload + def findConsecutiveNotes( + self, + *, + skipRests: bool = False, + skipChords: t.Literal[True], + skipUnisons: bool = False, + skipOctaves: bool = False, + skipGaps: bool = False, + getOverlaps: bool = False, + noNone: t.Literal[True], + **keywords + ) -> t.List[note.Note]: + return [] + + @overload def findConsecutiveNotes( self, *, - skipRests=False, - skipChords=False, - skipUnisons=False, - skipOctaves=False, - skipGaps=False, - getOverlaps=False, - noNone=False, + skipRests: bool = False, + skipChords: bool = False, + skipUnisons: bool = False, + skipOctaves: bool = False, + skipGaps: bool = False, + getOverlaps: bool = False, + noNone: t.Literal[False] = False, **keywords ) -> t.List[t.Union[note.NotRest, None]]: + return [] + + def findConsecutiveNotes( + self, + *, + skipRests: bool = False, + skipChords: bool = False, + skipUnisons: bool = False, + skipOctaves: bool = False, + skipGaps: bool = False, + getOverlaps: bool = False, + noNone: bool = False, + **keywords + ) -> t.Union[ + t.List[t.Union[note.NotRest, None]], + t.List[note.NotRest], + t.List[note.Note], + ]: r''' Returns a list of consecutive *pitched* Notes in a Stream.