-
Notifications
You must be signed in to change notification settings - Fork 433
Description
music21 version
9.5.0
Operating System(s) checked
OS agnostic
(I am on Ubuntu 24.04, though, but that should be irrelevant.)
Problem summary
music21.midi.MidiEvent.getBytes() confuses SEQUENCER_SPECIFIC_META_EVENT (0x7F) with POLY_MODE_ON (also 0x7F) and will therefore cause a TypeError if the former does not care about channels (i.e self.channel == None).
Steps to reproduce
from music21 import midi
ev = midi.MidiEvent(type=midi.MetaEvents.SEQUENCER_SPECIFIC_META_EVENT, channel=None)
print(ev)
b = ev.getBytes()FYI: Actually I encountered this issue not by instantiating a MidiEvent manually but by writing a MidiFile which comes with some SEQUENCER_SPECIFIC_META_EVENT's.
mf = midi.MidiFile()
mf.open(inputfile)
mf.read()
mf.close()
# write the contents back immediately without touching anything
mf.open(outputfile, 'wb')
mf.write()
mf.close()
Expected vs. actual behavior
EXPECTED BEHAVIOUR:
music21.midi.MidiEvent.getBytes() should process all MIDI events correctly without triggering any exception.
ACTUAL BEHAVIOUR:
File "/foo/bar/lib/python3.12/site-packages/music21/midi/__init__.py", line 982, in getBytes
channelMode = bytes([0xB0 + self.channel - 1])
~~~~~^~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
More information
The current code of music21.midi.MidiEvent.getBytes() checks the event type essentially by looking at its value only:
def getBytes(self):
if self.type in ChannelVoiceMessages:
...
elif self.type in ChannelModeMessages:
channelMode = bytes([0xB0 + self.channel - 1])
...
elif self.type in SysExEvents:
...
elif self.type in MetaEvents:
...
else:
...However:
class ChannelModeMessages(_ContainsEnum):
...
POLY_MODE_ON = 0x7F
...class MetaEvents(_ContainsEnum):
...
SEQUENCER_SPECIFIC_META_EVENT = 0x7F
...Apparently, getBytes() will mistake a SEQUENCER_SPECIFIC_META_EVENT as a POLY_MODE_ON, and will then fail immediately if self.channel is null.
Therefore, it is probably more appropriate to also check the event class, in addition to the event type value:
if isinstance(self.type, ChannelVoiceMessages) and self.type in ChannelVoiceMessages:
...
elif isinstance(self.type, ChannelModeMessages) and self.type in ChannelModeMessages:
...
elif isinstance(self.type, SysExEvents) and self.type in SysExEvents:
...
elif isinstance(self.type, MetaEvents) and self.type in MetaEvents:
...