A Mido message is a Python object with methods and attributes. The attributes will vary depending on message type.

To create a new message:

>>> mido.Message('note_on')
Message('note_on', channel=0, note=0, velocity=64, time=0)

You can pass attributes as keyword arguments:

>>> mido.Message('note_on', note=100, velocity=3, time=6.2)
Message('note_on', channel=0, note=100, velocity=3, time=6.2)

All attributes will default to 0. The exceptions are velocity, which defaults to 64 (middle velocity) and data which defaults to ().

You can set and get attributes as you would expect:

>>> msg = mido.Message('note_on')
>>> msg.note

The type attribute can be used to determine message type:

>>> msg.type

Attributes are also settable but it’s always better to use msg.copy():

>>> msg.copy(note=99, time=100.0)
Message('note_on', channel=0, note=99, velocity=64, time=100.0)


Mido always makes a copy of messages instead of modifying them so if you do the same you have immutable messages in practice. (Third party libraries may not follow the same rule.)


Frozen Messages are a variant of messages that are hashable and can be used as dictionary keys. They are also safe from tampering by third party libraries. You can freely convert between the two and use frozen messages wherever normal messages are allowed.

Mido supports all message types defined by the MIDI standard. For a full list of messages and their attributes, see Message Types.

Control Changes

if msg.is_cc():
    print('Control change message received')

if msg.is_cc(7):
    print('Volume changed to', msg.value)

Converting To & From Bytes

To Bytes

You can convert a message to MIDI bytes with one of these methods:

>>> msg = mido.Message('note_on')
>>> msg
Message('note_on', channel=0, note=0, velocity=64, time=0)
>>> msg.bytes()
[144, 0, 64]
>>> msg.bin()
>>> msg.hex()
'90 00 40'

From Bytes

You can turn bytes back into messages with the parser.

New in version 1.2.

You can also create a message from bytes using class methods:

msg1 = mido.Message.from_bytes([0x90, 0x40, 0x60])
msg2 = mido.Message.from_hex('90, 40 60')

The bytes must contain exactly one complete message. If not ValueError is raised.

The Time Attribute

Each message has a time attribute, which can be set to any value of type int or float.

Some parts of Mido use the attribute for special purposes. In MIDI file tracks, it is used as delta time (in ticks), and it must be a non-negative integer.

In other parts of Mido, this value is ignored.

Changed in version 1.1.18: In earlier versions, the time attribute was not included in comparisons. If you want the old behavior the easiest way is msg1.bytes() == msg2.bytes().

To sort messages on time you can do:

messages.sort(key=lambda message: message.time)


import operator


System Exclusive Messages

System Exclusive (aka SysEx) messages are used to send device specific data. The data attribute is a tuple of data bytes which serves as the payload of the message:

>>> msg = Message('sysex', data=[1, 2, 3])
>>> msg
Message('sysex', data=(1, 2, 3), time=0)
>>> msg.hex()
'F0 01 02 03 F7'

You can also extend the existing data:

>>> msg = Message('sysex', data=[1, 2, 3])
>>> += [4, 5]
>>> += [6, 7, 8]
>>> msg
Message('sysex', data=(1, 2, 3, 4, 5, 6, 7, 8), time=0)

Any sequence of integers between 0 and 127 is allowed, and type and range checking is applied to each data byte.

These are all valid:

(65, 66, 67)
[65, 66, 67]
(i + 65 for i in range(3))
(ord(c) for c in 'ABC')
b'ABC'  # Python 3 only.

For example:

>>> msg = Message('sysex', data=bytearray(b'ABC'))
>>> += bytearray(b'DEF')
>>> msg
Message('sysex', data=(65, 66, 67, 68, 69, 70), time=0)

Frozen Messages

New in version 1.2.

Since Mido messages are mutable (can change) they can not be hashed or put in dictionaries. This makes it hard to use them for things like Markov chains.

In these situations you can use frozen messages:

from mido.frozen import FrozenMessage

msg = FrozenMessage('note_on')
d = {msg: 'interesting'}

Frozen messages are used and behave in exactly the same way as normal messages with one exception: attributes are not settable.

There are also variants for meta messages (FrozenMetaMessage and FrozenUnknownMetaMessage).

You can freeze and thaw messages with:

from mido.frozen import freeze_message, thaw_message

frozen = freeze_message(msg)
thawed = thaw_message(frozen)

thaw_message() will always return a copy. Passing a frozen message to freeze_message() will return the original message.

Both functions return None if you pass None which is handy for things like:

msg = freeze_message(port.receive())

for msg in map(freeze_message, port):

To check if a message is frozen:

from mido.frozen import is_frozen

if is_frozen(msg):

Parsing MIDI Bytes

The MIDI protocol is a binary protocol. Each message is encoded as a status byte followed by up to three data bytes. (Except SysEx messages which can have an arbitrary number of data bytes immediately followed by an EOX status byte.)

New in version 1.2: mido.Message.from_hex()


To parse a single message you can use the class methods mido.Message.from_bytes() and mido.Message.from_hex()

Mido comes with a parser that turns MIDI bytes into messages. You can create a parser object or call one of the utility functions:

>>> mido.parse([0x92, 0x10, 0x20])
Message('note_on', channel=2, note=16, velocity=32, time=0)

>>> mido.parse_all([0x92, 0x10, 0x20, 0x82, 0x10, 0x20])
[Message('note_on', channel=2, note=16, velocity=32, time=0),
 Message('note_off', channel=2, note=16, velocity=32, time=0)]

These functions are just shortcuts for the full Parser class. This is the same parser as used inside input ports to parse incoming messages. Here are a few examples of how it can be used:

>>> p = mido.Parser()
>>> p.feed([0x90, 0x10, 0x20])
>>> p.pending()
>>> p.get_message()
Message('note_on', channel=0, note=16, velocity=32, time=0)

>>> p.feed_byte(0x90)
>>> p.feed_byte(0x10)
>>> p.feed_byte(0x20)
>>> p.feed([0x80, 0x10, 0x20])
>>> p.pending()
>>> p.get_message()
Message('note_on', channel=0, note=16, velocity=32, time=0)
>>> p.get_message()
Message('note_off', channel=0, note=16, velocity=32, time=0)

feed() accepts any iterable that generates integers in 0..255. The parser will skip and stray status bytes or data bytes, so you can safely feed it random data and see what comes out the other end.

get_message() will return None if there are no messages ready to be gotten.

You can also fetch parsed messages out of the parser by iterating over it:

>>> p.feed([0x92, 0x10, 0x20, 0x82, 0x10, 0x20])
>>> for message in p:
...    print(message)
note_on channel=2 note=16 velocity=32 time=0
note_off channel=2 note=16 velocity=32 time=0

The messages are available in p.messages (a collections.deque).


String Encoding

Mido messages can be serialized to a text format, which can be used to safely store messages in text files, send them across sockets or embed them in JSON, among other things.

To encode a message, simply call str() on it:

>>> cc = control_change(channel=9, control=1, value=122, time=60)
>>> str(cc)
'control_change channel=9 control=1 value=122 time=60'

To convert the other way (new method in 1.2):

>>> mido.Message.from_str('control_change control=1 value=122')
Message('control_change', channel=0, control=1, value=122, time=0)

Alternatively, you can call the format_as_string function directly:

>>> mido.format_as_string(cc)
'control_change channel=9 control=1 value=122 time=60'

If you don’t need the time attribute or you want to store it elsewhere, you can pass include_time=False:

>>> mido.format_as_string(cc)
'control_change channel=9 control=1 value=122'

(This option is also available in mido.Message.from_str().)


The format is simple:


These are the same as the arguments to mido.Message(). The order of parameters doesn’t matter but each one can only appear once.

Only these characters will ever occur in a string encoded Mido message:

[a-z][0-9][ =_.+()]

or written out:

'abcdefghijklmnopqrstuvwxyz0123456789 =_.+()'

This means the message can be embedded in most text formats without any form of escaping.


To parse a message, you can use mido.parse_string():

>>> parse_string('control_change control=1 value=122 time=0.5')
Message('control_change', channel=0, control=1, value=122, time=0.5)

Parameters that are left out are set to their default values. ValueError is raised if the message could not be parsed. Extra whitespace is ignored:

>>> parse_string('  control_change   control=1  value=122')
Message('control_change', channel=0, control=1, value=122, time=0)

To parse messages from a stream, you can use mido.messages.parse_string_stream():

for (message, error) in parse_string_stream(open('some_music.text')):
    if error:

This will return every valid message in the stream. If a message could not be parsed, message will be None and error will be an error message describing what went wrong, as well as the line number where the error occurred.

The argument to parse_string_stream() can be any object that generates strings when iterated over, such as a file or a list.

parse_string_stream() will ignore blank lines and comments (which start with a # and go to the end of the line). An example of valid input:

# A very short song with an embedded sysex message.
note_on channel=9 note=60 velocity=120 time=0
# Send some data

sysex data=(1,2,3) time=0.5

pitchwheel pitch=4000  # bend the not a little time=0.7
note_off channel=9 note=60 velocity=60 time=1.0


An example of messages embedded into JSON:

    "messages": [
        "0.0 note_on channel=9 note=60 velocity=120",
        "0.5 sysex data=(1,2,3)",