music21.midi

Objects and tools for processing MIDI data. Converts from MIDI files to MidiEvent, MidiTrack, and MidiFile objects, and vice-versa.

Further conversion to-and-from MidiEvent/MidiTrack/MidiFile and music21 Stream, Note, etc., objects takes place in music21.midi.translate.

This module originally used routines from Will Ware’s public domain midi.py library from 2001 which was once posted at (http link) groups.google.com/g/alt.sources/msg/0c5fc523e050c35e

ChannelModeMessages

class music21.midi.ChannelModeMessages(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

ChannelModeMessages bases

  • _ContainsEnum

ChannelVoiceMessages

class music21.midi.ChannelVoiceMessages(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

ChannelVoiceMessages bases

  • _ContainsEnum

DeltaTime

class music21.midi.DeltaTime(track, time=0, channel=None)

A MidiEvent subclass that stores the time change (in ticks) since the start or since the last MidiEvent.

Pairs of DeltaTime and MidiEvent objects are the basic presentation of temporal data.

The track argument must be a MidiTrack object.

Time values are in integers, representing ticks.

The channel attribute, inherited from MidiEvent is not used and set to None unless overridden (don’t!).

>>> mt = midi.MidiTrack(1)
>>> dt = midi.DeltaTime(mt)
>>> dt.time = 1
>>> dt
<music21.midi.DeltaTime t=1, track=1, channel=None>

DeltaTime bases

DeltaTime read-only properties

Read-only properties inherited from MidiEvent:

Read-only properties inherited from ProtoM21Object:

DeltaTime read/write properties

Read/write properties inherited from MidiEvent:

DeltaTime methods

DeltaTime.getBytes() bytes

Convert the time integer into a set of bytes.

>>> mt = midi.MidiTrack(1)
>>> dt = midi.DeltaTime(mt)
>>> dt.time = 1
>>> dt.getBytes()
b'\x01'
>>> dt.time = 128
>>> dt.getBytes()
b'\x81\x00'
>>> dt.time = 257
>>> dt.getBytes()
b'\x82\x01'
>>> dt.time = 16385
>>> dt.getBytes()
b'\x81\x80\x01'
DeltaTime.readUntilLowByte(oldBytes: bytes) tuple[int, bytes]

Read a byte-string until hitting a character below 0x80 and return the converted number and the rest of the bytes

>>> mt = midi.MidiTrack(1)
>>> dt = midi.DeltaTime(mt)
>>> dt.readUntilLowByte(b'\x20')
(32, b'')
>>> dt.readUntilLowByte(b'\x20hello')
(32, b'hello')

here the ‘x82’ is above 0x80 so the ‘h’ is read as part of the continuation.

>>> dt.readUntilLowByte(b'\x82hello')
(360, b'ello')

Changed in v9: was read() but had an incompatible signature with MidiEvent

Methods inherited from MidiEvent:

Methods inherited from ProtoM21Object:

MetaEvents

class music21.midi.MetaEvents(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

MetaEvents bases

  • _ContainsEnum

MidiEvent

class music21.midi.MidiEvent(track: MidiTrack | None = None, type=None, time: int = 0, channel: int | None = None)

A model of a MIDI event, including note-on, note-off, program change, controller change, any many others.

MidiEvent objects are paired (preceded) by DeltaTime objects in the list of events in a MidiTrack object.

The track argument must be a MidiTrack object.

The type attribute is an enumeration of a Midi event from the ChannelVoiceMessages or metaEvents enums.

The channel attribute is an integer channel id, from 1 to 16.

The time attribute is an integer duration of the event in ticks. This value can be zero. This value is not essential, as ultimate time positioning is determined by DeltaTime objects.

The pitch attribute is only defined for note-on and note-off messages. The attribute stores an integer representation (0-127, with 60 = middle C).

The velocity attribute is only defined for note-on and note-off messages. The attribute stores an integer representation (0-127). A note-on message with velocity 0 is generally assumed to be the same as a note-off message.

The data attribute is used for storing other messages, such as SEQUENCE_TRACK_NAME string values.

Warning

The attributes .midiProgram and .midiChannel on Instrument objects are 0-indexed, just as they need to be in the written binary .mid. However, as a convenience, MidiEvent.channel is 1-indexed. No analogous convenience is provided for program change data.

>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me1.channel = 3
>>> me1.time = 200
>>> me1.pitch = 60
>>> me1.velocity = 120
>>> me1
<music21.midi.MidiEvent NOTE_ON, t=200, track=1, channel=3, pitch=60, velocity=120>
>>> me2 = midi.MidiEvent(mt)
>>> me2.type = midi.MetaEvents.SEQUENCE_TRACK_NAME
>>> me2.data = 'guitar'
>>> me2
<music21.midi.MidiEvent SEQUENCE_TRACK_NAME, track=1, channel=None, data=b'guitar'>

MidiEvent bases

MidiEvent read-only properties

MidiEvent.sortOrder

Ensure that for MidiEvents at the same “time”, that order is NOTE_OFF, PITCH_BEND, all others.

>>> CVM = midi.ChannelVoiceMessages
>>> noteOn = midi.MidiEvent(type=CVM.NOTE_ON)
>>> noteOff = midi.MidiEvent(type=CVM.NOTE_OFF)
>>> pitchBend = midi.MidiEvent(type=CVM.PITCH_BEND)
>>> sorted([noteOn, noteOff, pitchBend], key=lambda me: me.sortOrder)
[<music21.midi.MidiEvent NOTE_OFF, track=None, channel=None>,
 <music21.midi.MidiEvent PITCH_BEND, track=None, channel=None>,
 <music21.midi.MidiEvent NOTE_ON, track=None, channel=None>]

Read-only properties inherited from ProtoM21Object:

MidiEvent read/write properties

MidiEvent.data

Read or set the data (.parameter1) for the object

Does some automatic conversions:

>>> me = midi.MidiEvent(type=midi.ChannelModeMessages.LOCAL_CONTROL)
>>> me.data = True
>>> me.data
b'\x01'
MidiEvent.pitch
MidiEvent.velocity

MidiEvent methods

MidiEvent.getBytes()

Return a set of bytes for this MIDI event.

>>> noteOn = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=10)
>>> noteOn.pitch = 60
>>> noteOn.velocity = 127
>>> noteOn.getBytes()
b'\x99<\x7f'
>>> noteOn.pitch = 62
>>> noteOn.getBytes()
b'\x99>\x7f'
MidiEvent.isDeltaTime()

Return a boolean if this is a DeltaTime subclass.

>>> mt = midi.MidiTrack(1)
>>> dt = midi.DeltaTime(mt)
>>> dt.isDeltaTime()
True
MidiEvent.isNoteOff()

Return a boolean if this should be interpreted as a note-off message, either as a real note-off or as a note-on with zero velocity.

>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.ChannelVoiceMessages.NOTE_OFF
>>> me1.isNoteOn()
False
>>> me1.isNoteOff()
True
>>> me2 = midi.MidiEvent(mt)
>>> me2.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me2.velocity = 0
>>> me2.isNoteOn()
False
>>> me2.isNoteOff()
True
MidiEvent.isNoteOn()

Return a boolean if this is a note-on message and velocity is not zero.

>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me1.velocity = 120
>>> me1.isNoteOn()
True
>>> me1.isNoteOff()
False
MidiEvent.matchedNoteOff(other)

Returns True if other is a MIDI event that specifies a note-off message for this message. That is, this event is a NOTE_ON message, and the other is a NOTE_OFF message for this pitch on this channel. Otherwise returns False

>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me1.velocity = 120
>>> me1.pitch = 60
>>> me2 = midi.MidiEvent(mt)
>>> me2.type = midi.ChannelVoiceMessages.NOTE_ON
>>> me2.velocity = 0
>>> me2.pitch = 60

me2 is a Note off for me1 because it has velocity 0 and matches pitch.

>>> me1.matchedNoteOff(me2)
True

Now the pitch does not match, so it does not work.

>>> me2.pitch = 61
>>> me1.matchedNoteOff(me2)
False
>>> me2.type = midi.ChannelVoiceMessages.NOTE_OFF
>>> me1.matchedNoteOff(me2)
False
>>> me2.pitch = 60
>>> me1.matchedNoteOff(me2)
True

Channels must match also:

>>> me2.channel = 12
>>> me1.matchedNoteOff(me2)
False
MidiEvent.parseChannelVoiceMessage(midiBytes: bytes) bytes

Take a set of bytes that represent a ChannelVoiceMessage and set the appropriate enumeration value and data, returning the remaining bytes.

These are started with messages from 0x80 to 0xEF

Demonstration. First let’s create a helper function and a MidiEvent:

>>> to_bytes = midi.intsToHexBytes
>>> midBytes = to_bytes([0x90, 60, 120])
>>> midBytes
b'\x90<x'
>>> midBytes += b'hello'
>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1
<music21.midi.MidiEvent None, track=1, channel=None>

Now show how the midiBytes changes the event:

>>> remainder = me1.parseChannelVoiceMessage(midBytes)
>>> me1
<music21.midi.MidiEvent NOTE_ON, track=1, channel=1, pitch=60, velocity=120>

The remainder would probably contain a delta time and following events, but here we’ll just show that it passes through.

>>> remainder
b'hello'

We will ignore remainders from now on.

All attributes are set properly:

>>> me1.type
<ChannelVoiceMessages.NOTE_ON: 0x90>
>>> me1.pitch  # 60 = middle C
60
>>> me1.velocity
120
>>> me1.channel
1

Here we send the message for a note on another channel (0x91 = channel 2):

>>> rem = me1.parseChannelVoiceMessage(to_bytes([0x91, 60, 120]))
>>> me1
<music21.midi.MidiEvent NOTE_ON, track=1, channel=2, pitch=60, velocity=120>
>>> me1.channel
2

Now let’s make a program change

>>> me2 = midi.MidiEvent(mt)
>>> rem = me2.parseChannelVoiceMessage(to_bytes([0xC0, 71]))
>>> me2
<music21.midi.MidiEvent PROGRAM_CHANGE, track=1, channel=1, data=71>
>>> me2.data  # 71 = clarinet (0-127 indexed)
71

Program change and channel pressure only go to 127. More than that is an error:

>>> me2.parseChannelVoiceMessage(to_bytes([0xC0, 200]))
Traceback (most recent call last):
music21.midi.MidiException: Cannot have a
    <ChannelVoiceMessages.PROGRAM_CHANGE: 0xC0> followed by a byte > 127: 200
MidiEvent.read(midiBytes: bytes) bytes

Parse the bytes given and take the beginning section and convert it into data for this event and return the now truncated bytes.

>>> channel = 0x2
>>> noteOnMessage = midi.ChannelVoiceMessages.NOTE_ON | channel
>>> hex(noteOnMessage)
'0x92'

This is how the system reads note-on messages (0x90-0x9F) and channels

>>> hex(0x91 & 0xF0)  # testing message type extraction
'0x90'
>>> hex(0x92 & 0xF0)  # testing message type extraction
'0x90'
>>> (0x90 & 0x0F) + 1  # getting the channel
1
>>> (0x9F & 0x0F) + 1  # getting the channel
16
MidiEvent.setPitchBend(cents, bendRange=2)
Treat this event as a pitch bend value, and set the .parameter1 and

.parameter2 fields appropriately given a specified bend value in cents.

Also called Pitch Wheel

The bendRange parameter gives the number of half steps in the bend range.

>>> mt = midi.MidiTrack(1)
>>> me1 = midi.MidiEvent(mt)
>>> me1.setPitchBend(50)
>>> me1.parameter1, me1.parameter2
(0, 80)
>>> me1.setPitchBend(100)
>>> me1.parameter1, me1.parameter2
(0, 96)

Neutral is 0, 64

>>> me1.setPitchBend(0)
>>> me1.parameter1, me1.parameter2
(0, 64)

Parameter 2 is the most significant digit, not parameter 1.

>>> me1.setPitchBend(101)
>>> me1.parameter1, me1.parameter2
(40, 96)

Exceeding maximum sets max

>>> me1.setPitchBend(200)
>>> me1.parameter1, me1.parameter2
(127, 127)
>>> me1.setPitchBend(300)
>>> me1.parameter1, me1.parameter2
(127, 127)
>>> me1.setPitchBend(-50)
>>> me1.parameter1, me1.parameter2
(0, 48)
>>> me1.setPitchBend(-100)
>>> me1.parameter1, me1.parameter2
(0, 32)
>>> me1.setPitchBend(-196)
>>> me1.parameter1, me1.parameter2
(36, 1)
>>> me1.setPitchBend(-200)
>>> me1.parameter1, me1.parameter2
(0, 0)

Again, excess trimmed

>>> me1.setPitchBend(-300)
>>> me1.parameter1, me1.parameter2
(0, 0)

But a larger bendRange can be set in semitones for non-GM devices:

>>> me1.setPitchBend(-300, bendRange=4)
>>> me1.parameter1, me1.parameter2
(0, 16)
>>> me1.setPitchBend(-399, bendRange=4)
>>> me1.parameter1, me1.parameter2
(20, 0)

Methods inherited from ProtoM21Object:

MidiFile

class music21.midi.MidiFile

Low-level MIDI file writing, emulating methods from normal Python files.

For most users, do not go here, simply use:

score = converter.parse(‘path/to/file/in.mid’) midi_out = score.write(‘midi’, fp=’path/to/file/out.mid’)

The ticksPerQuarterNote attribute must be set before writing. 1024 is a common value.

This object is returned by some properties for directly writing files of midi representations.

>>> mf = midi.MidiFile()
>>> mf
<music21.midi.MidiFile 0 tracks>

Music21 can read format 0 and format 1, and writes format 1. Format 2 files are not parsable.

>>> mf.format
1

After loading or before writing, tracks are stored in this list.

>>> mf.tracks
[]

Most midi files store ticksPerQuarterNote and not ticksPerSecond

>>> mf.ticksPerQuarterNote
10080
>>> mf.ticksPerSecond is None
True

All MidiFiles have the same headerId

>>> midi.MidiFile.headerId
b'MThd'

MidiFile bases

MidiFile read-only properties

Read-only properties inherited from ProtoM21Object:

MidiFile methods

MidiFile.close()

Close the file.

MidiFile.open(filename, attrib='rb')

Open a MIDI file path for reading or writing.

For writing to a MIDI file, attrib should be “wb”.

MidiFile.openFileLike(fileLike)

Assign a file-like object, such as those provided by BytesIO, as an open file object.

>>> from io import BytesIO
>>> fileLikeOpen = BytesIO()
>>> mf = midi.MidiFile()
>>> mf.openFileLike(fileLikeOpen)
>>> mf.close()
MidiFile.read()

Read and parse MIDI data stored in a file.

MidiFile.readstr(midiBytes)

Read and parse MIDI data as a bytes, putting the data in .ticksPerQuarterNote and a list of MidiTrack objects in the attribute .tracks.

The name readstr is a carryover from Python 2. It works on bytes objects, not strings

MidiFile.write()

Write MIDI data as a file to the file opened with .open().

MidiFile.writeMThdStr() bytes

Convert the information in self.ticksPerQuarterNote into MIDI data header and return it as bytes.

The name writeMThdStr is a carry-over from Python 2. It works on bytes, not strings.

MidiFile.writestr() bytes

Generate the MIDI data header and convert the list of MidiTrack objects in self.tracks into MIDI data and return it as bytes.

The name writestr is a carry-over from Python 2. It works on bytes, not strings.

Methods inherited from ProtoM21Object:

MidiTrack

class music21.midi.MidiTrack(index=0)

A MIDI Track. Each track contains an index and a list of events.

All events are stored in the events list, in order.

An index is an integer identifier for this object. It is often called “trackId” though technically the id for a MidiTrack is always b’MTrk’

>>> mt = midi.MidiTrack(index=3)
>>> mt.events
[]
>>> mt.index
3

The data consists of all the midi data after b’MTrk’

>>> mt.data
b''

And the .length is the same as the data’s length

>>> mt.length
0

After reading a string

>>> mt.read(b'MTrk\x00\x00\x00\x16\x00\xff\x03\x00\x00'
...         + b'\xe0\x00@\x00\x90CZ\x88\x00\x80C\x00\x88\x00\xff/\x00')
b''

The returned value is what is left over after the track is read (for instance the data for another track)

>>> mt.length
22
>>> mt.data[:8]
b'\x00\xff\x03\x00\x00\xe0\x00@'

Note that the ‘x16’ got translated to ascii ‘@’.

>>> mt.events
[<music21.midi.DeltaTime (empty) track=3, channel=None>,
 <music21.midi.MidiEvent SEQUENCE_TRACK_NAME, track=3, channel=None, data=b''>,
 <music21.midi.DeltaTime (empty) track=3, channel=None>,
 <music21.midi.MidiEvent PITCH_BEND, track=3, channel=1, parameter1=0, parameter2=64>,
 <music21.midi.DeltaTime (empty) track=3, channel=None>,
 <music21.midi.MidiEvent NOTE_ON, track=3, channel=1, pitch=67, velocity=90>,
 <music21.midi.DeltaTime t=1024, track=3, channel=None>,
 <music21.midi.MidiEvent NOTE_OFF, track=3, channel=1, pitch=67, velocity=0>,
 <music21.midi.DeltaTime t=1024, track=3, channel=None>,
 <music21.midi.MidiEvent END_OF_TRACK, track=3, channel=None, data=b''>]
>>> mt
<music21.midi.MidiTrack 3 -- 10 events>

There is a class attribute of the headerId which is the same for all MidiTrack objects

>>> midi.MidiTrack.headerId
b'MTrk'

MidiTrack bases

MidiTrack read-only properties

MidiTrack.length

Read-only properties inherited from ProtoM21Object:

MidiTrack methods

MidiTrack.getBytes()

returns bytes of midi-data from the .events in the object.

>>> mt = midi.MidiTrack(index=2)
>>> noteOn = midi.MidiEvent(mt, type=midi.ChannelVoiceMessages.NOTE_ON, channel=1)
>>> noteOn.pitch = 60
>>> noteOn.velocity = 20
>>> dt = midi.DeltaTime(mt, time=1024)
>>> noteOff = midi.MidiEvent(mt, type=midi.ChannelVoiceMessages.NOTE_OFF, channel=1)
>>> noteOff.pitch = 60
>>> noteOff.velocity = 0
>>> mt.events = [noteOn, dt, noteOff]
>>> mt.getBytes()
b'MTrk\x00\x00\x00\x08\x90<\x14\x88\x00\x80<\x00'

The b’x08’ indicates that there are 8 bytes to follow.

MidiTrack.getChannels()

Get all channels (excluding None) used in this Track (sorted)

>>> mt = midi.MidiTrack(index=2)
>>> noteOn = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=14)
>>> noteOn2 = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=5)
>>> noteOn3 = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=14)
>>> noteOn4 = midi.MidiEvent(type=midi.ChannelVoiceMessages.PROGRAM_CHANGE, channel=None)
>>> mt.events = [noteOn, noteOn2, noteOn3, noteOn4]
>>> mt.getChannels()
[5, 14]
MidiTrack.getProgramChanges()

Get all unique program changes used in this Track, in order they appear.

>>> mt = midi.MidiTrack(index=2)
>>> pc1 = midi.MidiEvent(type=midi.ChannelVoiceMessages.PROGRAM_CHANGE)
>>> pc1.data = 14
>>> noteOn = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=14)
>>> pc2 = midi.MidiEvent(type=midi.ChannelVoiceMessages.PROGRAM_CHANGE)
>>> pc2.data = 1
>>> mt.events = [pc1, noteOn, pc2]
>>> mt.getProgramChanges()
[14, 1]
MidiTrack.hasNotes()

Return True/False if this track has any note-ons defined.

>>> mt = midi.MidiTrack(index=2)
>>> mt.hasNotes()
False
>>> noteOn = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=1)
>>> mt.events = [noteOn]
>>> mt.hasNotes()
True
MidiTrack.processDataToEvents(trackData: bytes = b'') None

Populate .events with trackData. Called by .read()

MidiTrack.read(midiBytes: bytes) bytes

Read as much of the bytes object (representing midi data) as necessary; return the remaining bytes object for reassignment and further processing.

The string should begin with MTrk, specifying a Midi Track

Calls processDataToEvents which creates and stores DeltaTime and MidiEvent objects.

MidiTrack.setChannel(value)

Set the channel of all events in this Track.

>>> mt = midi.MidiTrack(index=2)
>>> noteOn = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=1)
>>> mt.events = [noteOn]
>>> mt.setChannel(11)
>>> noteOn.channel
11

Channel must be a value from 1-16

>>> mt.setChannel(22)
Traceback (most recent call last):
music21.midi.MidiException: bad channel value: 22
MidiTrack.updateEvents()

We may attach events to this track before setting their track parameter. This method will move through all events and set their track to this track.

>>> mt = midi.MidiTrack(index=2)
>>> noteOn = midi.MidiEvent(type=midi.ChannelVoiceMessages.NOTE_ON, channel=1)
>>> noteOn.pitch = 60
>>> noteOn.velocity = 20
>>> noteOn
<music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=60, velocity=20>
>>> mt.events = [noteOn]
>>> mt.updateEvents()
>>> noteOn
<music21.midi.MidiEvent NOTE_ON, track=2, channel=1, pitch=60, velocity=20>
>>> noteOn.track is mt
True

Methods inherited from ProtoM21Object:

SysExEvents

class music21.midi.SysExEvents(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

SysExEvents bases

  • _ContainsEnum

Functions

music21.midi.charToBinary(char)

Convert a char into its binary representation. Useful for debugging.

>>> midi.charToBinary('a')
'01100001'
music21.midi.getNumber(midiStr, length)

Return the value of a string byte or bytes if length > 1 from an 8-bit string or bytes object

Then, return the remaining string or bytes object

The length is the number of chars to read. This will sum a length greater than 1 if desired.

Note that MIDI uses big-endian for everything. This is the inverse of Python’s chr() function.

>>> midi.getNumber('test', 0)
(0, 'test')

Given bytes, return bytes:

>>> midi.getNumber(b'test', 0)
(0, b'test')
>>> midi.getNumber('test', 2)
(29797, 'st')
>>> midi.getNumber(b'test', 4)
(1952805748, b'')
music21.midi.getNumbersAsList(midiBytes)

Translate each char into a number, return in a list. Used for reading data messages where each byte encodes a different discrete value.

>>> midi.getNumbersAsList(b'\x00\x00\x00\x03')
[0, 0, 0, 3]
music21.midi.getVariableLengthNumber(midiBytes)

Given a string or bytes of data, strip off the first character, or all high-byte characters terminating with one whose ord() function is < 0x80. Thus, a variable number of bytes might be read.

After finding the appropriate termination, return the remaining string.

This is necessary as DeltaTime times are given with variable size, and thus may be of different numbers if characters are used.

>>> midi.getVariableLengthNumber(b'A-u')
(65, b'-u')
>>> midi.getVariableLengthNumber(b'-u')
(45, b'u')
>>> midi.getVariableLengthNumber('u')
(117, b'')
>>> midi.getVariableLengthNumber(b'test')
(116, b'est')
>>> midi.getVariableLengthNumber(b'E@-E')
(69, b'@-E')
>>> midi.getVariableLengthNumber(b'@-E')
(64, b'-E')
>>> midi.getVariableLengthNumber(b'-E')
(45, b'E')
>>> midi.getVariableLengthNumber('E')
(69, b'')

Test that variable length characters work:

>>> midi.getVariableLengthNumber(b'\xff\x7f')
(16383, b'')
>>> midi.getVariableLengthNumber('中xy')
(210638584, b'y')

If no low-byte character is encoded, raises an IndexError

>>> midi.getVariableLengthNumber('中国')
Traceback (most recent call last):
IndexError: index out of range
music21.midi.intsToHexBytes(intList)

Convert a list of integers into hex bytes, suitable for testing MIDI encoding.

Here we take NOTE_ON message, Middle C, 120 velocity and translate it to bytes

>>> midi.intsToHexBytes([0x90, 60, 120])
b'\x90<x'
music21.midi.putNumber(num, length)

Put a single number as a hex number at the end of a string length bytes long.

>>> midi.putNumber(3, 4)
b'\x00\x00\x00\x03'
>>> midi.putNumber(0, 1)
b'\x00'
music21.midi.putNumbersAsList(numList)

Translate a list of numbers (0-255) into bytes. Used for encoding data messages where each byte encodes a different discrete value.

>>> midi.putNumbersAsList([0, 0, 0, 3])
b'\x00\x00\x00\x03'

If a number is < 0 then it wraps around from the top.

>>> midi.putNumbersAsList([0, 0, 0, -3])
b'\x00\x00\x00\xfd'
>>> midi.putNumbersAsList([0, 0, 0, -1])
b'\x00\x00\x00\xff'

list can be of any length

>>> midi.putNumbersAsList([1, 16, 255])
b'\x01\x10\xff'

Any number > 255 raises an exception:

>>> midi.putNumbersAsList([256])
Traceback (most recent call last):
music21.midi.MidiException: Cannot place a number > 255 in a list: 256
music21.midi.putVariableLengthNumber(x)

Turn a number into the smallest bytes object that can hold it for MIDI

>>> midi.putVariableLengthNumber(4)
b'\x04'
>>> midi.putVariableLengthNumber(127)
b'\x7f'
>>> midi.putVariableLengthNumber(0)
b'\x00'

Numbers > 7bit but < 255 need two characters, with the first character to 0x80 + n // 128

>>> midi.putVariableLengthNumber(128)
b'\x81\x00'
>>> midi.putVariableLengthNumber(129)
b'\x81\x01'
>>> midi.putVariableLengthNumber(255)
b'\x81\x7f'
>>> midi.putVariableLengthNumber(256)
b'\x82\x00'
>>> midi.putVariableLengthNumber(1024)
b'\x88\x00'
>>> midi.putVariableLengthNumber(8193)
b'\xc0\x01'
>>> midi.putVariableLengthNumber(16383)
b'\xff\x7f'

Numbers > 16383 are not really MIDI numbers, but this is how they work:

>>> midi.putVariableLengthNumber(16384)
b'\x81\x80\x00'
>>> midi.putVariableLengthNumber(16384 + 128)
b'\x81\x81\x00'
>>> midi.putVariableLengthNumber(16384 * 2)
b'\x82\x80\x00'
>>> midi.putVariableLengthNumber(16384 ** 2)
b'\x81\x80\x80\x80\x00'
>>> midi.putVariableLengthNumber(-1)
Traceback (most recent call last):
music21.midi.MidiException: cannot putVariableLengthNumber() when number is negative: -1