Closed laurentVeliscek closed 3 years ago
heya @laurentVeliscek, thanks for the report.
There are MIDI messages in your file I don't understand. The following is from the second track of music:
>>> mf = midi.MidiFile()
>>> mf.open('../Desktop/test.mid')
>>> mf.read()
>>> mf.close()
>>> mf.tracks[2].events[:8]
[<MidiEvent DeltaTime, t=0, track=2, channel=None>, <MidiEvent SEQUENCE_TRACK_NAME, t=0, track=2, channel=None, data=b'goldberg.mid'>,
<MidiEvent DeltaTime, t=20, track=2, channel=None>, <MidiEvent NOTE_ON, t=0, track=2, channel=2, pitch=71, velocity=124>,
<MidiEvent DeltaTime, t=12, track=2, channel=None>, <MidiEvent CONTROLLER_CHANGE, t=0, track=2, channel=2, parameter1=7, parameter2=107>,
<MidiEvent DeltaTime, t=468, track=2, channel=None>, <MidiEvent NOTE_ON, t=0, track=2, channel=2, pitch=71, velocity=0>]
The pair of events in the second row looks fishy: I don't think music21 is interpreting that correctly. Here's the binary:
>>> mf.tracks[2].data
b'\x00\xff\x03\x0cgoldberg.mid\x14\x91G|\x0c\xb1\x07k\x83T\x91G\x00\x00L|$Jk\x10L\x00,Le\x0cJ ...
The 0x14 after the goldberg.mid I don't understand, and neither does music21 -- music21 gives up and converts 0x14 to an integer to get a value of 20, which becomes your DeltaTime of 20 ticks, which turns out to be a 1/96th rest. Here are your timepoints even after turning quantization off (see the mailing list for some posts on this recently):
>>> s = converter.parse('../Desktop/test.mid', quantizePost=False, forceSource=True)
>>> s.parts[1].makeNotation(inPlace=True)
>>> s.parts[1].measure(1).show('text')
{0.0} <music21.clef.TrebleClef>
{0.0} <music21.meter.TimeSignature 3/4>
{0.0} <music21.stream.Voice 0>
{0.0} <music21.note.Rest rest>
{0.0417} <music21.note.Note B>
{1.1167} <music21.note.Note D>
{2.55} <music21.note.Note D>
{0.0} <music21.stream.Voice 1>
{1.0417} <music21.note.Note E>
{1.2417} <music21.note.Note E>
{2.775} <music21.note.Note C>
So the B that should start at 0.0 actually starts at 0.0417. That 20-tick 1/96th rest is what's hosing the rest of your score.
The 0x14 has to be signifying something else, but I have no idea. After some poking around this is my hypothesis: -- 0x14 is used in the WAV file format to declare the MIDI unity note for playback on a MIDI sampler -- 0x14 in MIDI is used to specify cues or loop markers, which music21 doesn't support -- a MIDI writer got confused and wrote the MIDI unity note it parsed from a WAV file into the MIDI output -- music21 got confused on account of its lack of support for cue or loop markers and just converted into the fishy events as above, giving you a 1/96th rest.
Notice \x91G|
in \x14\x91G|
could be a MIDI unity note, since it's exactly that first B4 in the track (x9: note_on prefix, 1G: pitch value (71), |: velocity value (124):
<MidiEvent DeltaTime, t=20, track=2, channel=None>, <MidiEvent NOTE_ON, t=0, track=2, channel=2, pitch=71, velocity=124>
When I load the MIDI into GarageBand, I see ticks very early in the tracks, suggesting markers of some kind, which is bonkers, but at least follows the MIDI spec.
Finally, may I ask -- where did this file come from? If MuseScore is writing it, we can report it to them, and look into a workaround on this end, but if it's less common than that I doubt we can do much. At the very least, you can possibly get rid of these microscopic rests and try building a new stream from everything else. good luck!
Hi, Thank you very much for your answer.
I 've built from scratch a midifile using Ableton Live. If the notes are quantified, it seems to work almost fine (see later the problem with the final whole notes that get truncated.)
But if the're not quantified, I get some errors.
I had to use museScore to merge the 2 parts into one single midifile as Ableton Live can only export single parts as midifiles.
Then I tried:
s = converter.parse('../pathTo/test.mid', quantizePost=False, forceSource=True)
I tried to import and show() my unQuantified midifiles.
The fact that the midifile was exported from sibelius or museScore doesn't change anything. M21 crashes(in module noteStream.show()):
music21.duration.DurationException: Cannot return types smaller than 2048th; qLen was: 1/960
music21.musicxml.m21ToXml.MusicXMLExportException: Cannot convert "2048th" duration to MusicXML (too short).
Notice that there's no "very short notes" in the score. So I guess the problem may occur when rounding delta-time to a valid value for the specified clock.
I join the full traceback error reports as text files:
traceback partsFromLiveFromSibelius.txt traceback partsFromLiveMergedUsingMuseScore.txt
The crashing midiFiles are in midi files.zip, attached at the end of this message.
Then I've done more tests using the default converter.parse() (with no instructions)
I noticed a strange behavior: After importing a very simple 2-parts score made in museScore, and showing it, the last note's duration isn't correct. The ending whole of the score becomes a half dotted dotted dotted.
before:
After import:
The whole seems to be shortened before the show() function call.
Here's the midiFile: verySimple2partsMidifile.mid.zip
Is it normal ?
Another bug, may be: I noticed that the time signature wasn't imported properly with a chopin waltz (testChopin.mid)). Both museScore and Sibelius see the score as a 3/4 time signature, when M21 see a 4/4 score.
Finally, I've made some experiments with an unquantified midiFile.
I can import and show each part separatley using m21.
If I merge them into a single score using museScore, and import the score in m21, I can show each part, but when trying to show() the full Score, I get the "not a valid musicXML" alert from museScore, and crash it if try to open it.
I loaded and exported the same 2-parts unquantified midiFile using Sibelius. So now, the midifile is generated by Sibelius.
Then, the first part is showed, the second part triggers a MusicXml Error but can be "showed", and I can show() the full score.
Finally, I tried to open in Sibelius the temp "corrupted" XML files generated by M21 by the show() command. But with no success. (Sibelius complains "UNEXPECTED")
So it seems that those XML files really get corrupted, and that's not specifically a museScore bug.
I attach the midifiles I used for testing... midi files.zip
I guess there's an issue somewhere with how M21 deals with note durations. May be it opens a duration tag, but forgets to close it in some circumstances.
Still digging in...
Thanks again for your help !
:-)
Laurent.
Hi Laurent,
from .mid straight into Finale:
from .mid to music21 with default quantization to xml, displayed in Finale [looks correct]:
>>> s.show('text')
{0.0} <music21.stream.Part 0x7fab825cbac8>
{0.0} <music21.instrument.Piano 'Piano, 1-Kontakt 1\x00'>
I think this is probably also a MuseScore issue. I can't recreate this example inside Finale and get the same result.
Hi again,
You're absolutely right. I was so confident in museScore I couldn't imagine that it would create incorrect midiFiles from a score, so I didn't check this. (sorry!.. :-/ )
The "Dotted dotted dotted" midifile was written in museScore using mouse clicks. I didn't check/uncheck anything about "human playback".
It seems that museScore has a problem with durations. Here, I've just written 4 measures of 4 whole notes, using my mouse-clicks.
and after midi export from museScore I get this in Ableton Live:
(!)
So I believe I have to use another score editor until those museScore bugs get fixed... (So bad, I love museScore interface...)
I gonna make some more tests using Sibelius and let you know if everything is okay...
Thanks !
L.
Hi again,
I've re-done my tests using Sibelius.
Still a crash if I set forceSource=True (But I don't really understand the point of this option)
But works much better. No more XML errors.
\o/
I've just noticed that the note.durations are not displayed as real float values, even if they seem to not be quantized. (I've set parse with quantizePost=False)
Is there a way to display the "real" duration expressed as a float ? (I couldn't find the answer exploring the documentation)
I'd like to check something.
Here's what I import into m21 with quantizePost=False partition2partsNonQuantiseSibelius.mid.zip
partition2partsNonQuantiseSibelius.mid:
After import, I show the stream, and then export it to a midifile with museScore. The upper part seems to be quantized.
Curiously, if I write the imported stream using midi_out = score.write(‘midi’, fp=’path/to/file/out.mid’), It's the opposite. the lower is quantized, but not the upper.
Not a big deal, but I prefer to share...
Thanks again for your help !
L.
You can just take float(note.duration.quarterLength)
if you want Fraction(1, 3)
-> 0.3333333333333333
. Don't know if there's a better way offhand.
And about the different quantization behavior, I don't know quite what's going on there, but in order to demonstrate a bug with music21 you would really need to pare it down to a midi file with just two or three notes, and show exactly how you're parsing it by quoting a block of code and then annotating the result of a function like s.show('text', addEndTimes=True)
.
Try this -- if you need finer control over the quantization units, use converter.parse(quarterLengthDivisors=(16))
where a value such as 16 represents 1/16 of a quarter (that is, a 64th note). The default is (4, 3), as in, sixteenths or triplet-eighths are both acceptable.
Let us know if you have a suggestion to improve the documentation. I haven't interacted with many users after making some contributions to the quantization functionality this summer. forceSource=True
shouldn't be necessary every time after #630 is merged. Good luck!
Unfortunately, you and I have each demonstrated issues with MuseScore's MIDI output--yours with duration, mine with null bytes in instrument names. @mscuthbert this is worth noting for future to discourage users from using MuseScore to write MIDI.
well, maybe some TODOs here for music21 after all...
1 -- should stream.quantize(processDurations=True)
enforce a minimum duration of the smallest quantization unit (unless the target is a grace note) to avoid writing out 0-duration elements? that's what caused the crazy display in the first post on this thread, also mentioned in #530, and since quantization is on by default...
2 -- should we implement a workaround for the empty byte that MuseScore is adding to instruments, since that will probably stay wrong for a while? as easy as just .split('\x00')[0]
Hi !
Thanks for your feedback. I've posted messages about those 2 issues on museScore forum.
1-- m21 can be fed with human unquantized midifiles, and having very very short notes is some unpredictable. A note with a zero duration doesn't make sense. At least, it must have a minimal duration > 0. So I believe this should be fixed. And a fix would avoid strange behaviors and bugs.
2-- As museScore is the default companion of m21, I agree with you that fixing things that can be fixed would avoid user's complains (like I did ;-). If a workaround is obvious (as you suggest), I thinks it's a good idea to implement it.
The more I dig in m21 documentation, the more I realize how powerful it is.
Thanks again for your amazing work !...
:-)
L.
music21 version
6.1.0
Problem summary
Something seems to be broken when importing multi-part midifiles
When I try to show() the imported score, I always get warnings about meter (?):
Then, I get a MuseScore error message "the xxx.xml file is not a valid MusicXML file". (here, the error message is in french, but that's the idea...)
If I try to open the file in museScore, it crashes, or if I'm lucky, I can display something, but the score seems to be corrupted.
Steps to reproduce
Expected vs. actual behavior
I wish I could import and manipulate the score transparently.
More information
Mac OS Mojave, python 3.7, all modules freshly installed and updated
I tried using many midifiles downloaded from different locations, I tried loading and saving with museScore, but the same results.
In fact, I couldn't find any multi-part midifile that can be imported and showed properly.
To illustrate this, I join 2 screen copies of the file before and after import, using museScore , so you can compare (notes timing are just messed up...)
I attached the test.mid file (zipped).
Did I miss something ?
Thanks !
test.mid.zip