Skip to content

Commit

Permalink
Merge pull request #1121 from cuthbertLab/typing_fixes
Browse files Browse the repository at this point in the history
Typing fixes and improvements to <software>
  • Loading branch information
mscuthbert authored Sep 8, 2021
2 parents 8b76096 + 4a8f2da commit 300f663
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 78 deletions.
2 changes: 1 addition & 1 deletion music21/chord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4228,7 +4228,7 @@ def commonName(self):
Changed in v5.5: special cases for checking enharmonics in some cases
Changed in v6.5: better handling of 0-, 1-, and 2-pitchClass and microtonal chords.
Changed in v7: Inversions of augmented triads are used.
Changed in v7: Inversions of augmented sixth-chords are specified.
'''
if any(not p.isTwelveTone() for p in self.pitches):
return 'microtonal chord'
Expand Down
6 changes: 3 additions & 3 deletions music21/converter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,9 +697,9 @@ def parseURL(self, url, *, format=None, number=None,
self.setSubconverterFromFormat(useFormat)
self.subConverter.keywords = keywords
self.subConverter.parseFile(fp, number=number)
self.stream.filePath = fp
self.stream.fileNumber = number
self.stream.fileFormat = useFormat
self.stream.filePath = fp # These are attributes defined outside of
self.stream.fileNumber = number # __init__ and will be moved to
self.stream.fileFormat = useFormat # Metadata in v8.

# -----------------------------------------------------------------------#
# Subconverters
Expand Down
7 changes: 4 additions & 3 deletions music21/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@
from music21 import environment
_MOD = 'instrument'
environLocal = environment.Environment(_MOD)
StreamType = stream.StreamType


def unbundleInstruments(streamIn, *, inPlace=False):
def unbundleInstruments(streamIn: StreamType, *, inPlace=False) -> Optional[StreamType]:
# noinspection PyShadowingNames
'''
takes a :class:`~music21.stream.Stream` that has :class:`~music21.note.NotRest` objects
Expand Down Expand Up @@ -79,7 +80,7 @@ def unbundleInstruments(streamIn, *, inPlace=False):
return s


def bundleInstruments(streamIn, *, inPlace=False):
def bundleInstruments(streamIn: stream.Stream, *, inPlace=False) -> Optional[stream.Stream]:
# noinspection PyShadowingNames
'''
>>> up1 = note.Unpitched()
Expand Down Expand Up @@ -169,7 +170,7 @@ def __init__(self, instrumentName=None):
self.highestNote = None

# define interval to go from written to sounding
self.transposition = None
self.transposition: Optional[interval.Interval] = None

self.inGMPercMap = False
self.soundfontFn = None # if defined...
Expand Down
4 changes: 2 additions & 2 deletions music21/metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ def __init__(self, *args, **keywords):
self.software = [defaults.software]

# Copyright can be None or a copyright object
# TODO: Change to property to prevent text setting
# (but need to regenerate CoreCorpus() after doing so.)
# TODO: Change to property to prevent setting as a plain string
# (but need to regenerate CoreCorpus() after doing so.)
self.copyright = None

# a dictionary of Text elements, where keys are work id strings
Expand Down
17 changes: 15 additions & 2 deletions music21/musicxml/m21ToXml.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,9 @@ def setColor(self, mxObject, m21Object):
'''
Sets mxObject['color'] to a normalized version of m21Object.style.color
'''
# we repeat 'color' rather than just letting setStyleAttributes
# handle it, because otherwise it will run the expensive
# hyphenToCamelCase routine on something called on each note.
self.setStyleAttributes(mxObject, m21Object, 'color', 'color')
if 'color' in mxObject.attrib: # set
mxObject.attrib['color'] = normalizeColor(mxObject.attrib['color'])
Expand Down Expand Up @@ -969,7 +972,6 @@ def setPosition(self, mxObject, m21Object):
set positioning information for an mxObject from
default-x, default-y, relative-x, relative-y from
the .style attribute's absoluteX, relativeX, etc. attributes.
'''
musicXMLNames = ('default-x', 'default-y', 'relative-x', 'relative-y')
m21Names = ('absoluteX', 'absoluteY', 'relativeX', 'relativeY')
Expand Down Expand Up @@ -2317,11 +2319,20 @@ def setEncoding(self):
# TODO: encoder

if self.scoreMetadata is not None:
found_m21_already = False
for software in self.scoreMetadata.software:
if 'music21 v.' in software:
if found_m21_already:
# only write out one copy of the music21 software
# tag. First one should be current version.
continue
else:
found_m21_already = True
mxSoftware = SubElement(mxEncoding, 'software')
mxSoftware.text = software

else:
# there will not be a music21 software tag if no scoreMetadata
# if not for this.
mxSoftware = SubElement(mxEncoding, 'software')
mxSoftware.text = defaults.software

Expand Down Expand Up @@ -5776,6 +5787,8 @@ def beamToXml(self, beamObject):

# not to be done: repeater (deprecated)
self.setColor(mxBeam, beamObject)
# again, we pass the name 'fan' twice so we don't have to run
# hyphenToCamelCase on it.
self.setStyleAttributes(mxBeam, beamObject, 'fan', 'fan')

return mxBeam
Expand Down
115 changes: 60 additions & 55 deletions music21/musicxml/xmlToM21.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def textNotNone(mxObj):
return True


def textStripValid(mxObj):
def textStripValid(mxObj: ET.Element):
'''
returns True if textNotNone(mxObj)
and mxObj.text.strip() is not empty
Expand All @@ -131,7 +131,7 @@ def textStripValid(mxObj):
return True


def musicXMLTypeToType(value):
def musicXMLTypeToType(value: str) -> str:
'''
Utility function to convert a MusicXML duration type to an music21 duration type.
Expand Down Expand Up @@ -475,6 +475,9 @@ def setColor(self, mxObject, m21Object):
'''
Sets m21Object.style.color to be the same as color...
'''
# we repeat 'color' rather than just letting setStyleAttributes
# handle it, because otherwise it will run the expensive
# hyphenToCamelCase routine on something called on each note.
self.setStyleAttributes(mxObject, m21Object, 'color', 'color')

def setFont(self, mxObject, m21Object):
Expand Down Expand Up @@ -1273,7 +1276,9 @@ def xmlMetadata(self, el=None, inputM21=None):
if inputM21 is None:
return md

def identificationToMetadata(self, identification, inputM21=None):
def identificationToMetadata(self,
identification: ET.Element,
inputM21: Optional[metadata.Metadata] = None):
'''
Convert an <identification> tag, containing <creator> tags, <rights> tags, and
<miscellaneous> tag.
Expand Down Expand Up @@ -1302,30 +1307,7 @@ def identificationToMetadata(self, identification, inputM21=None):

encoding = identification.find('encoding')
if encoding is not None:
# TODO: encoder (text + type = role) multiple
# TODO: encoding date multiple
# TODO: encoding-description (string) multiple
for software in encoding.findall('software'):
if textStripValid(software):
md.software.append(software.text.strip())

for supports in encoding.findall('supports'):
# todo: element: required
# todo: type: required -- not sure of the difference between this and value
# though type is yes-no while value is string
attr = supports.get('attribute')
value = supports.get('value')
if value is None:
value = supports.get('type')

# found in wild: element=accidental type="no" -- No accidentals are indicated
# found in wild: transpose
# found in wild: beam
# found in wild: stem
if (attr, value) == ('new-system', 'yes'):
self.definesExplicitSystemBreaks = True
elif (attr, value) == ('new-page', 'yes'):
self.definesExplicitPageBreaks = True
self.processEncoding(encoding, md)

# TODO: source
# TODO: relation
Expand All @@ -1348,7 +1330,35 @@ def identificationToMetadata(self, identification, inputM21=None):
if inputM21 is None:
return md

def creatorToContributor(self, creator, inputM21=None):
def processEncoding(self, encoding: ET.Element, md: metadata.Metadata):
# TODO: encoder (text + type = role) multiple
# TODO: encoding date multiple
# TODO: encoding-description (string) multiple
for software in encoding.findall('software'):
if textStripValid(software):
md.software.append(software.text.strip())

for supports in encoding.findall('supports'):
# todo: element: required
# todo: type: required -- not sure of the difference between this and value
# though type is yes-no while value is string
attr = supports.get('attribute')
value = supports.get('value')
if value is None:
value = supports.get('type')

# found in wild: element=accidental type="no" -- No accidentals are indicated
# found in wild: transpose
# found in wild: beam
# found in wild: stem
if (attr, value) == ('new-system', 'yes'):
self.definesExplicitSystemBreaks = True
elif (attr, value) == ('new-page', 'yes'):
self.definesExplicitPageBreaks = True

def creatorToContributor(self,
creator: ET.Element,
inputM21: Optional[metadata.primitives.Contributor] = None):
# noinspection PyShadowingNames
'''
Given a <creator> tag, fill the necessary parameters of a Contributor.
Expand Down Expand Up @@ -1768,7 +1778,6 @@ def copy_into_partStaff(source, target, omitTheseElementIds):
staffGroup.style.hideObjectOnPrint = True # in truth, hide the name, not the brace
self.parent.stream.insert(0, staffGroup)


def _getStaffExclude(
self,
staffReference: StaffReferenceType,
Expand Down Expand Up @@ -1807,7 +1816,7 @@ def _getUniqueStaffKeys(self) -> List[int]:
post.sort()
return post

def xmlMeasureToMeasure(self, mxMeasure):
def xmlMeasureToMeasure(self, mxMeasure: ET.Element) -> stream.Measure:
# noinspection PyShadowingNames
'''
Convert a measure element to a Measure, using
Expand Down Expand Up @@ -1888,7 +1897,7 @@ def xmlMeasureToMeasure(self, mxMeasure):

return m

def updateTransposition(self, newTransposition):
def updateTransposition(self, newTransposition: interval.Interval):
'''
As you might expect, a measureParser that reveals a change
in transposition is going to have an effect on the
Expand Down Expand Up @@ -1936,7 +1945,7 @@ def updateTransposition(self, newTransposition):
self.activeInstrument.transposition = newTransposition
self.atSoundingPitch = False

def setLastMeasureInfo(self, m):
def setLastMeasureInfo(self, m: stream.Measure):
# noinspection PyShadowingNames
'''
Sets self.lastMeasureNumber and self.lastMeasureSuffix from the measure,
Expand Down Expand Up @@ -2017,7 +2026,7 @@ def setLastMeasureInfo(self, m):
ts = meter.TimeSignature('4/4')
self.lastTimeSignature = ts

def adjustTimeAttributesFromMeasure(self, m):
def adjustTimeAttributesFromMeasure(self, m: stream.Measure):
'''
Adds padAsAnacrusis to pickup measures and other measures that
do not fill the whole tile, if the first measure of the piece, or
Expand Down Expand Up @@ -2109,7 +2118,7 @@ def adjustTimeAttributesFromMeasure(self, m):

self.lastMeasureOffset += mOffsetShift

def applyMultiMeasureRest(self, r):
def applyMultiMeasureRest(self, r: note.Rest):
'''
If there is an active MultiMeasureRestSpanner, add the Rest, r, to it:
Expand Down Expand Up @@ -2201,11 +2210,6 @@ class MeasureParser(XMLParserBase):
'bookmark': None,
# Note: <print> is handled separately...
}

# TODO: editorial, i.e., footnote and level
# staves: see separateOutPartStaves()
# TODO: part-symbol
# not to be done: directive DEPRECATED since MusicXML 2.0
def __init__(self, mxMeasure=None, parent=None):
super().__init__()

Expand Down Expand Up @@ -2460,7 +2464,7 @@ def parse(self):
self.fullMeasureRest = True
# it might already be True because a rest had a "measure='yes'" attribute

def xmlBackup(self, mxObj):
def xmlBackup(self, mxObj: ET.Element):
'''
Parse a backup tag by changing :attr:`offsetMeasureNote`.
Expand Down Expand Up @@ -2491,7 +2495,7 @@ def xmlBackup(self, mxObj):
# https://github.com/cuthbertLab/music21/issues/971
self.offsetMeasureNote = max(self.offsetMeasureNote, 0.0)

def xmlForward(self, mxObj):
def xmlForward(self, mxObj: ET.Element):
'''
Parse a forward tag by changing :attr:`offsetMeasureNote`.
'''
Expand All @@ -2514,7 +2518,7 @@ def xmlForward(self, mxObj):
# xmlToNote() sets None
self.endedWithForwardTag = r

def xmlPrint(self, mxPrint):
def xmlPrint(self, mxPrint: ET.Element):
'''
<print> handles changes in pages, numbering, layout,
etc. so can generate PageLayout, SystemLayout, or StaffLayout
Expand Down Expand Up @@ -2574,7 +2578,7 @@ def hasSystemLayout():
# TODO: part-abbreviation display
# TODO: print-attributes: staff-spacing, blank-page; skip deprecated staff-spacing

def xmlToNote(self, mxNote):
def xmlToNote(self, mxNote: ET.Element) -> None:
'''
Handles everything for creating a Note or Rest or Chord
Expand Down Expand Up @@ -2667,7 +2671,7 @@ def xmlToNote(self, mxNote):
self.offsetMeasureNote += offsetIncrement
self.endedWithForwardTag = None

def xmlToChord(self, mxNoteList):
def xmlToChord(self, mxNoteList: List[ET.Element]) -> chord.Chord:
# noinspection PyShadowingNames
'''
Given an a list of mxNotes, fill the necessary parameters
Expand Down Expand Up @@ -2762,9 +2766,6 @@ def xmlToSimpleNote(self, mxNote, freeSpanners=True) -> Union[note.Note, note.Un
The `spannerBundle` parameter can be a list or a Stream
for storing and processing Spanner objects.
If inputM21 is not `None` then that object is used
for translating. Otherwise a new Note is created.
if freeSpanners is False then pending spanners will not be freed.
>>> from xml.etree.ElementTree import fromstring as EL
Expand Down Expand Up @@ -2804,7 +2805,7 @@ def xmlToSimpleNote(self, mxNote, freeSpanners=True) -> Union[note.Note, note.Un
'''
d = self.xmlToDuration(mxNote)

n = None
n: Union[note.Note, note.Unpitched]

mxUnpitched = mxNote.find('unpitched')
if mxUnpitched is None:
Expand Down Expand Up @@ -2844,7 +2845,7 @@ def xmlToSimpleNote(self, mxNote, freeSpanners=True) -> Union[note.Note, note.Un

# beam and beams

def xmlToBeam(self, mxBeam, inputM21=None):
def xmlToBeam(self, mxBeam: ET.Element, inputM21=None):
# noinspection PyShadowingNames
'''
given an mxBeam object return a :class:`~music21.beam.Beam` object
Expand Down Expand Up @@ -5119,21 +5120,25 @@ def parseAttributesTag(self, mxAttributes):
self.activeAttributes = mxAttributes
for mxSub in mxAttributes:
tag = mxSub.tag
meth = None
# key, clef, time, details
# clef, key, measure-style, time, staff-details
if tag in self.attributeTagsToMethods:
meth = getattr(self, self.attributeTagsToMethods[tag])

if meth is not None:
meth(mxSub)
elif tag == 'staves':
self.staves = int(mxSub.text)
# NOT to be done: directive -- deprecated since v2.
elif tag == 'divisions':
self.divisions = common.opFrac(float(mxSub.text))
# TODO: for-part
# TODO: instruments -- int if more than one instrument plays most of the time
# TODO: part-symbol
elif tag == 'staves':
self.staves = int(mxSub.text)
elif tag == 'transpose':
self.transposition = self.xmlTransposeToInterval(mxSub)
# environLocal.warn('Got a transposition of ', str(self.transposition) )

# footnote, level
self.setEditorial(mxAttributes, self.stream)

if self.parent is not None:
self.parent.lastDivisions = self.divisions
self.parent.activeAttributes = self.activeAttributes
Expand Down
Loading

0 comments on commit 300f663

Please sign in to comment.