diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index 58befa6da..7628593d9 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -211,7 +211,20 @@ def __init__(self, **keywords): self._chordReduction = None # store a chordal reduction of available - def _setScore(self, value): + @property + def score(self): + ''' + Get or set the Score. Setting the score set a deepcopy of the score; the score + set here will not be altered. + + >>> s = corpus.parse('bwv66.6') + >>> sr = analysis.reduction.ScoreReduction() + >>> sr.score = s + ''' + return self._score + + @score.setter + def score(self, value): if not isinstance(value, stream.Stream): raise ScoreReductionException('cannot set a non Stream') if value.hasPartLikeStreams: @@ -223,20 +236,16 @@ def _setScore(self, value): self._score = s self._score.setDerivationMethod('ScoreReduction', recurse=True) - def _getScore(self): - return self._score - - score = property(_getScore, _setScore, doc=''' - Get or set the Score. Setting the score set a deepcopy of the score; the score - set here will not be altered. - - >>> s = corpus.parse('bwv66.6') - >>> sr = analysis.reduction.ScoreReduction() - >>> sr.score = s - ''') - + @property + def chordReduction(self): + ''' + Get or set a Chord reduction as a Stream or Score. Setting the this values + set a deepcopy of the reduction; the reduction set here will not be altered. + ''' + return self._chordReduction - def _setChordReduction(self, value): + @chordReduction.setter + def chordReduction(self, value): if not isinstance(value, stream.Stream): raise ScoreReductionException('cannot set a non Stream') if value.hasPartLikeStreams(): @@ -247,14 +256,6 @@ def _setChordReduction(self, value): s.insert(0, copy.deepcopy(value)) self._chordReduction = s - def _getChordReduction(self): - return self._chordReduction - - chordReduction = property(_getChordReduction, _setChordReduction, doc=''' - Get or set a Chord reduction as a Stream or Score. Setting the this values - set a deepcopy of the reduction; the reduction set here will not be altered. - ''') - def _extractReductionEvents(self, score, removeAfterParsing=True): diff --git a/music21/articulations.py b/music21/articulations.py index 087589a1b..95b03e1f1 100644 --- a/music21/articulations.py +++ b/music21/articulations.py @@ -170,10 +170,20 @@ class name without the leading letter lowercase. className = self.__class__.__name__ return common.camelCaseToHyphen(className, replacement=' ') - def _getVolumeShift(self): + @property + def volumeShift(self): + ''' + Get or set the volumeShift of this Articulation. This value, between -1 and 1, + that is used to shift the final Volume of the object it is attached to. + + >>> at1 = articulations.StrongAccent() + >>> at1.volumeShift > 0.1 + True + ''' return self._volumeShift - def _setVolumeShift(self, value): + @volumeShift.setter + def volumeShift(self, value): # value should be between 0 and 1 if value > 1: value = 1 @@ -181,16 +191,6 @@ def _setVolumeShift(self, value): value = -1 self._volumeShift = value - volumeShift = property(_getVolumeShift, _setVolumeShift, doc=''' - Get or set the volumeShift of this Articulation. This value, between -1 and 1, - that is used to shift the final Volume of the object it is attached to. - - - >>> at1 = articulations.StrongAccent() - >>> at1.volumeShift > 0.1 - True - ''') - # ------------------------------------------------------------------------------ class LengthArticulation(Articulation): ''' diff --git a/music21/bar.py b/music21/bar.py index a8104c3f4..05b1c35f0 100644 --- a/music21/bar.py +++ b/music21/bar.py @@ -150,14 +150,9 @@ def _reprInternal(self): return f'type={self.type}' - def _getType(self): - return self._type - - def _setType(self, value): - self._type = standardizeBarType(value) - - type = property(_getType, _setType, - doc=''' + @property + def type(self): + ''' Get and set the Barline type property. >>> b = bar.Barline() @@ -171,7 +166,12 @@ def _setType(self, value): >>> b.type = 'light-light' >>> b.type 'double' - ''') + ''' + return self._type + + @type.setter + def type(self, value): + self._type = standardizeBarType(value) def musicXMLBarStyle(self): ''' diff --git a/music21/base.py b/music21/base.py index 17d9f0a97..1f01c7aa0 100644 --- a/music21/base.py +++ b/music21/base.py @@ -1307,10 +1307,9 @@ def purgeOrphans(self, excludeStorageStreams=True) -> None: orphans.append(id(s)) for i in orphans: self.sites.removeById(i) - p = self._getActiveSite() # this can be simplified. + p = self.activeSite if p is not None and id(p) == i: - # noinspection PyArgumentList - self._setActiveSite(None) + self.activeSite = None def purgeLocations(self, rescanIsDead=False) -> None: ''' @@ -2419,20 +2418,48 @@ def previous(self, # ------------------------------------------------------------------------- # properties - def _getActiveSite(self): + @property + def activeSite(self): + ''' + A reference to the most-recent object used to + contain this object. In most cases, this will be a + Stream or Stream sub-class. In most cases, an object's + activeSite attribute is automatically set when the + object is attached to a Stream. + + + >>> n = note.Note('C#4') + >>> p = stream.Part() + >>> p.insert(20.0, n) + >>> n.activeSite is p + True + >>> n.offset + 20.0 + + >>> m = stream.Measure() + >>> m.insert(10.0, n) + >>> n.activeSite is m + True + >>> n.offset + 10.0 + >>> n.activeSite = p + >>> n.offset + 20.0 + ''' # can be None if WEAKREF_ACTIVE: if self._activeSite is None: # leave None return None else: # even if current activeSite is not a weakref, this will work - # environLocal.printDebug(['_getActiveSite() called:', + # environLocal.printDebug(['activeSite getter called:', # 'self._activeSite', self._activeSite]) return common.unwrapWeakref(self._activeSite) else: # pragma: no cover return self._activeSite - def _setActiveSite(self, site: stream.Stream|None): - # environLocal.printDebug(['_setActiveSite() called:', 'self', self, 'site', site]) + @activeSite.setter + def activeSite(self, site: stream.Stream|None): + # environLocal.printDebug(['activeSite setter called:', 'self', self, 'site', site]) # NOTE: this is a performance intensive call if site is not None: @@ -2461,35 +2488,6 @@ def _setActiveSite(self, site: stream.Stream|None): else: # pragma: no cover self._activeSite = site - activeSite = property(_getActiveSite, - _setActiveSite, - doc=''' - A reference to the most-recent object used to - contain this object. In most cases, this will be a - Stream or Stream sub-class. In most cases, an object's - activeSite attribute is automatically set when the - object is attached to a Stream. - - - >>> n = note.Note('C#4') - >>> p = stream.Part() - >>> p.insert(20.0, n) - >>> n.activeSite is p - True - >>> n.offset - 20.0 - - >>> m = stream.Measure() - >>> m.insert(10.0, n) - >>> n.activeSite is m - True - >>> n.offset - 10.0 - >>> n.activeSite = p - >>> n.offset - 20.0 - ''') - @property def offset(self) -> OffsetQL: ''' @@ -2826,24 +2824,9 @@ def informSites(self, changedInformation=None): # noinspection PyCallingNonCallable s.coreElementsChanged(updateIsFlat=False, keepIndex=True) - def _getPriority(self): - return self._priority - - def _setPriority(self, value): - ''' - value is an int. - - Informs all sites of the change. + @property + def priority(self): ''' - if not isinstance(value, int): - raise ElementException('priority values must be integers.') - if self._priority != value: - self._priority = value - self.informSites({'changedElement': 'priority', 'priority': value}) - - priority = property(_getPriority, - _setPriority, - doc=''' Get and set the priority integer value. Priority specifies the order of processing from left (lowest number) @@ -2872,7 +2855,21 @@ def _setPriority(self, value): >>> a.priority = 'high' Traceback (most recent call last): music21.base.ElementException: priority values must be integers. - ''') + ''' + return self._priority + + @priority.setter + def priority(self, value): + ''' + value is an int. + + Informs all sites of the change. + ''' + if not isinstance(value, int): + raise ElementException('priority values must be integers.') + if self._priority != value: + self._priority = value + self.informSites({'changedElement': 'priority', 'priority': value}) # ------------------------------------------------------------------------- # display and writing @@ -3948,31 +3945,9 @@ def beatStrength(self) -> float: except Music21ObjectException: return float('nan') - def _getSeconds(self) -> float: - from music21 import tempo - # do not search if duration is zero - if self.duration.quarterLength == 0.0: - return 0.0 - - ti = self.getContextByClass(tempo.TempoIndication) - if ti is None: - return float('nan') - mm = ti.getSoundingMetronomeMark() - # once we have mm, simply pass in this duration - return mm.durationToSeconds(self.duration) - - def _setSeconds(self, value: int|float) -> None: - from music21 import tempo - ti = self.getContextByClass(tempo.TempoIndication) - if ti is None: - raise Music21ObjectException('this object does not have a TempoIndication in Sites') - mm = ti.getSoundingMetronomeMark() - self.duration = mm.secondsToDuration(value) - for s in self.sites.get(excludeNone=True): - if self in s.elements: - s.coreElementsChanged() # highest time is changed. - - seconds = property(_getSeconds, _setSeconds, doc=''' + @property + def seconds(self) -> float: + ''' Get or set the duration of this object in seconds, assuming that this object has a :class:`~music21.tempo.MetronomeMark` or :class:`~music21.tempo.MetricModulation` @@ -4051,7 +4026,30 @@ def _setSeconds(self, value: int|float) -> None: 'half' * Changed in v6.3: return `nan` instead of raising an exception. - ''') + ''' + from music21 import tempo + # do not search if duration is zero + if self.duration.quarterLength == 0.0: + return 0.0 + + ti = self.getContextByClass(tempo.TempoIndication) + if ti is None: + return float('nan') + mm = ti.getSoundingMetronomeMark() + # once we have mm, simply pass in this duration + return mm.durationToSeconds(self.duration) + + @seconds.setter + def seconds(self, value: int|float) -> None: + from music21 import tempo + ti = self.getContextByClass(tempo.TempoIndication) + if ti is None: + raise Music21ObjectException('this object does not have a TempoIndication in Sites') + mm = ti.getSoundingMetronomeMark() + self.duration = mm.secondsToDuration(value) + for s in self.sites.get(excludeNone=True): + if self in s.elements: + s.coreElementsChanged() # highest time is changed. # ------------------------------------------------------------------------------ diff --git a/music21/common/numberTools.py b/music21/common/numberTools.py index fa465cd53..21c798fad 100644 --- a/music21/common/numberTools.py +++ b/music21/common/numberTools.py @@ -14,7 +14,6 @@ from functools import cache import math from math import isclose, gcd -import numbers import random from typing import overload, TYPE_CHECKING import unittest @@ -358,7 +357,7 @@ def opFrac(num: OffsetQLIn) -> OffsetQL: raise TypeError(f'Cannot convert num: {num}') -def mixedNumeral(expr: numbers.Real, +def mixedNumeral(expr: int|float|Fraction, limitDenominator: int = defaults.limitOffsetDenominator) -> str: ''' Returns a string representing a mixedNumeral form of a number diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index 884167c49..2cfea252b 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -113,16 +113,17 @@ def parseFile(self, return self.stream - def _getStream(self): + @property + def stream(self): + ''' + Returns or sets the stream in the converter. Must be defined for subConverter to work. + ''' return self._stream - def _setStream(self, newStream): + @stream.setter + def stream(self, newStream): self._stream = newStream - stream = property(_getStream, _setStream, doc=''' - Returns or sets the stream in the converter. Must be defined for subConverter to work. - ''') - def checkShowAbility(self, **keywords) -> bool: ''' return bool on whether the *system* is diff --git a/music21/corpus/chorales.py b/music21/corpus/chorales.py index 16436b514..fa709545f 100644 --- a/music21/corpus/chorales.py +++ b/music21/corpus/chorales.py @@ -1394,12 +1394,25 @@ def _initializeNumberList(self): # ---Properties # - Numbering System - def _getNumberingSystem(self): + @property + def numberingSystem(self): + ''' + This property determines which numbering + system to iterate through chorales with. + It can be set to 'bwv', 'kalmus', 'baerenreiter', + 'budapest', or 'riemenschneider'. + It can also be set to 'title' in which case the + iterator needs to be given a list + of chorale titles in .titleList. At this time, + the titles need to be exactly as they + appear in the dictionary it queries. + ''' if self._numberingSystem is None: raise BachException('Numbering System not set.') return self._numberingSystem - def _setNumberingSystem(self, value): + @numberingSystem.setter + def numberingSystem(self, value): if value in ['bwv', 'kalmus', 'baerenreiter', 'budapest', 'riemenschneider']: self._numberingSystem = value # initializes the number list and sets current and highest numbers / indices @@ -1410,18 +1423,6 @@ def _setNumberingSystem(self, value): else: raise BachException(f'{value} is not a valid numbering system for Bach Chorales.') - numberingSystem = property(_getNumberingSystem, _setNumberingSystem, - doc=''' - This property determines which numbering - system to iterate through chorales with. - It can be set to 'bwv', 'kalmus', 'baerenreiter', - 'budapest', or 'riemenschneider'. - It can also be set to 'title' in which case the - iterator needs to be given a list - of chorale titles in .titleList. At this time, - the titles need to be exactly as they - appear in the dictionary it queries.''') - # - Title List @property diff --git a/music21/duration.py b/music21/duration.py index 811087db9..0a0d152d7 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -1841,7 +1841,8 @@ def _updateComponents(self): self._componentsNeedUpdating = False # PUBLIC METHODS # - def _getLinked(self) -> bool: + @property + def linked(self) -> bool: ''' Gets or sets the `.linked` property -- if linked (default) then type, dots, tuplets are always coherent with quarterLength. If not, then they are separate. @@ -1865,7 +1866,8 @@ def _getLinked(self) -> bool: ''' return self._linked - def _setLinked(self, value: bool): + @linked.setter + def linked(self, value: bool): if value not in (True, False): raise TypeError(f'Linked can only be True or False, not {value}') if self._quarterLengthNeedsUpdating: @@ -1878,8 +1880,6 @@ def _setLinked(self, value: bool): self._linked = value - linked = property(_getLinked, _setLinked) - def addDurationTuple(self, dur: DurationTuple|Duration|str|OffsetQLIn, *, @@ -2915,33 +2915,9 @@ def quarterLengthNoTuplets(self) -> float: tot += c.quarterLength return tot - def _getQuarterLength(self) -> OffsetQL: - if self._quarterLengthNeedsUpdating: - self._updateQuarterLength() - return self._qtrLength - - def _setQuarterLength(self, value: OffsetQLIn): - if not self.linked: - # linked durations get this check for free from opFrac below. - if not isfinite(value): - raise ValueError( - f'quarterLength must be a finite number, not {value!r}' - ) - self._qtrLength = value - elif (self._qtrLength != value - or self._componentsNeedUpdating # skip a type update for next type check - or self.type == 'inexpressible'): - value = opFrac(value) - if value == 0.0 and self.linked: - self.clear() - self._qtrLength = value - self.expressionIsInferred = True - self._componentsNeedUpdating = True - self._quarterLengthNeedsUpdating = False - - self.informClient() - - quarterLength = property(_getQuarterLength, _setQuarterLength, doc=''' + @property + def quarterLength(self) -> OffsetQL: + ''' Returns the quarter note length or Sets the quarter note length to the specified value. May be expressed as a float or Fraction. @@ -2990,7 +2966,32 @@ def _setQuarterLength(self, value: OffsetQLIn): >>> d.quarterLength = 1/3 >>> d.quarterLength Fraction(1, 3) - ''') + ''' + if self._quarterLengthNeedsUpdating: + self._updateQuarterLength() + return self._qtrLength + + @quarterLength.setter + def quarterLength(self, value: OffsetQLIn): + if not self.linked: + # linked durations get this check for free from opFrac below. + if not isfinite(value): + raise ValueError( + f'quarterLength must be a finite number, not {value!r}' + ) + self._qtrLength = value + elif (self._qtrLength != value + or self._componentsNeedUpdating # skip a type update for next type check + or self.type == 'inexpressible'): + value = opFrac(value) + if value == 0.0 and self.linked: + self.clear() + self._qtrLength = value + self.expressionIsInferred = True + self._componentsNeedUpdating = True + self._quarterLengthNeedsUpdating = False + + self.informClient() @property def tuplets(self) -> tuple[Tuplet, ...]: @@ -3517,7 +3518,7 @@ def fixBrokenTupletDuration(self, tupletGroup: list[note.GeneralNote]) -> None: return firstTup = tupletGroup[0].duration.tuplets[0] totalTupletDuration = opFrac(firstTup.totalTupletLength()) - currentTupletDuration = 0.0 + currentTupletDuration: OffsetQL = 0.0 smallestTupletTypeOrdinal: int = SMALL_SENTINEL largestTupletTypeOrdinal: int = LARGE_SENTINEL diff --git a/music21/dynamics.py b/music21/dynamics.py index 8261a3ba7..ecd20297a 100644 --- a/music21/dynamics.py +++ b/music21/dynamics.py @@ -237,23 +237,9 @@ def __init__(self, value=None, **keywords): def _reprInternal(self): return str(self.value) - def _getValue(self): - return self._value - - def _setValue(self, value): - self._value = value - if self._value in longNames: - self.longName = longNames[self._value] - else: - self.longName = None - - if self._value in englishNames: - self.englishName = englishNames[self._value] - else: - self.englishName = None - - value = property(_getValue, _setValue, - doc=''' + @property + def value(self): + ''' Get or set the value of this dynamic, which sets the long and English names of this Dynamic. The value is a string specification. @@ -272,35 +258,25 @@ def _setValue(self, value): 'loud' >>> p.longName 'forte' - ''') + ''' + return self._value - def _getVolumeScalar(self): - if self._volumeScalar is not None: - return self._volumeScalar - # use default - elif self._value in dynamicStrToScalar: - return dynamicStrToScalar[self._value] + @value.setter + def value(self, value): + self._value = value + if self._value in longNames: + self.longName = longNames[self._value] else: - thisDynamic = self._value - # ignore leading s like in sf - if 's' in thisDynamic: - thisDynamic = thisDynamic[1:] - # ignore closing z like in fz - if thisDynamic[-1] == 'z': - thisDynamic = thisDynamic[:-1] - if thisDynamic in dynamicStrToScalar: - return dynamicStrToScalar[thisDynamic] - else: - return dynamicStrToScalar[None] + self.longName = None - def _setVolumeScalar(self, value): - # we can manually set this to be anything, overriding defaults - if common.isNum(value) and 0 <= value <= 1: - self._volumeScalar = value + if self._value in englishNames: + self.englishName = englishNames[self._value] else: - raise DynamicException(f'cannot set as volume scalar to: {value}') + self.englishName = None - volumeScalar = property(_getVolumeScalar, _setVolumeScalar, doc=r''' + @property + def volumeScalar(self): + r''' Get or set the volume scalar for this dynamic. If not explicitly set, a default volume scalar will be provided. Any number between 0 and 1 can be used to set the volume scalar, overriding the expected behavior. @@ -308,7 +284,6 @@ def _setVolumeScalar(self, value): As mezzo is at 0.5, the unit interval range is doubled for generating final output. The default output is 0.5. - >>> d = dynamics.Dynamic('mf') >>> d.volumeScalar 0.55... @@ -319,7 +294,6 @@ def _setVolumeScalar(self, value): >>> d.value 'mf' - int(volumeScalar \* 127) gives the MusicXML tag >>> xmlOut = musicxml.m21ToXml.GeneralObjectExporter().parse(d).decode('utf-8') @@ -333,7 +307,32 @@ def _setVolumeScalar(self, value): ... - ''') + ''' + if self._volumeScalar is not None: + return self._volumeScalar + # use default + elif self._value in dynamicStrToScalar: + return dynamicStrToScalar[self._value] + else: + thisDynamic = self._value + # ignore leading s like in sf + if 's' in thisDynamic: + thisDynamic = thisDynamic[1:] + # ignore closing z like in fz + if thisDynamic[-1] == 'z': + thisDynamic = thisDynamic[:-1] + if thisDynamic in dynamicStrToScalar: + return dynamicStrToScalar[thisDynamic] + else: + return dynamicStrToScalar[None] + + @volumeScalar.setter + def volumeScalar(self, value): + # we can manually set this to be anything, overriding defaults + if common.isNum(value) and 0 <= value <= 1: + self._volumeScalar = value + else: + raise DynamicException(f'cannot set as volume scalar to: {value}') # ------------------------------------------------------------------------------ diff --git a/music21/expressions.py b/music21/expressions.py index af46e55ee..23674a0d4 100644 --- a/music21/expressions.py +++ b/music21/expressions.py @@ -2724,16 +2724,9 @@ def __init__(self, *spannedElements, **keywords): self._placement = None # can above or below or None, after musicxml - def _getPlacement(self): - return self._placement - - def _setPlacement(self, value): - if value is not None and value.lower() not in ['above', 'below']: - raise TrillExtensionException(f'incorrect placement value: {value}') - if value is not None: - self._placement = value.lower() - - placement = property(_getPlacement, _setPlacement, doc=''' + @property + def placement(self): + ''' Get or set the placement as either above, below, or None. >>> s = stream.Stream() @@ -2745,7 +2738,15 @@ def _setPlacement(self, value): A setting of None implies that the placement will be determined by notation software and no particular placement is demanded. - ''') + ''' + return self._placement + + @placement.setter + def placement(self, value): + if value is not None and value.lower() not in ['above', 'below']: + raise TrillExtensionException(f'incorrect placement value: {value}') + if value is not None: + self._placement = value.lower() class TremoloSpanner(spanner.Spanner): diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index 66aaa12f3..3c93861b2 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -364,12 +364,19 @@ def parseOpusDataCollections(self, dataCollections: list[list[str]]) -> stream.O hdc = HumdrumDataCollection(dc) sc = hdc.parse() sc.id = 'section_' + str(i + 1) - sc.metadata.number = i + 1 + scoreMetadata = sc.metadata + if scoreMetadata is None: + scoreMetadata = metadata.Metadata() + sc.insert(0, scoreMetadata) + scoreMetadata.number = str(i + 1) opus.append(sc) if dataCollections: - opus.metadata = copy.deepcopy(opus.scores[0].metadata) - opus.metadata.number = 0 + opusMetadata = copy.deepcopy(opus.scores[0].metadata) + if opusMetadata is None: + opusMetadata = metadata.Metadata() + opusMetadata.number = '0' + opus.metadata = opusMetadata self.stream = opus return opus @@ -1198,7 +1205,8 @@ def _getParentSpineType(self) -> str: return '' return parentSpine.spineType - def _getSpineType(self) -> str: + @property + def spineType(self) -> str: ''' Searches the current and parent spineType. ''' @@ -1215,11 +1223,10 @@ def _getSpineType(self) -> str: raise HumdrumException('Could not determine spineType ' + 'for spine with id ' + str(self.id)) - def _setSpineType(self, newSpineType: str = '') -> None: + @spineType.setter + def spineType(self, newSpineType: str = '') -> None: self._spineType = newSpineType - spineType = property(_getSpineType, _setSpineType) - def moveElementsIntoMeasures(self, streamIn: stream.Stream) -> stream.Stream: # noinspection PyShadowingNames ''' diff --git a/music21/instrument.py b/music21/instrument.py index 799d5c1a8..0d819a0a9 100644 --- a/music21/instrument.py +++ b/music21/instrument.py @@ -500,7 +500,32 @@ def __init__(self, **keywords): self.midiProgram = 48 - def _getStringPitches(self): + @property + def stringPitches(self): + ''' + stringPitches is a property that stores a list of Pitches (or pitch names, + such as "C4") that represent the pitch of the open strings from lowest to + highest.[*] + + >>> vln1 = instrument.Violin() + >>> [str(p) for p in vln1.stringPitches] + ['G3', 'D4', 'A4', 'E5'] + + instrument.stringPitches are full pitch objects, not just names: + + >>> [x.octave for x in vln1.stringPitches] + [3, 4, 4, 5] + + Scordatura for Scelsi's violin concerto *Anahit*. + (N.B. that string to pitch conversion is happening automatically) + + >>> vln1.stringPitches = ['G3', 'G4', 'B4', 'D4'] + + (`[*]In some tuning methods such as reentrant tuning on the ukulele, + lute, or five-string banjo the order might not strictly be from lowest to + highest. The same would hold true for certain violin scordatura pieces, such + as some of Biber's *Mystery Sonatas*`) + ''' if hasattr(self, '_cachedPitches') and self._cachedPitches is not None: return self._cachedPitches elif not hasattr(self, '_stringPitches'): @@ -509,7 +534,8 @@ def _getStringPitches(self): self._cachedPitches = [pitch.Pitch(x) for x in self._stringPitches] return self._cachedPitches - def _setStringPitches(self, newPitches): + @stringPitches.setter + def stringPitches(self, newPitches): if newPitches and (hasattr(newPitches[0], 'step') or newPitches[0] is None): # newPitches is pitchObjects or something self._stringPitches = newPitches @@ -518,31 +544,6 @@ def _setStringPitches(self, newPitches): self._cachedPitches = None self._stringPitches = newPitches - stringPitches = property(_getStringPitches, _setStringPitches, doc=''' - stringPitches is a property that stores a list of Pitches (or pitch names, - such as "C4") that represent the pitch of the open strings from lowest to - highest.[*] - - >>> vln1 = instrument.Violin() - >>> [str(p) for p in vln1.stringPitches] - ['G3', 'D4', 'A4', 'E5'] - - instrument.stringPitches are full pitch objects, not just names: - - >>> [x.octave for x in vln1.stringPitches] - [3, 4, 4, 5] - - Scordatura for Scelsi's violin concerto *Anahit*. - (N.B. that string to pitch conversion is happening automatically) - - >>> vln1.stringPitches = ['G3', 'G4', 'B4', 'D4'] - - (`[*]In some tuning methods such as reentrant tuning on the ukulele, - lute, or five-string banjo the order might not strictly be from lowest to - highest. The same would hold true for certain violin scordatura pieces, such - as some of Biber's *Mystery Sonatas*`) - ''') - class Violin(StringInstrument): def __init__(self, **keywords): @@ -1125,10 +1126,40 @@ def __init__(self, **keywords): self._percMapPitchToModifier = {} self.midiChannel = 9 # 0-indexed, i.e. MIDI channel 10 - def _getModifier(self): + @property + def modifier(self): + ''' + Returns or sets the modifier for this instrument. A modifier could + be something like "low-floor" for a TomTom or "rimshot" for a SnareDrum. + + If the modifier is in the object's ._modifierToPercMapPitch dictionary + then changing the modifier also changes the .percMapPitch for the object + + >>> bd = instrument.BongoDrums() + >>> bd.modifier + 'high' + + >>> bd.percMapPitch + 60 + >>> bd.modifier = 'low' + >>> bd.percMapPitch + 61 + + Variations on modifiers can also be used and they get normalized: + + >>> wb1 = instrument.Woodblock() + >>> wb1.percMapPitch + 76 + >>> wb1.modifier = 'LO' + >>> wb1.percMapPitch + 77 + >>> wb1.modifier # n.b. -- not LO + 'low' + ''' return self._modifier - def _setModifier(self, modifier): + @modifier.setter + def modifier(self, modifier): modifier = modifier.lower().strip() # BEN: to-do, pull out hyphens, spaces, etc. @@ -1141,36 +1172,6 @@ def _setModifier(self, modifier): self._modifier = modifier - modifier = property(_getModifier, _setModifier, doc=''' - Returns or sets the modifier for this instrument. A modifier could - be something like "low-floor" for a TomTom or "rimshot" for a SnareDrum. - - If the modifier is in the object's ._modifierToPercMapPitch dictionary - then changing the modifier also changes the .percMapPitch for the object - - - >>> bd = instrument.BongoDrums() - >>> bd.modifier - 'high' - - >>> bd.percMapPitch - 60 - >>> bd.modifier = 'low' - >>> bd.percMapPitch - 61 - - Variations on modifiers can also be used and they get normalized: - - >>> wb1 = instrument.Woodblock() - >>> wb1.percMapPitch - 76 - >>> wb1.modifier = 'LO' - >>> wb1.percMapPitch - 77 - >>> wb1.modifier # n.b. -- not LO - 'low' - ''') - class Vibraphone(PitchedPercussion): def __init__(self, **keywords): diff --git a/music21/layout.py b/music21/layout.py index e423c2c81..11dcf60f0 100644 --- a/music21/layout.py +++ b/music21/layout.py @@ -442,10 +442,27 @@ def __init__(self, # -------------------------------------------------------------------------- - def _getBarTogether(self) -> t.Literal[True, False, None, 'Mensurstrich']: + @property + def barTogether(self) -> t.Literal[True, False, None, 'Mensurstrich']: + ''' + Get or set the barTogether value, with either Boolean values + or yes or no strings. Or the string 'Mensurstrich' which + indicates barring between staves but not in staves. + + Currently Mensurstrich is not supported by most exporters. + + >>> sg = layout.StaffGroup() + >>> sg.barTogether = 'yes' + >>> sg.barTogether + True + >>> sg.barTogether = 'Mensurstrich' + >>> sg.barTogether + 'Mensurstrich' + ''' return self._barTogether - def _setBarTogether(self, value: t.Literal[True, False, None, 'Mensurstrich', 'yes', 'no']): + @barTogether.setter + def barTogether(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]: @@ -457,26 +474,20 @@ def _setBarTogether(self, value: t.Literal[True, False, None, 'Mensurstrich', 'y else: raise StaffGroupException(f'the bar together value {value} is not acceptable') - barTogether = property(_getBarTogether, _setBarTogether, doc=''' - Get or set the barTogether value, with either Boolean values - or yes or no strings. Or the string 'Mensurstrich' which - indicates barring between staves but not in staves. - - Currently Mensurstrich is not supported by most exporters. + @property + def symbol(self) -> t.Literal['bracket', 'line', 'brace', 'square']|None: + ''' + Get or set the symbol value, with either Boolean values or yes or no strings. >>> sg = layout.StaffGroup() - >>> sg.barTogether = 'yes' - >>> sg.barTogether - True - >>> sg.barTogether = 'Mensurstrich' - >>> sg.barTogether - 'Mensurstrich' - ''') - - def _getSymbol(self) -> t.Literal['bracket', 'line', 'brace', 'square']|None: + >>> sg.symbol = 'Brace' + >>> sg.symbol + 'brace' + ''' return self._symbol - def _setSymbol(self, value: t.Literal['bracket', 'line', 'brace', 'square']|None): + @symbol.setter + def symbol(self, value: t.Literal['bracket', 'line', 'brace', 'square']|None): if value is None or str(value).lower() == 'none': self._symbol = None elif value.lower() in ['brace', 'line', 'bracket', 'square']: @@ -484,15 +495,6 @@ def _setSymbol(self, value: t.Literal['bracket', 'line', 'brace', 'square']|None else: raise StaffGroupException(f'the symbol value {value} is not acceptable') - 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 - 'brace' - ''') - # --------------------------------------------------------------- # Stream subclasses for layout diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index 7b9472197..e08de8796 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -1456,7 +1456,7 @@ def fileNumber(self) -> str|None: return self._getSingularAttribute('fileNumber') @fileNumber.setter - def fileNumber(self, value: str) -> None: + def fileNumber(self, value: str|int|None) -> None: ''' For type checking only. Does not run. ''' diff --git a/music21/meter/base.py b/music21/meter/base.py index 69d82b271..835752ba9 100644 --- a/music21/meter/base.py +++ b/music21/meter/base.py @@ -195,14 +195,15 @@ def bestTimeSignature(meas: stream.Stream) -> 'music21.meter.TimeSignature': break # numerator is the count of min parts in the sum multiplier = 1 + numeratorFloat = 0.0 while i > 0: - numerator = multiplier * sumDurQL / minDurQL - if numerator == int(numerator): + numeratorFloat = multiplier * sumDurQL / minDurQL + if numeratorFloat == int(numeratorFloat): break multiplier *= 2 i -= 1 - numerator = int(numerator) + numerator = int(numeratorFloat) floatDenominator *= multiplier denominator = int(floatDenominator) # simplifies to "simplest terms," with 4 in denominator, before testing beat strengths diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py index fdc7b0a22..7433a493d 100644 --- a/music21/musicxml/xmlToM21.py +++ b/music21/musicxml/xmlToM21.py @@ -6,7 +6,7 @@ # Christopher Ariza # Jacob Tyler Walls # -# Copyright: Copyright © 2009-2024 Michael Scott Asato Cuthbert +# Copyright: Copyright © 2009-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ from __future__ import annotations @@ -1484,8 +1484,8 @@ def __init__(self, self.staffReferenceList: list[StaffReferenceType] = [] self.lastTimeSignature: meter.TimeSignature|None = None - self.lastMeasureWasShort = False - self.lastMeasureOffset = 0.0 + self.lastMeasureWasShort: bool = False + self.lastMeasureOffset: OffsetQL = 0.0 # a dict of clefs per staff number -- needed for converting rests w/ steps self.lastClefs: dict[int, clef.Clef|None] = {} @@ -2282,7 +2282,7 @@ def adjustTimeAttributesFromMeasure(self, m: stream.Measure): mOffsetShift = mHighestTime else: mOffsetShift = mHighestTime # lastTimeSignatureQuarterLength - if self.lastMeasureWasShort is True: + if self.lastMeasureWasShort: if m.barDurationProportion() < 1.0: m.padAsAnacrusis() # probably a pickup after a repeat or phrase boundary # or something @@ -5161,10 +5161,16 @@ def xmlBarline(self, mxBarline: ET.Element) -> None: rb.addSpannedElements(m) if mxEndingObj.get('type') == 'start': - mxNumber = mxEndingObj.get('number') + mxNumberStr = mxEndingObj.get('number') + if mxNumberStr is None: + warnings.warn(f'number is required on object, {mxEndingObj}', + MusicXMLWarning, + stacklevel=2) + mxNumberStr = '1' + # RepeatBracket handles comma-separated values, such as "1,2" try: - rb.number = mxNumber + rb.number = mxNumberStr except spanner.SpannerException: rb.number = 1 diff --git a/music21/romanText/clercqTemperley.py b/music21/romanText/clercqTemperley.py index b4917e1d4..e3af1d3b3 100644 --- a/music21/romanText/clercqTemperley.py +++ b/music21/romanText/clercqTemperley.py @@ -609,8 +609,7 @@ def toPart( m.number = i + 1 partObj.append(measures) - partObj.insert(0, metadata.Metadata()) - partObj.metadata.title = self.title + partObj.insert(0, metadata.Metadata(title=self.title)) self._partObj = partObj return partObj @@ -672,15 +671,17 @@ def _reprInternal(self) -> str: return f'text={self.text!r}' # -------------------------------------------------------------------------- - def _getParent(self) -> CTSong|None: + @property + def parent(self) -> CTSong|None: + r''' + A reference to the CTSong object housing the CTRule if any. + ''' return common.unwrapWeakref(self._parent) - def _setParent(self, parent: CTSong) -> None: + @parent.setter + def parent(self, parent: CTSong) -> None: self._parent = common.wrapWeakref(parent) - parent = property(_getParent, _setParent, doc=r''' - A reference to the CTSong object housing the CTRule if any. - ''') # -------------------------------------------------------------------------- def expand( @@ -1007,10 +1008,18 @@ def fixupChordAtom(self, atom: str) -> str: return atom # -------------------------------------------------------------------------- - def _setMusicText(self, value: str) -> None: - self._musicText = str(value) + @property + def musicText(self) -> str: + ''' + Gets just the music text of the CTRule, excluding the left hand side and comments - def _getMusicText(self) -> str: + >>> rs = 'In: $BP*3 I IV | I | $BP*3 I IV | I | R |*4 I |*4 % This is a comment' + >>> s = romanText.clercqTemperley.CTRule(rs) + >>> s.text + 'In: $BP*3 I IV | I | $BP*3 I IV | I | R |*4 I |*4 % This is a comment' + >>> s.musicText + '$BP*3 I IV | I | $BP*3 I IV | I | R |*4 I |*4' + ''' if self._musicText: return self._musicText @@ -1025,16 +1034,9 @@ def _getMusicText(self) -> str: self._musicText = text.strip() return self._musicText - musicText = property(_getMusicText, _setMusicText, doc=''' - Gets just the music text of the CTRule, excluding the left hand side and comments - - >>> rs = 'In: $BP*3 I IV | I | $BP*3 I IV | I | R |*4 I |*4 % This is a comment' - >>> s = romanText.clercqTemperley.CTRule(rs) - >>> s.text - 'In: $BP*3 I IV | I | $BP*3 I IV | I | R |*4 I |*4 % This is a comment' - >>> s.musicText - '$BP*3 I IV | I | $BP*3 I IV | I | R |*4 I |*4' - ''') + @musicText.setter + def musicText(self, value: str) -> None: + self._musicText = str(value) @property def comment(self) -> str|None: @@ -1050,7 +1052,18 @@ def comment(self) -> str|None: return self.text[self.text.index('%') + 1:].strip() return None - def _getLHS(self) -> str: + @property + def LHS(self) -> str: + ''' + Get the LHS (Left Hand Side) of the CTRule. + If not specified explicitly but CTtext present, searches + first characters up until ':' for rule and returns string) + + >>> rs = 'In: $BP*3 I IV | R |*4 I |*4 % This is a comment' + >>> s = romanText.clercqTemperley.CTRule(rs) + >>> s.LHS + 'In' + ''' if self._LHS: return self._LHS @@ -1066,20 +1079,10 @@ def _getLHS(self) -> str: else: return '' - def _setLHS(self, value: str) -> None: + @LHS.setter + def LHS(self, value: str) -> None: self._LHS = str(value) - LHS = property(_getLHS, _setLHS, doc=''' - Get the LHS (Left Hand Side) of the CTRule. - If not specified explicitly but CTtext present, searches - first characters up until ':' for rule and returns string) - - >>> rs = 'In: $BP*3 I IV | R |*4 I |*4 % This is a comment' - >>> s = romanText.clercqTemperley.CTRule(rs) - >>> s.LHS - 'In' - ''') - @property def sectionName(self) -> str: ''' diff --git a/music21/romanText/tsvConverter.py b/music21/romanText/tsvConverter.py index 6f3b62875..9c91ac587 100644 --- a/music21/romanText/tsvConverter.py +++ b/music21/romanText/tsvConverter.py @@ -660,21 +660,22 @@ def prepStream(self) -> stream.Score: if self.dcml_version == 1: # This sort of metadata seems to have been removed altogether from the # v2 files - s.insert(0, metadata.Metadata()) + md = metadata.Metadata() + s.insert(0, md) firstEntry = self.chordList[0] # Any entry will do title = [] if 'op' in firstEntry.extra: - s.metadata.opusNumber = firstEntry.extra['op'] - title.append('Op' + s.metadata.opusNumber) + md.opusNumber = firstEntry.extra['op'] + title.append('Op' + firstEntry.extra['op']) if 'no' in firstEntry.extra: - s.metadata.number = firstEntry.extra['no'] - title.append('No' + s.metadata.number) + md.number = firstEntry.extra['no'] + title.append('No' + firstEntry.extra['no']) if 'mov' in firstEntry.extra: - s.metadata.movementNumber = firstEntry.extra['mov'] - title.append('Mov' + s.metadata.movementNumber) + md.movementNumber = firstEntry.extra['mov'] + title.append('Mov' + firstEntry.extra['mov']) if title: - s.metadata.title = '_'.join(title) + md.title = '_'.join(title) startingKeySig = str(self.chordList[0].global_key) ks = key.Key(startingKeySig) @@ -816,9 +817,10 @@ def _m21ToTsv_v1(self) -> list[list[str]]: thisEntry.timesig = '' else: thisEntry.timesig = ts.ratioString - thisEntry.extra['op'] = self.m21Stream.metadata.opusNumber or '' - thisEntry.extra['no'] = self.m21Stream.metadata.number or '' - thisEntry.extra['mov'] = self.m21Stream.metadata.movementNumber or '' + md = self.m21Stream.metadata + thisEntry.extra['op'] = (md.opusNumber or '') if md is not None else '' + thisEntry.extra['no'] = (md.number or '') if md is not None else '' + thisEntry.extra['mov'] = (md.movementNumber or '') if md is not None else '' thisEntry.length = thisRN.quarterLength thisEntry.global_key = global_key thisEntry.local_key = thisRN.key.tonicPitchNameWithCase diff --git a/music21/sites.py b/music21/sites.py index 9d9598f33..b09a695cf 100644 --- a/music21/sites.py +++ b/music21/sites.py @@ -114,7 +114,8 @@ def _reprInternal(self): return f'{self.siteIndex}/{self.globalSiteIndex} to {siteRepr}' - def _getAndUnwrapSite(self): + @property + def site(self): if WEAKREF_ACTIVE: ret = common.unwrapWeakref(self.siteWeakref) else: @@ -125,15 +126,14 @@ def _getAndUnwrapSite(self): return ret - def _setAndWrapSite(self, site): + @site.setter + def site(self, site): if WEAKREF_ACTIVE: self.siteWeakref = common.wrapWeakref(site) else: self.siteWeakref = site self.isDead = False - site = property(_getAndUnwrapSite, _setAndWrapSite) - # called before pickling. def __getstate__(self): currentSite = None diff --git a/music21/spanner.py b/music21/spanner.py index 0ae0bb55f..681f7e03c 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -1772,10 +1772,21 @@ def _numberSpanIsContiguous(self) -> bool: return common.contiguousList(self.numberRange) # property to enforce numerical numbers - def _getNumber(self) -> str: + @property + def number(self) -> str: ''' - This must return a string, as we may have single numbers or lists. - For a raw numerical list, look at `.numberRange`. + Get or set the number -- returning a string always, as we may have + single numbers or lists. For a raw numerical list, look at `.numberRange`. + + >>> rb = spanner.RepeatBracket() + >>> rb.number + '' + >>> rb.number = '5-7' + >>> rb.number + '5-7' + >>> rb.numberRange + [5, 6, 7] + >>> rb.number = 1 ''' if len(self.numberRange) == 1: if self.numberRange[0] == 0: @@ -1789,10 +1800,9 @@ def _getNumber(self) -> str: else: # range of values return f'{self.numberRange[0]}-{self.numberRange[-1]}' - def _setNumber(self, value: int|str|Iterable[int]): - ''' - Set the bracket number. There may be a range of values provided. - ''' + @number.setter + def number(self, value: int|str|Iterable[int]): + # There may be a range of values provided. if value == '': # undefined. self.numberRange = [0] @@ -1822,20 +1832,6 @@ def _setNumber(self, value: int|str|Iterable[int]): else: raise SpannerException(f'number for RepeatBracket must be a number, not {value!r}') - number = property(_getNumber, _setNumber, doc=''' - Get or set the number -- returning a string always. - - >>> rb = spanner.RepeatBracket() - >>> rb.number - '' - >>> rb.number = '5-7' - >>> rb.number - '5-7' - >>> rb.numberRange - [5, 6, 7] - >>> rb.number = 1 - ''') - def _reprInternal(self): if self.overrideDisplay is not None: msg = self.overrideDisplay + ' ' @@ -1929,10 +1925,24 @@ def __init__(self, self.placement = placement # can above or below, after musicxml self.transposing = transposing - def _getType(self): + @property + def type(self): + ''' + Get or set Ottava type. This can be set by as complete string + (such as 8va or 15mb) or with a pair specifying size and direction. + + >>> os = spanner.Ottava() + >>> os.type = '8vb' + >>> os.type + '8vb' + >>> os.type = 15, 'down' + >>> os.type + '15mb' + ''' return self._type - def _setType(self, newType): + @type.setter + def type(self, newType): if common.isNum(newType) and newType in (8, 15): if newType == 8: self._type = '8va' @@ -1959,19 +1969,6 @@ def _setType(self, newType): f'cannot create Ottava of type: {newType}') self._type = newType.lower() - type = property(_getType, _setType, doc=''' - Get or set Ottava type. This can be set by as complete string - (such as 8va or 15mb) or with a pair specifying size and direction. - - >>> os = spanner.Ottava() - >>> os.type = '8vb' - >>> os.type - '8vb' - >>> os.type = 15, 'down' - >>> os.type - '15mb' - ''') - def _reprInternal(self): transposing = 'transposing' if not self.transposing: @@ -2155,62 +2152,56 @@ def __init__( if startHeight is not None: self.startHeight = startHeight # use property - def _getEndTick(self): + @property + def endTick(self): + ''' + Get or set the endTick property. + ''' return self._endTick - def _setEndTick(self, value): + @endTick.setter + def endTick(self, value): if value.lower() not in self.validTickTypes: raise SpannerException(f'not a valid value: {value}') self._endTick = value.lower() - endTick = property(_getEndTick, _setEndTick, doc=''' - Get or set the endTick property. - ''') - - def _getStartTick(self): - return self._startTick - - def _setStartTick(self, value): - if value.lower() not in self.validTickTypes: - raise SpannerException(f'not a valid value: {value}') - self._startTick = value.lower() - - startTick = property(_getStartTick, _setStartTick, doc=''' + @property + def startTick(self): + ''' Get or set the startTick property. - ''') - - def _getTick(self): - return self._startTick # just returning start + ''' + return self._startTick - def _setTick(self, value): + @startTick.setter + def startTick(self, value): if value.lower() not in self.validTickTypes: raise SpannerException(f'not a valid value: {value}') self._startTick = value.lower() - self._endTick = value.lower() - tick = property(_getTick, _setTick, doc=''' + @property + def tick(self): + ''' Set the start and end tick to the same value - >>> b = spanner.Line() >>> b.tick = 'arrow' >>> b.startTick 'arrow' >>> b.endTick 'arrow' - ''') - - def _getLineType(self): - return self._lineType + ''' + return self._startTick # just returning start - def _setLineType(self, value): - if value is not None and value.lower() not in self.validLineTypes: + @tick.setter + def tick(self, value): + if value.lower() not in self.validTickTypes: raise SpannerException(f'not a valid value: {value}') - # not sure if we should permit setting as None - if value is not None: - self._lineType = value.lower() + self._startTick = value.lower() + self._endTick = value.lower() - lineType = property(_getLineType, _setLineType, doc=''' + @property + def lineType(self): + ''' Get or set the lineType property. Valid line types are listed in .validLineTypes. >>> b = spanner.Line() @@ -2221,41 +2212,52 @@ def _setLineType(self, value): >>> b.validLineTypes ('solid', 'dashed', 'dotted', 'wavy') - ''') - - def _getEndHeight(self): - return self._endHeight + ''' + return self._lineType - def _setEndHeight(self, value): - if not (common.isNum(value) and value >= 0): + @lineType.setter + def lineType(self, value): + if value is not None and value.lower() not in self.validLineTypes: raise SpannerException(f'not a valid value: {value}') - self._endHeight = value + # not sure if we should permit setting as None + if value is not None: + self._lineType = value.lower() - endHeight = property(_getEndHeight, _setEndHeight, doc=''' + @property + def endHeight(self): + ''' Get or set the endHeight property. >>> b = spanner.Line() >>> b.endHeight = -20 Traceback (most recent call last): music21.spanner.SpannerException: not a valid value: -20 - ''') - - def _getStartHeight(self): - return self._startHeight + ''' + return self._endHeight - def _setStartHeight(self, value): + @endHeight.setter + def endHeight(self, value): if not (common.isNum(value) and value >= 0): raise SpannerException(f'not a valid value: {value}') - self._startHeight = value + self._endHeight = value - startHeight = property(_getStartHeight, _setStartHeight, doc=''' + @property + def startHeight(self): + ''' Get or set the startHeight property. >>> b = spanner.Line() >>> b.startHeight = None Traceback (most recent call last): music21.spanner.SpannerException: not a valid value: None - ''') + ''' + return self._startHeight + + @startHeight.setter + def startHeight(self, value): + if not (common.isNum(value) and value >= 0): + raise SpannerException(f'not a valid value: {value}') + self._startHeight = value class Glissando(Spanner): @@ -2299,18 +2301,19 @@ def __init__(self, if label is not None: self.label = label # use property - def _getLineType(self): + @property + def lineType(self): + ''' + Get or set the lineType property. See Line for valid line types. + ''' return self._lineType - def _setLineType(self, value): + @lineType.setter + def lineType(self, value): if value.lower() not in self.validLineTypes: raise SpannerException(f'not a valid value: {value}') self._lineType = value.lower() - lineType = property(_getLineType, _setLineType, doc=''' - Get or set the lineType property. See Line for valid line types. - ''') - @property def slideType(self): ''' diff --git a/music21/stream/base.py b/music21/stream/base.py index 121bcc0bf..d296ad5f5 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -5099,42 +5099,9 @@ def measureOffsetMap( orderedOffsetMap = OrderedDict(sorted(offsetMap.items(), key=lambda o: o[0])) return orderedOffsetMap - def _getFinalBarline(self): - # if we have part-like streams, process each part - if self.hasPartLikeStreams(): - post = [] - for p in self.getElementsByClass('Stream'): - post.append(p._getFinalBarline()) - return post # a list of barlines - # core routines for a single Stream - else: - if self.hasMeasures(): - return self.getElementsByClass(Measure).last().rightBarline - elif hasattr(self, 'rightBarline'): - return self.rightBarline - else: - return None - - def _setFinalBarline(self, value): - # if we have part-like streams, process each part - if self.hasPartLikeStreams(): - if not common.isListLike(value): - value = [value] - for i, p in enumerate(self.getElementsByClass('Stream')): - # set final barline w/ mod iteration of value list - bl = value[i % len(value)] - # environLocal.printDebug(['enumerating measures', i, p, 'setting barline', bl]) - p._setFinalBarline(bl) - return - - # core routines for a single Stream - if self.hasMeasures(): - self.getElementsByClass(Measure).last().rightBarline = value - elif hasattr(self, 'rightBarline'): - self.rightBarline = value # pylint: disable=attribute-defined-outside-init - # do nothing for other streams - - finalBarline = property(_getFinalBarline, _setFinalBarline, doc=''' + @property + def finalBarline(self): + ''' Get or set the final barline of this Stream's Measures, if and only if there are Measures defined as elements in this Stream. This method will not create Measures if none exist. @@ -5193,7 +5160,41 @@ def _setFinalBarline(self, value): * Changed in v6.3: does not raise an exception if queried or set on a measure-less stream. Previously raised a StreamException - ''') + ''' + # if we have part-like streams, process each part + if self.hasPartLikeStreams(): + post = [] + for p in self.getElementsByClass('Stream'): + post.append(p.finalBarline) + return post # a list of barlines + # core routines for a single Stream + else: + if self.hasMeasures(): + return self.getElementsByClass(Measure).last().rightBarline + elif hasattr(self, 'rightBarline'): + return self.rightBarline + else: + return None + + @finalBarline.setter + def finalBarline(self, value): + # if we have part-like streams, process each part + if self.hasPartLikeStreams(): + if not common.isListLike(value): + value = [value] + for i, p in enumerate(self.getElementsByClass('Stream')): + # set final barline w/ mod iteration of value list + bl = value[i % len(value)] + # environLocal.printDebug(['enumerating measures', i, p, 'setting barline', bl]) + p.finalBarline = bl + return + + # core routines for a single Stream + if self.hasMeasures(): + self.getElementsByClass(Measure).last().rightBarline = value + elif hasattr(self, 'rightBarline'): + self.rightBarline = value # pylint: disable=attribute-defined-outside-init + # do nothing for other streams @property def voices(self): @@ -8593,10 +8594,47 @@ def duration(self) -> 'music21.duration.Duration': def duration(self, value: 'music21.duration.Duration'): self._setDuration(value) - def _setSeconds(self, value): - pass + @property + def seconds(self): + ''' + Get or set the duration of this Stream in seconds, assuming that + this object contains a :class:`~music21.tempo.MetronomeMark` or + :class:`~music21.tempo.MetricModulation`. + + >>> s = corpus.parse('bwv66.6') # piece without a tempo + >>> sFlat = s.flatten() + >>> t = tempo.MetronomeMark('adagio') + >>> sFlat.insert(0, t) + >>> sFlat.seconds + 38.57142857... + >>> tFast = tempo.MetronomeMark('allegro') + >>> sFlat.replace(t, tFast) + >>> sFlat.seconds + 16.363... + + Setting seconds on streams is not supported. Ideally it would instead + scale all elements to fit, but this is a long way off. - def _getSeconds(self): + If a stream does not have a tempo-indication in it then the property + returns 0.0 if an empty Stream (or self.highestTime is 0.0) or 'nan' + if there are non-zero duration objects in the stream: + + >>> s = stream.Stream() + >>> s.seconds + 0.0 + >>> s.insert(0, clef.TrebleClef()) + >>> s.seconds + 0.0 + >>> s.append(note.Note(type='half')) + >>> s.seconds + nan + >>> import math + >>> math.isnan(s.seconds) + True + + * Changed in v6.3: return nan rather than raising an exception. Do not + attempt to change seconds on a stream, as it did not do what you would expect. + ''' getTempoFromContext = False # need to find all tempo indications and the number of quarter lengths # under each @@ -8643,45 +8681,9 @@ def _getSeconds(self): return sec - seconds = property(_getSeconds, _setSeconds, doc=''' - Get or set the duration of this Stream in seconds, assuming that - this object contains a :class:`~music21.tempo.MetronomeMark` or - :class:`~music21.tempo.MetricModulation`. - - >>> s = corpus.parse('bwv66.6') # piece without a tempo - >>> sFlat = s.flatten() - >>> t = tempo.MetronomeMark('adagio') - >>> sFlat.insert(0, t) - >>> sFlat.seconds - 38.57142857... - >>> tFast = tempo.MetronomeMark('allegro') - >>> sFlat.replace(t, tFast) - >>> sFlat.seconds - 16.363... - - Setting seconds on streams is not supported. Ideally it would instead - scale all elements to fit, but this is a long way off. - - If a stream does not have a tempo-indication in it then the property - returns 0.0 if an empty Stream (or self.highestTime is 0.0) or 'nan' - if there are non-zero duration objects in the stream: - - >>> s = stream.Stream() - >>> s.seconds - 0.0 - >>> s.insert(0, clef.TrebleClef()) - >>> s.seconds - 0.0 - >>> s.append(note.Note(type='half')) - >>> s.seconds - nan - >>> import math - >>> math.isnan(s.seconds) - True - - * Changed in v6.3: return nan rather than raising an exception. Do not - attempt to change seconds on a stream, as it did not do what you would expect. - ''') + @seconds.setter + def seconds(self, value): + pass def metronomeMarkBoundaries(self, srcObj=None): ''' @@ -8863,22 +8865,28 @@ def _getSecondsMap(self, srcObj=None) -> list[SecondsMapEntry]: # -------------------------------------------------------------------------- # Metadata access - def _getMetadata(self) -> metadata.Metadata|None: + @property + def metadata(self) -> metadata.Metadata|None: ''' - >>> a = stream.Stream() - >>> a.metadata = metadata.Metadata() + Get or set the :class:`~music21.metadata.Metadata` object + found at the beginning (offset 0) of this Stream. + + >>> s = stream.Stream() + >>> s.metadata = metadata.Metadata() + >>> s.metadata.composer = 'frank' + >>> s.metadata.composer + 'frank' + + May also return None if nothing is there. ''' mdList = self.getElementsByClass(metadata.Metadata) # only return metadata that has an offset = 0.0 mdList = mdList.getElementsByOffset(0) return mdList.first() - def _setMetadata(self, metadataObj: metadata.Metadata|None) -> None: - ''' - >>> a = stream.Stream() - >>> a.metadata = metadata.Metadata() - ''' - oldMetadata = self._getMetadata() + @metadata.setter + def metadata(self, metadataObj: metadata.Metadata|None) -> None: + oldMetadata = self.metadata if oldMetadata is not None: # environLocal.printDebug(['removing old metadata', oldMetadata]) junk = self.pop(self.index(oldMetadata)) @@ -8886,20 +8894,6 @@ def _setMetadata(self, metadataObj: metadata.Metadata|None) -> None: if metadataObj is not None and isinstance(metadataObj, metadata.Metadata): self.insert(0, metadataObj) - metadata = property(_getMetadata, _setMetadata, - doc=''' - Get or set the :class:`~music21.metadata.Metadata` object - found at the beginning (offset 0) of this Stream. - - >>> s = stream.Stream() - >>> s.metadata = metadata.Metadata() - >>> s.metadata.composer = 'frank' - >>> s.metadata.composer - 'frank' - - May also return None if nothing is there. - ''') - # -------------------------------------------------------------------------- # these methods override the behavior inherited from base.py @@ -13438,7 +13432,15 @@ def bestTimeSignature(self): ''' return meter.bestTimeSignature(self) - def _getLeftBarline(self): + @property + def leftBarline(self): + ''' + Get or set the left barline, or the Barline object + found at offset zero of the Measure. Can be set either with a string + representing barline style or a bar.Barline() object or None. + Note that not all bars have + barline objects here -- regular barlines don't need them. + ''' barList = [] # directly access _elements, as do not want to get any bars # in _endElements @@ -13452,7 +13454,8 @@ def _getLeftBarline(self): else: return barList[0] - def _setLeftBarline(self, barlineObj): + @leftBarline.setter + def leftBarline(self, barlineObj): insert = True if isinstance(barlineObj, str): barlineObj = bar.Barline(barlineObj) @@ -13462,26 +13465,45 @@ def _setLeftBarline(self, barlineObj): else: # assume a Barline object barlineObj.location = 'left' - oldLeftBarline = self._getLeftBarline() + oldLeftBarline = self.leftBarline if oldLeftBarline is not None: - # environLocal.printDebug(['_setLeftBarline()', 'removing left barline']) + # environLocal.printDebug(['leftBarline setter', 'removing left barline']) junk = self.pop(self.index(oldLeftBarline)) if insert: - # environLocal.printDebug(['_setLeftBarline()', + # environLocal.printDebug(['leftBarline setter', # 'inserting new left barline', barlineObj]) self.insert(0, barlineObj) - leftBarline = property(_getLeftBarline, - _setLeftBarline, - doc=''' - Get or set the left barline, or the Barline object - found at offset zero of the Measure. Can be set either with a string - representing barline style or a bar.Barline() object or None. - Note that not all bars have - barline objects here -- regular barlines don't need them. - ''') + @property + def rightBarline(self): + ''' + Get or set the right barline, or the Barline object + found at the offset equal to the bar duration. - def _getRightBarline(self): + >>> b = bar.Barline('final') + >>> m = stream.Measure() + >>> print(m.rightBarline) + None + >>> m.rightBarline = b + >>> m.rightBarline.type + 'final' + + + A string can also be used instead: + + >>> c = converter.parse('tinynotation: 3/8 C8 D E F G A B4.') + >>> c.measure(1).rightBarline = 'light-light' + >>> c.measure(3).rightBarline = 'light-heavy' + >>> #_DOCS_SHOW c.show() + + .. image:: images/stream_barline_demo.* + :width: 211 + + OMIT_FROM_DOCS + + .measure currently isn't the same as the + original measure. + ''' # TODO: Move to Stream or make setting .rightBarline, etc. on Stream raise an exception # look on _endElements barList = [] @@ -13495,7 +13517,8 @@ def _getRightBarline(self): else: return barList[0] - def _setRightBarline(self, barlineObj): + @rightBarline.setter + def rightBarline(self, barlineObj): insert = True if isinstance(barlineObj, str): barlineObj = bar.Barline(barlineObj) @@ -13510,50 +13533,18 @@ def _setRightBarline(self, barlineObj): # environLocal.printDebug(['got barline obj w/ direction', barlineObj.direction]) if barlineObj.direction in ['start', None]: barlineObj.direction = 'end' - oldRightBarline = self._getRightBarline() + oldRightBarline = self.rightBarline if oldRightBarline is not None: - # environLocal.printDebug(['_setRightBarline()', 'removing right barline']) + # environLocal.printDebug(['rightBarline setter', 'removing right barline']) junk = self.pop(self.index(oldRightBarline)) # insert into _endElements if insert: self.storeAtEnd(barlineObj) - # environLocal.printDebug(['post _setRightBarline', barlineObj, + # environLocal.printDebug(['post rightBarline setter', barlineObj, # 'len of elements highest', len(self._endElements)]) - rightBarline = property(_getRightBarline, - _setRightBarline, - doc=''' - Get or set the right barline, or the Barline object - found at the offset equal to the bar duration. - - >>> b = bar.Barline('final') - >>> m = stream.Measure() - >>> print(m.rightBarline) - None - >>> m.rightBarline = b - >>> m.rightBarline.type - 'final' - - - A string can also be used instead: - - >>> c = converter.parse('tinynotation: 3/8 C8 D E F G A B4.') - >>> c.measure(1).rightBarline = 'light-light' - >>> c.measure(3).rightBarline = 'light-heavy' - >>> #_DOCS_SHOW c.show() - - .. image:: images/stream_barline_demo.* - :width: 211 - - OMIT_FROM_DOCS - - .measure currently isn't the same as the - original measure. - - ''') - class Part(Stream): ''' @@ -13587,26 +13578,9 @@ def __init__(self, *args, **keywords): self._partName = None self._partAbbreviation = None - def _getPartName(self): - if self._partName is not None: - return self._partName - elif '_partName' in self._cache: - return self._cache['_partName'] - else: - pn = None - for e in self[instrument.Instrument]: - pn = e.partName - if pn is None: - pn = e.instrumentName - if pn is not None: - break - self._cache['_partName'] = pn - return pn - - def _setPartName(self, newName): - self._partName = newName - - partName = property(_getPartName, _setPartName, doc=''' + @property + def partName(self): + ''' Gets or sets a string representing the name of this part as a whole (not counting instrument changes, etc.). @@ -13639,28 +13613,29 @@ def _setPartName(self, newName): .coreElementsChanged() is called or this Stream's elements are otherwise altered. This is because the value is cached so that O(n) searches through the Stream do not need to be done every time. - ''') - - def _getPartAbbreviation(self): - if self._partAbbreviation is not None: - return self._partAbbreviation - elif '_partAbbreviation' in self._cache: - return self._cache['_partAbbreviation'] + ''' + if self._partName is not None: + return self._partName + elif '_partName' in self._cache: + return self._cache['_partName'] else: pn = None for e in self[instrument.Instrument]: - pn = e.partAbbreviation + pn = e.partName if pn is None: - pn = e.instrumentAbbreviation + pn = e.instrumentName if pn is not None: break - self._cache['_partAbbreviation'] = pn + self._cache['_partName'] = pn return pn - def _setPartAbbreviation(self, newName): - self._partAbbreviation = newName + @partName.setter + def partName(self, newName): + self._partName = newName - partAbbreviation = property(_getPartAbbreviation, _setPartAbbreviation, doc=''' + @property + def partAbbreviation(self): + ''' Gets or sets a string representing the abbreviated name of this part as a whole (not counting instrument changes, etc.). @@ -13693,7 +13668,25 @@ def _setPartAbbreviation(self, newName): .coreElementsChanged() is called or this Stream's elements are otherwise altered. This is because the value is cached so that O(n) searches through the Stream do not need to be done every time. - ''') + ''' + if self._partAbbreviation is not None: + return self._partAbbreviation + elif '_partAbbreviation' in self._cache: + return self._cache['_partAbbreviation'] + else: + pn = None + for e in self[instrument.Instrument]: + pn = e.partAbbreviation + if pn is None: + pn = e.instrumentAbbreviation + if pn is not None: + break + self._cache['_partAbbreviation'] = pn + return pn + + @partAbbreviation.setter + def partAbbreviation(self, newName): + self._partAbbreviation = newName def makeAccidentals( self, diff --git a/music21/style.py b/music21/style.py index 127b7e81c..d4f6fb1f4 100644 --- a/music21/style.py +++ b/music21/style.py @@ -176,27 +176,9 @@ def enclosure(self, value: Enclosure|str|None): else: raise TextFormatException(f'Not a supported enclosure: {value!r}') - def _getAbsoluteY(self): - return self._absoluteY - - def _setAbsoluteY(self, value): - if value is None: - self._absoluteY = None - elif value == 'above': # TODO: convert to Enum and keep it - self._absoluteY = 10 - elif value == 'below': - self._absoluteY = -70 - else: - try: - self._absoluteY = common.numToIntOrFloat(value) - except ValueError as ve: - raise TextFormatException( - f'Not a supported absoluteY position: {value!r}' - ) from ve - - absoluteY = property(_getAbsoluteY, - _setAbsoluteY, - doc=''' + @property + def absoluteY(self): + ''' Get or set the vertical position, where 0 is the top line of the staff and units are whatever is defined in `.units`, generally "tenths", meaning @@ -222,7 +204,24 @@ def _setAbsoluteY(self, value): Traceback (most recent call last): music21.style.TextFormatException: Not a supported absoluteY position: 'hello' - ''') + ''' + return self._absoluteY + + @absoluteY.setter + def absoluteY(self, value): + if value is None: + self._absoluteY = None + elif value == 'above': # TODO: convert to Enum and keep it + self._absoluteY = 10 + elif value == 'below': + self._absoluteY = -70 + else: + try: + self._absoluteY = common.numToIntOrFloat(value) + except ValueError as ve: + raise TextFormatException( + f'Not a supported absoluteY position: {value!r}' + ) from ve class NoteStyle(Style): @@ -307,19 +306,9 @@ def __init__(self): self._alignHorizontal = None self._alignVertical = None - def _getAlignVertical(self): - return self._alignVertical - - def _setAlignVertical(self, value): - # TODO: convert to StrEnum - if value in (None, 'top', 'middle', 'bottom', 'baseline'): - self._alignVertical = value - else: - raise TextFormatException(f'Invalid vertical align: {value!r}') - - alignVertical = property(_getAlignVertical, - _setAlignVertical, - doc=''' + @property + def alignVertical(self): + ''' Get or set the vertical align. Valid values are top, middle, bottom, baseline or None. @@ -334,20 +323,20 @@ def _setAlignVertical(self, value): Traceback (most recent call last): music21.style.TextFormatException: Invalid vertical align: 'hello' - ''') - - def _getAlignHorizontal(self): - return self._alignHorizontal + ''' + return self._alignVertical - def _setAlignHorizontal(self, value): - if value in (None, 'left', 'right', 'center'): - self._alignHorizontal = value + @alignVertical.setter + def alignVertical(self, value): + # TODO: convert to StrEnum + if value in (None, 'top', 'middle', 'bottom', 'baseline'): + self._alignVertical = value else: - raise TextFormatException(f'Invalid horizontal align: {value!r}') + raise TextFormatException(f'Invalid vertical align: {value!r}') - alignHorizontal = property(_getAlignHorizontal, - _setAlignHorizontal, - doc=''' + @property + def alignHorizontal(self): + ''' Get or set the horizontal alignment. Valid values are left, right, center, or None. @@ -362,7 +351,15 @@ def _setAlignHorizontal(self, value): Traceback (most recent call last): music21.style.TextFormatException: Invalid horizontal align: 'hello' - ''') + ''' + return self._alignHorizontal + + @alignHorizontal.setter + def alignHorizontal(self, value): + if value in (None, 'left', 'right', 'center'): + self._alignHorizontal = value + else: + raise TextFormatException(f'Invalid horizontal align: {value!r}') @property @@ -423,10 +420,22 @@ def fontStyle(self, value: str|None) -> None: raise TextFormatException(f'Not a supported fontStyle: {value!r}') self._fontStyle = value.lower() - def _getWeight(self): + # TODO: figure out if we want to use fontStyle for all weights. + + @property + def fontWeight(self): + ''' + Get or set the weight, as normal, or bold. + + >>> tst = style.TextStyle() + >>> tst.fontWeight = 'bold' + >>> tst.fontWeight + 'bold' + ''' return self._fontWeight - def _setWeight(self, value): + @fontWeight.setter + def fontWeight(self, value): if value is None: self._fontWeight = None else: @@ -434,23 +443,20 @@ def _setWeight(self, value): raise TextFormatException(f'Not a supported fontWeight: {value}') self._fontWeight = value.lower() - # TODO: figure out if we want to use fontStyle for all weights. - - fontWeight = property(_getWeight, - _setWeight, - doc=''' - Get or set the weight, as normal, or bold. + @property + def fontSize(self): + ''' + Get or set the size. Best, an int or float, but also a css font size. >>> tst = style.TextStyle() - >>> tst.fontWeight = 'bold' - >>> tst.fontWeight - 'bold' - ''') - - def _getSize(self): + >>> tst.fontSize = 20 + >>> tst.fontSize + 20 + ''' return self._fontSize - def _setSize(self, value): + @fontSize.setter + def fontSize(self, value): if value is not None: try: value = common.numToIntOrFloat(value) @@ -459,21 +465,21 @@ def _setSize(self, value): # raise TextFormatException(f'Not a supported size: {value}') self._fontSize = value - fontSize = property(_getSize, - _setSize, - doc=''' - Get or set the size. Best, an int or float, but also a css font size. + @property + def letterSpacing(self): + ''' + Get or set the letter spacing. >>> tst = style.TextStyle() - >>> tst.fontSize = 20 - >>> tst.fontSize - 20 - ''') - - def _getLetterSpacing(self): + >>> tst.letterSpacing = 20 + >>> tst.letterSpacing + 20.0 + >>> tst.letterSpacing = 'normal' + ''' return self._letterSpacing - def _setLetterSpacing(self, value): + @letterSpacing.setter + def letterSpacing(self, value): if value != 'normal' and value is not None: # convert to number try: @@ -485,18 +491,6 @@ def _setLetterSpacing(self, value): self._letterSpacing = value - letterSpacing = property(_getLetterSpacing, - _setLetterSpacing, - doc=''' - Get or set the letter spacing. - - >>> tst = style.TextStyle() - >>> tst.letterSpacing = 20 - >>> tst.letterSpacing - 20.0 - >>> tst.letterSpacing = 'normal' - ''') - @property def fontFamily(self): ''' diff --git a/music21/tempo.py b/music21/tempo.py index 888ee19b7..6eb003b83 100644 --- a/music21/tempo.py +++ b/music21/tempo.py @@ -200,17 +200,23 @@ def __init__(self, text=None, **keywords): def _reprInternal(self): return repr(self.text) - def _getText(self): + @property + def text(self): ''' - Get the text used for this expression. + Get or set the text as a string. Setting is also the primary way that + the stored TextExpression object is created. + + >>> import music21 + >>> tm = music21.tempo.TempoText('adagio') + >>> tm.text + 'adagio' + >>> tm.getTextExpression() + ''' return self._textExpression.content - def _setText(self, value): - ''' - Set the text of this repeat expression. This is also the primary way - that the stored TextExpression object is created. - ''' + @text.setter + def text(self, value): if self._textExpression is None: self._textExpression = expressions.TextExpression(value) if self.hasStyleInformation: @@ -222,17 +228,6 @@ def _setText(self, value): else: self._textExpression.content = value - text = property(_getText, _setText, doc=''' - Get or set the text as a string. - - >>> import music21 - >>> tm = music21.tempo.TempoText('adagio') - >>> tm.text - 'adagio' - >>> tm.getTextExpression() - - ''') - def getMetronomeMark(self): # noinspection PyShadowingNames ''' @@ -486,10 +481,16 @@ def _updateNumberFromText(self): self.numberImplicit = True # ------------------------------------------------------------------------- - def _getReferent(self): + @property + def referent(self): + ''' + Get or set the referent, or the Duration object that is the + reference for the tempo value in BPM. + ''' return self._referent - def _setReferent(self, value): + @referent.setter + def referent(self, value): if value is None: # this may be better not here # if referent is None, set a default quarter note duration self._referent = duration.Duration(type='quarter') @@ -506,11 +507,6 @@ def _setReferent(self, value): else: raise TempoException(f'Cannot get a Duration from the supplied object: {value}') - referent = property(_getReferent, _setReferent, doc=''' - Get or set the referent, or the Duration object that is the - reference for the tempo value in BPM. - ''') - # properties and conversions def _getText(self): if self._tempoText is None: @@ -576,15 +572,9 @@ def _setNumber(self, value: int|float|None, False ''') - def _getNumberSounding(self): - return self._numberSounding # may be None - - def _setNumberSounding(self, value): - if not common.isNum(value) and value is not None: - raise TempoException('cannot set numberSounding to a string') - self._numberSounding = value - - numberSounding = property(_getNumberSounding, _setNumberSounding, doc=''' + @property + def numberSounding(self): + ''' Get and set the numberSounding, or the numerical value of the Metronome that is used for playback independent of display. If numberSounding is None, number is assumed to be numberSounding. @@ -599,7 +589,14 @@ def _setNumberSounding(self, value): >>> mm.numberSounding = 120 >>> mm.numberSounding 120 - ''') + ''' + return self._numberSounding # may be None + + @numberSounding.setter + def numberSounding(self, value): + if not common.isNum(value) and value is not None: + raise TempoException('cannot set numberSounding to a string') + self._numberSounding = value # ------------------------------------------------------------------------- def getQuarterBPM(self, useNumberSounding=True) -> float|None: @@ -949,21 +946,9 @@ def _reprInternal(self): # -------------------------------------------------------------------------- # core properties - def _setOldMetronome(self, value): - if value is None: - pass # allow setting as None - elif not hasattr(value, 'classes') or 'MetronomeMark' not in value.classes: - raise MetricModulationException( - 'oldMetronome property must be set with a MetronomeMark instance') - self._oldMetronome = value - - def _getOldMetronome(self): - if self._oldMetronome is not None: - if self._oldMetronome.number is None: - self.updateByContext() - return self._oldMetronome - - oldMetronome = property(_getOldMetronome, _setOldMetronome, doc=''' + @property + def oldMetronome(self): + ''' Get or set the left :class:`~music21.tempo.MetronomeMark` object for the old, or previous value. @@ -979,9 +964,40 @@ def _getOldMetronome(self): Traceback (most recent call last): music21.tempo.MetricModulationException: oldMetronome property must be set with a MetronomeMark instance - ''') + ''' + if self._oldMetronome is not None: + if self._oldMetronome.number is None: + self.updateByContext() + return self._oldMetronome + + @oldMetronome.setter + def oldMetronome(self, value): + if value is None: + pass # allow setting as None + elif not hasattr(value, 'classes') or 'MetronomeMark' not in value.classes: + raise MetricModulationException( + 'oldMetronome property must be set with a MetronomeMark instance') + self._oldMetronome = value + + @property + def oldReferent(self): + ''' + Get or set the referent of the old MetronomeMark. + + >>> mm1 = tempo.MetronomeMark(number=60, referent=1) + >>> mmod1 = tempo.MetricModulation() + >>> mmod1.oldMetronome = mm1 + >>> mmod1.oldMetronome + + >>> mmod1.oldReferent = 0.25 + >>> mmod1.oldMetronome + + ''' + if self._oldMetronome is not None: + return self._oldMetronome.referent - def _setOldReferent(self, value): + @oldReferent.setter + def oldReferent(self, value): if value is None: raise MetricModulationException('cannot set old referent to None') # try to get and reassign equivalent @@ -1000,25 +1016,30 @@ def _setOldReferent(self, value): self._oldMetronome = MetronomeMark(referent=value) # raise MetricModulationException('cannot set old MetronomeMark from provided value.') - def _getOldReferent(self): - if self._oldMetronome is not None: - return self._oldMetronome.referent - - oldReferent = property(_getOldReferent, _setOldReferent, doc=''' - Get or set the referent of the old MetronomeMark. + @property + def newMetronome(self): + ''' + Get or set the right :class:`~music21.tempo.MetronomeMark` + object for the new, or following value. >>> mm1 = tempo.MetronomeMark(number=60, referent=1) - >>> mmod1 = tempo.MetricModulation() - >>> mmod1.oldMetronome = mm1 - >>> mmod1.oldMetronome + >>> mm1 - >>> mmod1.oldReferent = 0.25 - >>> mmod1.oldMetronome - - - ''') + >>> mmod1 = tempo.MetricModulation() + >>> mmod1.newMetronome = mm1 + >>> mmod1.newMetronome = 'junk' + Traceback (most recent call last): + music21.tempo.MetricModulationException: newMetronome property must be + set with a MetronomeMark instance + ''' + # before returning the referent, see if we can update the number + if self._newMetronome is not None: + if self._newMetronome.number is None: + self.updateByContext() + return self._newMetronome - def _setNewMetronome(self, value): + @newMetronome.setter + def newMetronome(self, value): if value is None: pass # allow setting as None elif not hasattr(value, 'classes') or 'MetronomeMark' not in value.classes: @@ -1026,29 +1047,25 @@ def _setNewMetronome(self, value): 'newMetronome property must be set with a MetronomeMark instance') self._newMetronome = value - def _getNewMetronome(self): - # before returning the referent, see if we can update the number - if self._newMetronome is not None: - if self._newMetronome.number is None: - self.updateByContext() - return self._newMetronome - - newMetronome = property(_getNewMetronome, _setNewMetronome, doc=''' - Get or set the right :class:`~music21.tempo.MetronomeMark` - object for the new, or following value. + @property + def newReferent(self): + ''' + Get or set the referent of the new MetronomeMark. >>> mm1 = tempo.MetronomeMark(number=60, referent=1) - >>> mm1 - >>> mmod1 = tempo.MetricModulation() >>> mmod1.newMetronome = mm1 - >>> mmod1.newMetronome = 'junk' - Traceback (most recent call last): - music21.tempo.MetricModulationException: newMetronome property must be - set with a MetronomeMark instance - ''') + >>> mmod1.newMetronome + + >>> mmod1.newReferent = 0.25 + >>> mmod1.newMetronome + + ''' + if self._newMetronome is not None: + return self._newMetronome.referent - def _setNewReferent(self, value): + @newReferent.setter + def newReferent(self, value): if value is None: raise MetricModulationException('cannot set new referent to None') # if oldMetronome is defined, get new metronome from old @@ -1065,23 +1082,6 @@ def _setNewReferent(self, value): # raise MetricModulationException('cannot set old MetronomeMark from provided value.') self._newMetronome = mm - def _getNewReferent(self): - if self._newMetronome is not None: - return self._newMetronome.referent - - newReferent = property(_getNewReferent, _setNewReferent, doc=''' - Get or set the referent of the new MetronomeMark. - - >>> mm1 = tempo.MetronomeMark(number=60, referent=1) - >>> mmod1 = tempo.MetricModulation() - >>> mmod1.newMetronome = mm1 - >>> mmod1.newMetronome - - >>> mmod1.newReferent = 0.25 - >>> mmod1.newMetronome - - ''') - @property def number(self): ''' diff --git a/music21/variant.py b/music21/variant.py index 9adbfd2a4..17e2184e3 100644 --- a/music21/variant.py +++ b/music21/variant.py @@ -271,19 +271,9 @@ def containedSite(self): ''' return self._stream - def _getReplacementQuarterLength(self): - if self._replacementQuarterLength is None: - return self._stream.duration.quarterLength - else: - return self._replacementQuarterLength - - def _setReplacementQuarterLength(self, value): - self._replacementQuarterLength = value - - replacementQuarterLength = property( - _getReplacementQuarterLength, - _setReplacementQuarterLength, - doc=''' + @property + def replacementQuarterLength(self): + ''' Set or Return the quarterLength in the main stream which this variant object replaces in the variant version of the stream. If replacementQuarterLength is not set, it is assumed to be the same length as the variant. If it is set to 0, @@ -292,23 +282,19 @@ def _setReplacementQuarterLength(self, value): itself. * New in v10.3: renamed from ``replacementDuration``. - ''') - - def _getReplacementDuration(self): - return self.replacementQuarterLength + ''' + if self._replacementQuarterLength is None: + return self._stream.duration.quarterLength + else: + return self._replacementQuarterLength - def _setReplacementDuration(self, value): - warnings.warn( - "'replacementDuration' is deprecated as of v11 and will be removed in v12; " - + 'use the synonym replacementQuarterLength instead.', - exceptions21.Music21DeprecationWarning, - stacklevel=2) - self.replacementQuarterLength = value + @replacementQuarterLength.setter + def replacementQuarterLength(self, value): + self._replacementQuarterLength = value - replacementDuration = property( - _getReplacementDuration, - _setReplacementDuration, - doc=''' + @property + def replacementDuration(self): + ''' Synonym for :attr:`replacementQuarterLength`. .. note:: @@ -317,7 +303,17 @@ def _setReplacementDuration(self, value): avoids referring to an offset/quarterLength value as a "Duration"). Setting it raises a deprecation warning, and it will be removed in v12. Use :attr:`replacementQuarterLength` instead. - ''') + ''' + return self.replacementQuarterLength + + @replacementDuration.setter + def replacementDuration(self, value): + warnings.warn( + "'replacementDuration' is deprecated as of v11 and will be removed in v12; " + + 'use the synonym replacementQuarterLength instead.', + exceptions21.Music21DeprecationWarning, + stacklevel=2) + self.replacementQuarterLength = value @property def lengthType(self): diff --git a/music21/voiceLeading.py b/music21/voiceLeading.py index b6a23659b..0387ac771 100644 --- a/music21/voiceLeading.py +++ b/music21/voiceLeading.py @@ -1801,42 +1801,44 @@ def getVerticalityOffset(self, *, leftAlign=True): else: return sorted(self.objects, key=lambda m21Obj: m21Obj.offset)[-1].offset - def _setLyric(self, value): - newList = sorted(self.objects, key=lambda x: x.offset, reverse=True) - newList[0].lyric = value - - def _getLyric(self): - newList = sorted(self.objects, key=lambda x: x.offset, reverse=True) - return newList[0].lyric - - lyric = property(_getLyric, _setLyric, doc=''' + @property + def lyric(self): + ''' Sets each object on the Verticality to have the passed in lyric. >>> h = voiceLeading.Verticality({1: note.Note('C'), 2: harmony.ChordSymbol('C')}) >>> h.lyric = 'Verticality 1' >>> h.getStream().flatten().getElementsByClass(note.Note).first().lyric 'Verticality 1' - ''') + ''' + newList = sorted(self.objects, key=lambda x: x.offset, reverse=True) + return newList[0].lyric + + @lyric.setter + def lyric(self, value): + newList = sorted(self.objects, key=lambda x: x.offset, reverse=True) + newList[0].lyric = value def _reprInternal(self): return f'contentDict={self.contentDict}' - def _setColor(self, color): - self.style.color = color - for obj in self.objects: - obj.style.color = color - - def _getColor(self): - return self.style.color - - color = property(_getColor, _setColor, doc=''' + @property + def color(self): + ''' Sets the color of each element in the Verticality. >>> vs1 = voiceLeading.Verticality({1:note.Note('C'), 2:harmony.ChordSymbol('D')}) >>> vs1.color = 'blue' >>> [(x, x.style.color) for x in vs1.objects] [(, 'blue'), (, 'blue')] - ''') + ''' + return self.style.color + + @color.setter + def color(self, color): + self.style.color = color + for obj in self.objects: + obj.style.color = color class VerticalityNTuplet(base.Music21Object): @@ -2010,18 +2012,9 @@ def noteList(self): return self._noteList[:] - def _getMelodicIntervals(self): - tempListOne = self.noteList[:-1] - tempListTwo = self.noteList[1:] - melodicIntervalList = [] - for n1, n2 in zip(tempListOne, tempListTwo, strict=True): - if n1 and n2: - melodicIntervalList.append(interval.Interval(n1, n2)) - else: - melodicIntervalList.append(None) - return melodicIntervalList - - melodicIntervals = property(_getMelodicIntervals, doc=''' + @property + def melodicIntervals(self): + ''' Calculates the melodic intervals and returns them as a list, with the interval at 0 being the interval between the first and second note. @@ -2031,7 +2024,16 @@ def _getMelodicIntervals(self): [, , ] - ''') + ''' + tempListOne = self.noteList[:-1] + tempListTwo = self.noteList[1:] + melodicIntervalList = [] + for n1, n2 in zip(tempListOne, tempListTwo, strict=True): + if n1 and n2: + melodicIntervalList.append(interval.Interval(n1, n2)) + else: + melodicIntervalList.append(None) + return melodicIntervalList class ThreeNoteLinearSegmentException(exceptions21.Music21Exception): @@ -2095,22 +2097,37 @@ def __init__(self, noteListOrN1=None, n2=None, n3=None, **keywords): else: super().__init__([noteListOrN1, n2, n3], **keywords) - def _getN1(self): + @property + def n1(self): + ''' + Get or set the first note (left-most) in the segment. + ''' return self.noteList[0] - def _setN1(self, value): + @n1.setter + def n1(self, value): self.noteList[0] = self._correctNoteInput(value) - def _getN2(self): + @property + def n2(self): + ''' + Get or set the middle note in the segment. + ''' return self.noteList[1] - def _setN2(self, value): + @n2.setter + def n2(self, value): self.noteList[1] = self._correctNoteInput(value) - def _getN3(self): + @property + def n3(self): + ''' + Get or set the last note (right-most) in the segment. + ''' return self.noteList[2] - def _setN3(self, value): + @n3.setter + def n3(self, value): self.noteList[2] = self._correctNoteInput(value) def _correctNoteInput(self, value): @@ -2130,53 +2147,44 @@ def _correctNoteInput(self, value): ) from e - n1 = property(_getN1, _setN1, doc=''' - Get or set the first note (left-most) in the segment. - ''') - n2 = property(_getN2, _setN2, doc=''' - Get or set the middle note in the segment. - ''') - n3 = property(_getN3, _setN3, doc=''' - Get or set the last note (right-most) in the segment. - ''') - - def _getILeftToRight(self): - if self.n1 and self.n3: - return interval.Interval(self.n1, self.n3) - else: - return None - - def _getILeft(self): - return self.melodicIntervals[0] - - def _getIRight(self): - return self.melodicIntervals[1] - - iLeftToRight = property(_getILeftToRight, doc=''' + @property + def iLeftToRight(self): + ''' Get the interval between the left-most note and the right-most note (read-only property). >>> tnls = voiceLeading.ThreeNoteLinearSegment('C', 'E', 'G') >>> tnls.iLeftToRight - ''') + ''' + if self.n1 and self.n3: + return interval.Interval(self.n1, self.n3) + else: + return None - iLeft = property(_getILeft, doc=''' + @property + def iLeft(self): + ''' Get the interval between the left-most note and the middle note (read-only property). >>> tnls = voiceLeading.ThreeNoteLinearSegment('A', 'B', 'G') >>> tnls.iLeft - ''') - iRight = property(_getIRight, doc=''' + ''' + return self.melodicIntervals[0] + + @property + def iRight(self): + ''' Get the interval between the middle note and the right-most note (read-only property). >>> tnls = voiceLeading.ThreeNoteLinearSegment('A', 'B', 'G') >>> tnls.iRight - ''') + ''' + return self.melodicIntervals[1] def _reprInternal(self): return f'n1={self.n1} n2={self.n2} n3={self.n3}'