MarkCWirt / MIDIUtil

A pure Python library for creating multi-track MIDI files
Other
247 stars 49 forks source link

IndexError with overlapping notes #24

Open lassik opened 6 years ago

lassik commented 6 years ago

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.

ToverPomelo commented 4 years ago

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

bossagypsy1 commented 3 years ago

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

bossagypsy1 commented 3 years ago

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

kevinlinxc commented 3 years ago

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 commented 3 years ago

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")
fornof commented 1 year ago

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)