From 3b345f11addd11994e4b1d1c2edc1575263730b8 Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Tue, 30 Apr 2024 20:08:30 +0200 Subject: [PATCH 1/8] Improve grace note and chord reading for kern Now when reading a kern file, a grace note will have its unlinked duration type match the note's notated notation in the file. Also, specifying a grace note with 'qq' instead of just 'q' will omit the slash on the accidental. Also, when reading chords where only the first note of the chord specifies its duration, the non-first notes in the chord will inherit this duration. Similar to how a chord object uses the same literal duration object of the first note (not just a copy), these non-first notes that don't have their own specified duration also reuse the first note's duration object. --- music21/humdrum/spineParser.py | 61 ++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index b2b1bc925..f0822198b 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -1363,7 +1363,7 @@ def processNoteEvent(self, eventC): self.lastNote = eventNote return eventNote - def processChordEvent(self, eventC): + def processChordEvent(self, eventC: str) -> chord.Chord: ''' Process a single chord event @@ -1372,12 +1372,14 @@ def processChordEvent(self, eventC): # multipleNotes notesToProcess = eventC.split() chordNotes = [] - for noteToProcess in notesToProcess: - thisNote = hdStringToNote(noteToProcess) + defaultDuration = None # used for non-first notes without a duration + for i, noteToProcess in enumerate(notesToProcess): + thisNote = hdStringToNote(noteToProcess, defaultDuration) + if i == 0: + defaultDuration = thisNote.duration chordNotes.append(thisNote) - eventChord = chord.Chord(chordNotes, beams=chordNotes[-1].beams) - eventChord.duration = chordNotes[0].duration - + eventChord = chord.Chord(chordNotes, beams=chordNotes[-1].beams, + duration=defaultDuration) self.setBeamsForNote(eventChord) self.setTupletTypeForNote(eventChord) self.lastNote = eventChord @@ -2118,7 +2120,8 @@ def getAllOccurring(self): return retEvents -def hdStringToNote(contents): +def hdStringToNote(contents: str, + defaultDuration: duration.Duration|None = None) -> note.Note | note.Rest: ''' returns a :class:`~music21.note.Note` (or Rest or Unpitched, etc.) matching the current SpineEvent. @@ -2191,7 +2194,7 @@ def hdStringToNote(contents): >>> n = humdrum.spineParser.hdStringToNote('gg#q/LL') >>> n.duration - + >>> n.duration.isGrace True @@ -2370,16 +2373,19 @@ def hdStringToNote(contents): thisObject.duration.dots = contents.count('.') # call Duration.TupletFixer after to correct this. + elif defaultDuration is not None: + thisObject.duration = defaultDuration + # 3.2.9 Grace Notes and Groupettos - if 'q' in contents: - thisObject = thisObject.getGrace() - thisObject.duration.type = 'eighth' + if qCount := contents.count('q'): + thisObject.getGrace(inPlace=True) + if qCount == 2: + thisObject.duration.slash = False elif 'Q' in contents: - thisObject = thisObject.getGrace() + thisObject.getGrace(inPlace=True) thisObject.duration.slash = False - thisObject.duration.type = 'eighth' elif 'P' in contents: - thisObject = thisObject.getGrace(appoggiatura=True) + thisObject.getGrace(appoggiatura=True, inPlace=True) elif 'p' in contents: pass # end appoggiatura duration -- not needed in music21... @@ -2899,6 +2905,33 @@ def testSingleNote(self): self.assertEqual(b.duration.dots, 0) self.assertEqual(b.duration.tuplets[0].durationNormal.dots, 2) + def testGraceNote(self): + ks = KernSpine() + # noinspection SpellCheckingInspection + a = ks.processNoteEvent('4Cq') + self.assertEqual(a.duration.type, 'quarter') + self.assertEqual(a.duration.slash, True) + + def testGraceNote2(self): + ks = KernSpine() + # noinspection SpellCheckingInspection + a = ks.processNoteEvent('16Cqq') + self.assertEqual(a.duration.type, '16th') + self.assertEqual(a.duration.slash, False) + + def testChord(self): + ks = KernSpine() + # noinspection SpellCheckingInspection + c = ks.processChordEvent('8C 8E') + self.assertEqual(len(c.notes), 2) + self.assertEqual(c.notes[0].duration, c.duration) + + def testChord2(self): + # noinspection SpellCheckingInspection + ks = KernSpine() + c = ks.processChordEvent('8C E') + self.assertEqual(c.notes[0].duration, c.notes[1].duration) + def testMeasureBoundaries(self): m0 = stream.Measure() m1 = hdStringToMeasure('=29a;:|:', m0) From b290d329d18742c88179a2d3e5ee64f2ad54052b Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Wed, 1 May 2024 14:48:20 +0200 Subject: [PATCH 2/8] Address mypy typing errors --- music21/humdrum/spineParser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index f0822198b..31e701b85 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -1295,7 +1295,7 @@ def __init__(self, spineId: int = 0, eventList=None, streamClass=stream.Stream): super().__init__(spineId, eventList, streamClass) self.lastContainer = None self.inTuplet = None - self.lastNote = None + self.lastNote: t.Optional[note.Note | note.Rest | chord.Chord] = None self.currentBeamNumbers = 0 self.currentTupletDuration = 0.0 self.desiredTupletDuration = 0.0 @@ -1378,7 +1378,7 @@ def processChordEvent(self, eventC: str) -> chord.Chord: if i == 0: defaultDuration = thisNote.duration chordNotes.append(thisNote) - eventChord = chord.Chord(chordNotes, beams=chordNotes[-1].beams, + eventChord = chord.Chord(chordNotes, beams=chordNotes[-1].beams, # type: ignore duration=defaultDuration) self.setBeamsForNote(eventChord) self.setTupletTypeForNote(eventChord) @@ -2207,7 +2207,7 @@ def hdStringToNote(contents: str, # 3.2.1 Pitches and 3.3 Rests matchedNote = re.search('([a-gA-G]+)', contents) - thisObject = None + thisObject: t.Optional[t.Union[note.Note, note.Rest]] = None # Detect rests first, because rests can contain manual positioning information, # which is also detected by the `matchedNote` variable above. From 3eace65b2cc044199192420ff0b8a0a8e6143142 Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Wed, 1 May 2024 15:14:05 +0200 Subject: [PATCH 3/8] Address more mypy issues --- music21/humdrum/spineParser.py | 62 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index 31e701b85..de7d5693a 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -2222,20 +2222,20 @@ def hdStringToNote(contents: str, else: # below middle C octave = 4 - len(kernNoteName) thisObject = note.Note(octave=octave) - thisObject.step = step + thisObject.step = step # type: ignore - else: - raise HumdrumException(f'Could not parse {contents} for note information') + matchedSharp = re.search(r'(#+)', contents) + matchedFlat = re.search(r'(-+)', contents) - matchedSharp = re.search(r'(#+)', contents) - matchedFlat = re.search(r'(-+)', contents) + if matchedSharp: + thisObject.pitch.accidental = matchedSharp.group(0) + elif matchedFlat: + thisObject.pitch.accidental = matchedFlat.group(0) + elif 'n' in contents: + thisObject.pitch.accidental = 'n' - if matchedSharp: - thisObject.pitch.accidental = matchedSharp.group(0) - elif matchedFlat: - thisObject.pitch.accidental = matchedFlat.group(0) - elif 'n' in contents: - thisObject.pitch.accidental = 'n' + else: + raise HumdrumException(f'Could not parse {contents} for note information') # 3.2.2 -- Slurs, Ties, Phrases # TODO: add music21 phrase information @@ -2309,13 +2309,6 @@ def hdStringToNote(contents: str, elif 'u' in contents: thisObject.articulations.append(articulations.DownBow()) - # 3.2.6 Stem Directions - if '/' in contents: - thisObject.stemDirection = 'up' - elif '\\' in contents: - thisObject.stemDirection = 'down' - - # 3.2.7 Duration + # 3.2.8 N-Tuplets @@ -2366,7 +2359,7 @@ def hdStringToNote(contents: str, JRP = flavors['JRP'] if JRP is False and '.' in contents: newTup.durationNormal = duration.durationTupleFromTypeDots( - newTup.durationNormal.type, contents.count('.')) + newTup.durationNormal.type, contents.count('.')) # type: ignore thisObject.duration.appendTuplet(newTup) if JRP is True and '.' in contents: @@ -2380,25 +2373,32 @@ def hdStringToNote(contents: str, if qCount := contents.count('q'): thisObject.getGrace(inPlace=True) if qCount == 2: - thisObject.duration.slash = False + thisObject.duration.slash = False # type: ignore elif 'Q' in contents: thisObject.getGrace(inPlace=True) - thisObject.duration.slash = False + thisObject.duration.slash = False # type: ignore elif 'P' in contents: thisObject.getGrace(appoggiatura=True, inPlace=True) elif 'p' in contents: pass # end appoggiatura duration -- not needed in music21... - # 3.2.10 Beaming - # TODO: Support really complex beams - for i in range(contents.count('L')): - thisObject.beams.append('start') - for i in range(contents.count('J')): - thisObject.beams.append('stop') - for i in range(contents.count('k')): - thisObject.beams.append('partial', 'right') - for i in range(contents.count('K')): - thisObject.beams.append('partial', 'right') + if thisObject.isNote: # handle note-specific attributes + # 3.2.6 Stem Directions + if '/' in contents: + thisObject.stemDirection = 'up' + elif '\\' in contents: + thisObject.stemDirection = 'down' + + # 3.2.10 Beaming + # TODO: Support really complex beams + for i in range(contents.count('L')): + thisObject.beams.append('start') + for i in range(contents.count('J')): + thisObject.beams.append('stop') + for i in range(contents.count('k')): + thisObject.beams.append('partial', 'right') + for i in range(contents.count('K')): + thisObject.beams.append('partial', 'right') return thisObject From 91e2a65b3ff7def56b98381a1460b220e27e6337 Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Wed, 1 May 2024 15:39:09 +0200 Subject: [PATCH 4/8] Handle more mypy errors --- music21/humdrum/spineParser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index de7d5693a..8dbf0b4b3 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -2207,7 +2207,7 @@ def hdStringToNote(contents: str, # 3.2.1 Pitches and 3.3 Rests matchedNote = re.search('([a-gA-G]+)', contents) - thisObject: t.Optional[t.Union[note.Note, note.Rest]] = None + thisObject = None # type: ignore # Detect rests first, because rests can contain manual positioning information, # which is also detected by the `matchedNote` variable above. @@ -2228,11 +2228,11 @@ def hdStringToNote(contents: str, matchedFlat = re.search(r'(-+)', contents) if matchedSharp: - thisObject.pitch.accidental = matchedSharp.group(0) + thisObject.pitch.accidental = matchedSharp.group(0) # type: ignore elif matchedFlat: - thisObject.pitch.accidental = matchedFlat.group(0) + thisObject.pitch.accidental = matchedFlat.group(0) # type: ignore elif 'n' in contents: - thisObject.pitch.accidental = 'n' + thisObject.pitch.accidental = 'n' # type: ignore else: raise HumdrumException(f'Could not parse {contents} for note information') From f503cb6cb84bd937fb0f683b1a1f47a94976b27b Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Wed, 1 May 2024 18:05:58 +0200 Subject: [PATCH 5/8] Resolve mypy errors in spineParser.py --- music21/humdrum/spineParser.py | 37 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index 8dbf0b4b3..055f8088d 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -2207,7 +2207,7 @@ def hdStringToNote(contents: str, # 3.2.1 Pitches and 3.3 Rests matchedNote = re.search('([a-gA-G]+)', contents) - thisObject = None # type: ignore + thisObject: t.Optional[t.Union[note.Note, note.Rest]] = None # Detect rests first, because rests can contain manual positioning information, # which is also detected by the `matchedNote` variable above. @@ -2234,6 +2234,23 @@ def hdStringToNote(contents: str, elif 'n' in contents: thisObject.pitch.accidental = 'n' # type: ignore + # Handle note-specific attributes + # 3.2.6 Stem Directions + if '/' in contents: + thisObject.stemDirection = 'up' + elif '\\' in contents: + thisObject.stemDirection = 'down' + # 3.2.10 Beaming + # TODO: Support really complex beams + for i in range(contents.count('L')): + thisObject.beams.append('start') + for i in range(contents.count('J')): + thisObject.beams.append('stop') + for i in range(contents.count('k')): + thisObject.beams.append('partial', 'right') + for i in range(contents.count('K')): + thisObject.beams.append('partial', 'right') + else: raise HumdrumException(f'Could not parse {contents} for note information') @@ -2382,24 +2399,6 @@ def hdStringToNote(contents: str, elif 'p' in contents: pass # end appoggiatura duration -- not needed in music21... - if thisObject.isNote: # handle note-specific attributes - # 3.2.6 Stem Directions - if '/' in contents: - thisObject.stemDirection = 'up' - elif '\\' in contents: - thisObject.stemDirection = 'down' - - # 3.2.10 Beaming - # TODO: Support really complex beams - for i in range(contents.count('L')): - thisObject.beams.append('start') - for i in range(contents.count('J')): - thisObject.beams.append('stop') - for i in range(contents.count('k')): - thisObject.beams.append('partial', 'right') - for i in range(contents.count('K')): - thisObject.beams.append('partial', 'right') - return thisObject From 020758fbc4be564a3405b5c9fcb96e88ac41d834 Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Wed, 1 May 2024 23:59:03 +0200 Subject: [PATCH 6/8] Create Duration before notes/rests to avoid placeholder durations --- music21/humdrum/spineParser.py | 194 ++++++++++++++++++--------------- 1 file changed, 104 insertions(+), 90 deletions(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index 055f8088d..bf7001fdd 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -68,6 +68,7 @@ from music21 import meter from music21 import metadata from music21 import roman +from music21 import pitch from music21 import prebase from music21 import stream from music21 import tempo @@ -2120,6 +2121,83 @@ def getAllOccurring(self): return retEvents +def _hdStringToDuration(contents: str, + defaultDuration: duration.Duration|None = None) -> duration.Duration: + ''' + returns a :class:`~music21.duration.Duration` matching the current + SpineEvent. + + This is used internally by hdStringToNote to figure out the duration part + of a humdrum note or rest in a kern spine. + ''' + # 3.2.7 Duration + + # 3.2.8 N-Tuplets + dotCount = contents.count('.') + durationRegex = re.search(r'(\d+)%?(\d+)?', contents) + if durationRegex: + foundNumber, foundRational = durationRegex.groups() + + if foundRational: + durationFirst = int(foundNumber) + durationSecond = float(foundRational) + quarterLength = 4 * durationSecond / durationFirst + thisDuration = duration.Duration(quarterLength, dots=dotCount) + + elif foundNumber: + durationType = int(foundNumber) + if durationType == 0: + if foundNumber == '000': + # for larger values, see https://extras.humdrum.org/man/rscale/ + _type = 'maxima' + elif foundNumber == '00': + # for larger values, see https://extras.humdrum.org/man/rscale/ + _type = 'longa' + else: + _type = 'breve' + thisDuration = duration.Duration(type=_type, dots=dotCount) + elif durationType in duration.typeFromNumDict: + _type = duration.typeFromNumDict[durationType] + thisDuration = duration.Duration(type=_type, dots=dotCount) + else: + dT = durationType + 0.0 + (unused_remainder, exponents) = math.modf(math.log2(dT)) + baseValue = 2 ** exponents + baseValueInt = int(baseValue) + _type = duration.typeFromNumDict[baseValueInt] + thisDuration = duration.Duration(type=_type) + newTup = duration.Tuplet() + newTup.durationActual = duration.durationTupleFromTypeDots(_type, 0) + newTup.durationNormal = duration.durationTupleFromTypeDots(_type, 0) + + gcd = math.gcd(durationType, baseValueInt) + newTup.numberNotesActual = int(dT / gcd) + newTup.numberNotesNormal = int(baseValue / gcd) + + # The Josquin Research Project uses an incorrect definition of + # humdrum tuplets that breaks normal usage. TODO: Refactor adding a Flavor = 'JRP' + # code that uses this other method... + JRP = flavors['JRP'] + if JRP is False and dotCount: + newTup.durationNormal = duration.durationTupleFromTypeDots( + newTup.durationNormal.type, dotCount) # type: ignore + + thisDuration.appendTuplet(newTup) + if JRP is True and dotCount: + thisDuration.dots = dotCount + # call Duration.TupletFixer after to correct this. + + elif defaultDuration is not None: + thisDuration = defaultDuration + + else: # no duration string or default duration given + if 'q' in contents: + thisDuration = duration.Duration(0.5, dots=dotCount) + else: + thisDuration = duration.Duration(1, dots=dotCount) + + return thisDuration + + def hdStringToNote(contents: str, defaultDuration: duration.Duration|None = None) -> note.Note | note.Rest: ''' @@ -2204,35 +2282,31 @@ def hdStringToNote(contents: str, # http://www.lib.virginia.edu/artsandmedia/dmmc/Music/Humdrum/kern_hlp.html#kern - # 3.2.1 Pitches and 3.3 Rests + # Determine duration part first to avoid making an unused duration + thisDuration = _hdStringToDuration(contents, defaultDuration) - matchedNote = re.search('([a-gA-G]+)', contents) - thisObject: t.Optional[t.Union[note.Note, note.Rest]] = None + # 3.2.1 Pitches and 3.3 Rests # Detect rests first, because rests can contain manual positioning information, # which is also detected by the `matchedNote` variable above. if 'r' in contents: - thisObject = note.Rest() + thisObject = note.Rest(duration=thisDuration) - elif matchedNote: + elif (matchedNote := re.search('([a-gA-G]+)([n#-]*)', contents)): kernNoteName = matchedNote.group(1) step = kernNoteName[0].lower() if step == kernNoteName[0]: # middle C or higher octave = 3 + len(kernNoteName) else: # below middle C octave = 4 - len(kernNoteName) - thisObject = note.Note(octave=octave) - thisObject.step = step # type: ignore - matchedSharp = re.search(r'(#+)', contents) - matchedFlat = re.search(r'(-+)', contents) + accid = matchedNote.group(2) + if accid: + _pitch = pitch.Pitch(step, octave=octave, accidental=accid) + else: + _pitch = pitch.Pitch(step, octave=octave) - if matchedSharp: - thisObject.pitch.accidental = matchedSharp.group(0) # type: ignore - elif matchedFlat: - thisObject.pitch.accidental = matchedFlat.group(0) # type: ignore - elif 'n' in contents: - thisObject.pitch.accidental = 'n' # type: ignore + thisObject = note.Note(_pitch, duration=thisDuration) # Handle note-specific attributes # 3.2.6 Stem Directions @@ -2256,14 +2330,14 @@ def hdStringToNote(contents: str, # 3.2.2 -- Slurs, Ties, Phrases # TODO: add music21 phrase information - for i in range(contents.count('{')): - pass # phraseMark start - for i in range(contents.count('}')): - pass # phraseMark end - for i in range(contents.count('(')): - pass # slur start - for i in range(contents.count(')')): - pass # slur end + # for i in range(contents.count('{')): + # pass # phraseMark start + # for i in range(contents.count('}')): + # pass # phraseMark end + # for i in range(contents.count('(')): + # pass # slur start + # for i in range(contents.count(')')): + # pass # slur end if '[' in contents: thisObject.tie = tie.Tie('start') elif ']' in contents: @@ -2295,10 +2369,10 @@ def hdStringToNote(contents: str, t1.connectedToPrevious = True # true by default, but explicitly thisObject.expressions.append(t1) - if ':' in contents: - # TODO: deal with arpeggiation -- should have been in a - # chord structure - pass + # if ':' in contents: + # # TODO: deal with arpeggiation -- should have been in a + # # chord structure + # pass if 'O' in contents: thisObject.expressions.append(expressions.Ornament()) @@ -2326,68 +2400,8 @@ def hdStringToNote(contents: str, elif 'u' in contents: thisObject.articulations.append(articulations.DownBow()) - # 3.2.7 Duration + - # 3.2.8 N-Tuplets - - # TODO: SPEEDUP -- only search for rational after foundNumber... - foundRational = re.search(r'(\d+)%(\d+)', contents) - foundNumber = re.search(r'(\d+)', contents) - if foundRational: - durationFirst = int(foundRational.group(1)) - durationSecond = float(foundRational.group(2)) - thisObject.duration.quarterLength = 4 * durationSecond / durationFirst - if '.' in contents: - thisObject.duration.dots = contents.count('.') - - elif foundNumber: - durationType = int(foundNumber.group(1)) - if durationType == 0: - durationString = foundNumber.group(1) - if durationString == '000': - # for larger values, see https://extras.humdrum.org/man/rscale/ - thisObject.duration.type = 'maxima' - elif durationString == '00': - # for larger values, see https://extras.humdrum.org/man/rscale/ - thisObject.duration.type = 'longa' - else: - thisObject.duration.type = 'breve' - if '.' in contents: - thisObject.duration.dots = contents.count('.') - elif durationType in duration.typeFromNumDict: - thisObject.duration.type = duration.typeFromNumDict[durationType] - if '.' in contents: - thisObject.duration.dots = contents.count('.') - else: - dT = int(durationType) + 0.0 - (unused_remainder, exponents) = math.modf(math.log2(dT)) - baseValue = 2 ** exponents - thisObject.duration.type = duration.typeFromNumDict[int(baseValue)] - newTup = duration.Tuplet() - newTup.durationActual = duration.durationTupleFromTypeDots(thisObject.duration.type, 0) - newTup.durationNormal = duration.durationTupleFromTypeDots(thisObject.duration.type, 0) - - gcd = math.gcd(int(dT), int(baseValue)) - newTup.numberNotesActual = int(dT / gcd) - newTup.numberNotesNormal = int(float(baseValue) / gcd) - - # The Josquin Research Project uses an incorrect definition of - # humdrum tuplets that breaks normal usage. TODO: Refactor adding a Flavor = 'JRP' - # code that uses this other method... - JRP = flavors['JRP'] - if JRP is False and '.' in contents: - newTup.durationNormal = duration.durationTupleFromTypeDots( - newTup.durationNormal.type, contents.count('.')) # type: ignore - - thisObject.duration.appendTuplet(newTup) - if JRP is True and '.' in contents: - thisObject.duration.dots = contents.count('.') - # call Duration.TupletFixer after to correct this. - - elif defaultDuration is not None: - thisObject.duration = defaultDuration - # 3.2.9 Grace Notes and Groupettos - if qCount := contents.count('q'): + if (qCount := contents.count('q')): thisObject.getGrace(inPlace=True) if qCount == 2: thisObject.duration.slash = False # type: ignore @@ -2396,8 +2410,8 @@ def hdStringToNote(contents: str, thisObject.duration.slash = False # type: ignore elif 'P' in contents: thisObject.getGrace(appoggiatura=True, inPlace=True) - elif 'p' in contents: - pass # end appoggiatura duration -- not needed in music21... + # elif 'p' in contents: + # pass # end appoggiatura duration -- not needed in music21... return thisObject From 875e412388de78e5e7489717358cbef042a09099 Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Thu, 2 May 2024 00:11:32 +0200 Subject: [PATCH 7/8] Remove whitespace and fix mypy errors --- music21/humdrum/spineParser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index bf7001fdd..b40028b4c 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -2128,7 +2128,7 @@ def _hdStringToDuration(contents: str, SpineEvent. This is used internally by hdStringToNote to figure out the duration part - of a humdrum note or rest in a kern spine. + of a humdrum note or rest in a kern spine. ''' # 3.2.7 Duration + # 3.2.8 N-Tuplets @@ -2286,7 +2286,7 @@ def hdStringToNote(contents: str, thisDuration = _hdStringToDuration(contents, defaultDuration) # 3.2.1 Pitches and 3.3 Rests - + thisObject: t.Union[note.Note|note.Rest] # Detect rests first, because rests can contain manual positioning information, # which is also detected by the `matchedNote` variable above. if 'r' in contents: From 502740484af34d396d2eb720bdf14e23841fff07 Mon Sep 17 00:00:00 2001 From: Alexander Morgan Date: Thu, 2 May 2024 00:21:34 +0200 Subject: [PATCH 8/8] Fix docstring test --- music21/humdrum/spineParser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index b40028b4c..5afb3c592 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -2272,7 +2272,7 @@ def hdStringToNote(contents: str, >>> n = humdrum.spineParser.hdStringToNote('gg#q/LL') >>> n.duration - + >>> n.duration.isGrace True