Messages
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
0
The type
attribute can be used to determine message type:
>>> msg.type
'note_on'
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)
Note
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.)
Note
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()
bytearray(b'\x90\x00@')
>>> 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)
or:
import operator
messages.sort(key=operator.attrgetter('time'))
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])
>>> msg.data += [4, 5]
>>> msg.data += [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')
bytearray(b'ABC')
b'ABC' # Python 3 only.
For example:
>>> msg = Message('sysex', data=bytearray(b'ABC'))
>>> msg.data += 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()
Note
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()
1
>>> 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()
2
>>> 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
).
Serializing
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()
.)
Format
The format is simple:
MESSAGE_TYPE [PARAMETER=VALUE ...]
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.
Parsing
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:
print(error)
else:
do_something_with(message)
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
Examples
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)",
"...",
]
}