Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading duplicate note-off events results in zero-duration notes #131

Closed
bzamecnik opened this issue Jul 18, 2017 · 2 comments
Closed

Reading duplicate note-off events results in zero-duration notes #131

bzamecnik opened this issue Jul 18, 2017 · 2 comments

Comments

@bzamecnik
Copy link
Contributor

bzamecnik commented Jul 18, 2017

When the input MIDI file contains note-off events for already inactive notes (with note-off events given before) pretty-midi still creates note events with zero duration. Writing such notes then results in spurious long-running notes that were not present originally.

Note that in this case there were two overlapping notes: note-on, note-on, note-off, note-off.

Example: bach-js_kunst_der_fuge_01_(c)unknown[2].mid (source: kunstderfuge.com)

image

If we zoom in the the start of the track 1 (first voice). Original looks like:

image

And written one:

image

Mido print_track() - original:

<meta message midi_port port=1 time=0>
<meta message track_name name='Contrapunctus I' time=0>
<message control_change channel=0 control=121 value=0 time=0>
<message note_on channel=0 note=69 velocity=88 time=1536>
<message note_on channel=0 note=74 velocity=88 time=192>
<message note_on channel=0 note=69 velocity=0 time=0>
<message note_on channel=0 note=72 velocity=88 time=192>
<message note_on channel=0 note=74 velocity=0 time=0>
<message note_on channel=0 note=69 velocity=88 time=192>
<message note_on channel=0 note=72 velocity=0 time=0>
<message note_on channel=0 note=68 velocity=88 time=192>
<message note_on channel=0 note=69 velocity=0 time=0>
<message note_on channel=0 note=69 velocity=88 time=192>
<message note_on channel=0 note=68 velocity=0 time=0>
<message note_on channel=0 note=71 velocity=88 time=96>
<message note_on channel=0 note=69 velocity=0 time=0>
<message note_on channel=0 note=72 velocity=88 time=96> <-- note-on
<message note_on channel=0 note=71 velocity=0 time=0>
<message note_on channel=0 note=72 velocity=88 time=192> <-- another note-on!
<message note_on channel=0 note=72 velocity=0 time=0> <-- proper note-off
<message note_on channel=0 note=74 velocity=88 time=48>
<message note_on channel=0 note=72 velocity=0 time=0> <-- duplicate note-off!
<message note_on channel=0 note=72 velocity=88 time=48>
<message note_on channel=0 note=74 velocity=0 time=0>
<message note_on channel=0 note=70 velocity=88 time=48>
<message note_on channel=0 note=72 velocity=0 time=0>
<message note_on channel=0 note=69 velocity=88 time=48>
<message note_on channel=0 note=70 velocity=0 time=0>
<message note_on channel=0 note=62 velocity=72 time=48>
<message note_on channel=0 note=69 velocity=0 time=0>
<message note_on channel=0 note=74 velocity=72 time=48>
<message note_on channel=0 note=62 velocity=0 time=0>
<message note_on channel=0 note=73 velocity=72 time=192>
<message note_on channel=0 note=74 velocity=0 time=0>

Mido print_track() - written:

written - bad:
<meta message track_name name='Contrapunctus I' time=0>
<message program_change channel=0 program=0 time=0>
<message control_change channel=0 control=121 value=0 time=0>
<message note_on channel=0 note=69 velocity=88 time=1536>
<message note_on channel=0 note=69 velocity=0 time=192>
<message note_on channel=0 note=74 velocity=88 time=0>
<message note_on channel=0 note=72 velocity=88 time=192>
<message note_on channel=0 note=74 velocity=0 time=0>
<message note_on channel=0 note=69 velocity=88 time=192>
<message note_on channel=0 note=72 velocity=0 time=0>
<message note_on channel=0 note=68 velocity=88 time=192>
<message note_on channel=0 note=69 velocity=0 time=0>
<message note_on channel=0 note=68 velocity=0 time=192>
<message note_on channel=0 note=69 velocity=88 time=0>
<message note_on channel=0 note=69 velocity=0 time=96>
<message note_on channel=0 note=71 velocity=88 time=0>
<message note_on channel=0 note=71 velocity=0 time=96>
<message note_on channel=0 note=72 velocity=88 time=0> <-- first note-on
<message note_on channel=0 note=72 velocity=0 time=192> <-- first note-off
<message note_on channel=0 note=72 velocity=0 time=0> <-- second note-off
<message note_on channel=0 note=72 velocity=88 time=0> <-- second note-on!
<message note_on channel=0 note=74 velocity=88 time=48>
<message note_on channel=0 note=72 velocity=88 time=48> <-- another note-on!
<message note_on channel=0 note=74 velocity=0 time=0>
<message note_on channel=0 note=70 velocity=88 time=48>
<message note_on channel=0 note=72 velocity=0 time=0> <-- note off
<message note_on channel=0 note=69 velocity=88 time=48>
<message note_on channel=0 note=70 velocity=0 time=0>
<message note_on channel=0 note=62 velocity=72 time=48>
<message note_on channel=0 note=69 velocity=0 time=0>
<message note_on channel=0 note=62 velocity=0 time=48>
<message note_on channel=0 note=74 velocity=72 time=0>
<message note_on channel=0 note=73 velocity=72 time=192>
<message note_on channel=0 note=74 velocity=0 time=0>

If we extract the input sequence of two notes of pitch 72:

<message note_on channel=0 note=72 velocity=88 time=0>
<message note_on channel=0 note=72 velocity=88 time=192>
<message note_on channel=0 note=72 velocity=0 time=0>
<message note_on channel=0 note=72 velocity=0 time=48>

it seems that the meaning was as follows (two consequent notes of length 192 and 48):

<message note_on channel=0 note=72 velocity=88 time=0>
<message note_on channel=0 note=72 velocity=0 time=192>
<message note_on channel=0 note=72 velocity=88 time=0>
<message note_on channel=0 note=72 velocity=0 time=48>

The only problem was that the note-off event preceded the note-on event, even though they occurred at the same time. Instead we created one note of duration 192, then one zero-duration note and one long note that was closed after encountering next note-off for that pitch.

We should fix two bugs here:

  • when processing events at single time step, first close open notes, then open new notes
  • ignore duplicate note-off events when reading MIDI
  • remove zero-duration events in case they appear
bzamecnik added a commit to bzamecnik/pretty-midi that referenced this issue Jul 18, 2017
In particular when there are two adjacent notes and at the same tick there's
first note-on and then note-off, the library incorrectly closes both notes, even
though the second note should be still open. Such a MIDI is probably a bit
malformed, but the library should not fail on that if it can resolve the situation.

Due to this bug the library incorrectly produces zero-duration events and some
long open notes.

The solution is to keep the note-on event in the list of open notes if it's at the
same tick as the note-off event and we're closing some previous notes.

Note that we changed the format of time within values of the last_note_on dict
- from real time to integer tick. This is to avoid floating point comparison when
we have the original integer values.
bzamecnik added a commit to bzamecnik/pretty-midi that referenced this issue Jul 18, 2017
In particular when there are two adjacent notes and at the same tick there's
first note-on and then note-off, the library incorrectly closes both notes, even
though the second note should be still open. Such a MIDI is probably a bit
malformed, but the library should not fail on that if it can resolve the situation.

Due to this bug the library incorrectly produces zero-duration events and some
long open notes.

The solution is to keep the note-on event in the list of open notes if it's at the
same tick as the note-off event and we're closing some previous notes.

Note that we changed the format of time within values of the last_note_on dict
- from real time to integer tick. This is to avoid floating point comparison when
we have the original integer values.
@bzamecnik
Copy link
Contributor Author

bzamecnik commented Jul 18, 2017

After fixing the piano roll read from the original MIDI seems to be fixed. Note that that rest is gone.

image

Full piece:

image

@craffel
Copy link
Owner

craffel commented Jul 19, 2017

Thanks for debugging this.

When the input MIDI file contains note-off events for already inactive notes (with note-off events given before) pretty-midi still creates note events with zero duration. Writing such notes then results in spurious long-running notes that were not present originally.

While I can understand why we are creating zero-duration notes, and why this is bad, I don't understand where writing zero-duration notes results in spurious long-running notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants