MIDIUtil icon indicating copy to clipboard operation
MIDIUtil copied to clipboard

IndexError with overlapping notes

Open lassik opened this issue 7 years ago • 6 comments

When the caller adds two overlapping notes (same time, same pitch) with different durations, then writeFile() causes the following exception to be raised in deInterleaveNotes():

File "./midiutil-overlapping-note-bug.py", line 16, in <module>
    midifile.writeFile(sys.stdout.buffer)
  File "[...]/midiutil/MidiFile.py", line 1637, in writeFile
    self.close()
  File "[...]/midiutil/MidiFile.py", line 1688, in close
    self.tracks[i].closeTrack()
  File "[...]/midiutil/MidiFile.py", line 826, in closeTrack
    self.processEventList()
  File "[...]/midiutil/MidiFile.py", line 789, in processEventList
    self.deInterleaveNotes()
  File "[...]/midiutil/MidiFile.py", line 889, in deInterleaveNotes
    stack[noteeventkey].pop()
IndexError: pop from empty list

(I think it's confused when a note-off event comes before the corresponding note-on event in the stream. Are the events sorted funny?)

The problem does not seem to arise when the overlapping notes have the exact same duration.

One could argue that it's the caller's responsibility not to add overlapping notes, but it's easy to do so by accident so it would be nice if the library did one of the following:

  1. merge the notes when writing the midi file (keep the one with the longest duration?)
  2. merge the notes when adding them
  3. silently drop overlapping notes when they are added, keeping only the first note
  4. raise an easy-to-understand exception saying something about overlapping notes

Here's a minimal example to reproduce the error:

#! /usr/bin/env python3

import sys

from midiutil.MidiFile import MIDIFile

TRACK = CHANNEL = TIME = 0
VOLUME = 100
DURATION1 = 1.0
DURATION2 = 2.0
PITCH = 42

midifile = MIDIFile(1, adjust_origin=False)
midifile.addNote(TRACK, CHANNEL, PITCH, TIME, DURATION1, VOLUME)
midifile.addNote(TRACK, CHANNEL, PITCH, TIME, DURATION2, VOLUME)
midifile.writeFile(sys.stdout.buffer)

I'm using MIDIUtil==1.2.1 from pip.

lassik avatar Sep 13 '18 17:09 lassik

I tried changing the code in file MidiFile.py line 889 form

else:
    stack[noteeventkey].pop()
    tempEventList.append(event)

to

else:
    if not stack[noteeventkey]==[]:
        stack[noteeventkey].pop()
        tempEventList.append(event)

and it works

ToverPomelo avatar Feb 14 '20 12:02 ToverPomelo

I had same error which I fixed with : elif len(stack[noteeventkey]) == 1: stack[noteeventkey].pop() tempEventList.append(event)

bossagypsy1 avatar Jun 04 '21 14:06 bossagypsy1

Its a really great module thanks so much for all the hard work

I had another error from the section before

KeyError 450

line 878 elif event.evtname == 'NoteOff': if len(stack[noteeventkey]) > 1: event.tick = stack[noteeventkey].pop() tempEventList.append(event)

I don't know how to reproduce, but this was from the result of scanning hundreds of Ableton XML midi files and converting them to .mid

I put a temp fix in with Try/Except

Sorry I cant help more

I am just making the assumption that there could be some badly formed inputs and covering all bases

bossagypsy1 avatar Jun 04 '21 14:06 bossagypsy1

I have also had the strange IndexError: pop from empty list even though I shouldn't have duplicate notes, and after trying @ToverPomelo's solution I get: KeyError: '580'

kevinlinxc avatar Aug 02 '21 08:08 kevinlinxc

For me, I didn't think I had any duplicate notes but I kept getting the error so I just ignored it:

                    else:
                        try:
                            stack[noteeventkey].pop()
                            tempEventList.append(event)
                        except IndexError as e:
                            print("IndexError skipped")

kevinlinxc avatar Aug 02 '21 22:08 kevinlinxc

I found a workaround until a solution is found for this library. set deinterleave to False

self.mid = MIDIFile(
          numTracks=num_tracks,
          removeDuplicates=True,
          deinterleave=False,
          adjust_origin=False,
          file_format=2,
          ticks_per_quarternote=960,
          eventtime_is_ticks=True)

fornof avatar Jan 09 '23 04:01 fornof