Problem summary
MIDI import is too quick to create voices and too reluctant to create chords because the "chord subbing" tolerance is different from the quantization units. The "chord subbing" is not configurable--a 64th note, regardless of the quantization units, meaning any onsets after a 64th note window jump to another voice instead of being collected onto a chord. Then when quantization runs later, it's too late--you've got voices when chords would have been appropriate, given the quantization scheme.
music21's MIDI parsing is very literal, which is great, but I think there are a couple of low-effort batteries-included improvements we could do in this area. Eventually I'll write up the other idea I have on quantization.
Steps to reproduce
File on #684
s = converter.parse('example-from-684'.mid)
s.parts[4].voices[0].show('t')
Expected vs. actual behavior
Before: {5.0}
After this proposal : {5.0} <music21.chord.Chord E4 C#4>
Suggested fix
index b2c8606da..e4eab20d4 100644
--- a/music21/midi/translate.py
+++ b/music21/midi/translate.py
@@ -1755,6 +1755,12 @@ def midiTrackToStream(
i = 0
iGathered = [] # store a list of indexes of gathered values put into chords
voicesRequired = False
+
+ if 'quarterLengthDivisors' in keywords:
+ quarterLengthDivisors = keywords['quarterLengthDivisors']
+ else:
+ quarterLengthDivisors = defaults.quantizationQuarterLengthDivisors
+
if len(notes) > 1:
# environLocal.printDebug(['\n', 'midiTrackToStream(): notes', notes])
while i < len(notes):
@@ -1779,10 +1785,15 @@ def midiTrackToStream(
tSub, unused_eSub = onSub
tOffSub, unused_eOffSub = offSub
- # can set a tolerance for chordSubbing; here at 1/16th
- # of a quarter
- chunkTolerance = ticksPerQuarter / 16
- if abs(tSub - t) <= chunkTolerance:
+ # let tolerance for chord subbing follow the quantization
+ if quantizePost:
+ divisor = max(quarterLengthDivisors)
+ # fallback: 1/16 of a quarter (64th)
+ else:
+ divisor = 16
+ chunkTolerance = ticksPerQuarter / divisor
+ # must be strictly less than the quantization unit
+ if abs(tSub - t) < chunkTolerance:
# isolate case where end time is not w/n tolerance
if abs(tOffSub - tOff) > chunkTolerance:
# need to store this as requiring movement to a diff
@@ -1839,10 +1850,6 @@ def midiTrackToStream(
s.coreElementsChanged()
# quantize to nearest 16th
if quantizePost:
- if 'quarterLengthDivisors' in keywords:
- quarterLengthDivisors = keywords['quarterLengthDivisors']
- else:
- quarterLengthDivisors = None
s.quantize(quarterLengthDivisors=quarterLengthDivisors,
processOffsets=True,
processDurations=True,
music21 version
6.7
Problem summary MIDI import is too quick to create voices and too reluctant to create chords because the "chord subbing" tolerance is different from the quantization units. The "chord subbing" is not configurable--a 64th note, regardless of the quantization units, meaning any onsets after a 64th note window jump to another voice instead of being collected onto a chord. Then when quantization runs later, it's too late--you've got voices when chords would have been appropriate, given the quantization scheme.
music21's MIDI parsing is very literal, which is great, but I think there are a couple of low-effort batteries-included improvements we could do in this area. Eventually I'll write up the other idea I have on quantization.
Steps to reproduce File on #684
Expected vs. actual behavior Before: {5.0}
After this proposal : {5.0} <music21.chord.Chord E4 C#4>
Suggested fix