Skip to content

music21.midi.MidiEvent.getBytes() confuses SEQUENCER_SPECIFIC_META_EVENT (0x7F) with POLY_MODE_ON (also 0x7F) #1772

@ptolemeow

Description

@ptolemeow

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:
    ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions