File: midi.rst

package info (click to toggle)
python-mido 1.3.3-0.3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 920 kB
  • sloc: python: 4,006; makefile: 127; sh: 4
file content (247 lines) | stat: -rw-r--r-- 8,048 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
.. SPDX-FileCopyrightText: 2013 Ole Martin Bjorndalen <ombdalen@gmail.com>
..
.. SPDX-License-Identifier: CC-BY-4.0

Standard MIDI Files
===================

``MidiFile`` objects can be used to *read*, *write* and *play back* MIDI files.


Opening
-------

You can open a file with::

    from mido import MidiFile

    mid = MidiFile('song.mid')

.. note::

    :term:`SysEx` dumps such as patch data are often stored in ``SYX``
    files rather than MIDI files. If you get "MThd not found. Probably not a
    MIDI file" try ``mido.read_syx_file()``.
    (See :doc:`syx` for more.)

The ``tracks`` attribute is a list of tracks. Each track is a list of
messages and meta messages, with the ``time`` attribute of each
messages set to its delta time (in ticks). (See Tempo and Beat
Resolution below for more on delta times.)

To print out all messages in the file, you can do::

    for i, track in enumerate(mid.tracks):
        print('Track {}: {}'.format(i, track.name))
        for msg in track:
            print(msg)

The entire file is read into memory. Thus you can freely modify tracks
and messages and save the file back by calling the ``save()``
method. (More on this below.)


Iterating Over Messages
-----------------------

Iterating over a ``MidiFile`` object will generate all MIDI messages
in the file in playback order. The ``time`` attribute of each message
is the number of seconds since the last message or the start of the
file.

Meta messages will also be included. If you want to filter them out,
you can do::

    if msg.is_meta:
        ...

This makes it easy to play back a MIDI file on a port (though this simple
implementation is subject to time drift)::

    for msg in MidiFile('song.mid'):
        time.sleep(msg.time)
        if not msg.is_meta:
            port.send(msg)

This is so useful that there's a method for it::

    for msg in MidiFile('song.mid').play():
        port.send(msg)

This does the sleeping and filtering for you (while avoiding drift). If you
pass ``meta_messages=True`` you will also get meta messages. These **cannot**
be sent on ports, which is why they are ``off`` by default.



Creating a New File
-------------------

You can create a new file by calling ``MidiFile`` without the ``filename``
argument. The file can then be saved by calling the ``save()`` method::

    from mido import Message, MidiFile, MidiTrack

    mid = MidiFile()
    track = MidiTrack()
    mid.tracks.append(track)

    track.append(Message('program_change', program=12, time=0))
    track.append(Message('note_on', note=64, velocity=64, time=32))
    track.append(Message('note_off', note=64, velocity=127, time=32))

    mid.save('new_song.mid')

The ``MidiTrack`` class is a subclass of list, so you can use all the
usual methods.

All messages must be tagged with delta time (in ticks). (A delta time
is how long to wait before the next message.)

If there is no ``end_of_track`` message at the end of a track, one will
be written anyway.

A complete example can be found in ``examples/midifiles/``.

The ``save`` method takes either a filename (``str``) or, using the ``file``
keyword parameter, a file-like object such as an in-memory binary file (an
``io.BytesIO``). If you pass a file object, ``save`` does not close it.
Similarly, the ``MidiFile`` constructor can take either a filename, or
a file object by using the ``file`` keyword parameter. if you pass a file
object to ``MidiFile`` as a context manager, the file is not closed when
the context manager exits.
Examples can be found in ``test_midifiles2.py``.


File Types
----------

There are three types of MIDI files:

* type 0 (single track): all messages are saved in one track
* type 1 (synchronous): all tracks start at the same time
* type 2 (asynchronous): each track is independent of the others

When creating a new file, you can select type by passing the ``type``
keyword argument or by setting the ``type`` attribute::

   mid = MidiFile(type=2)
   mid.type = 1

Type 0 files must have exactly one track. A ``ValueError`` is raised
if you attempt to save a file with no tracks or with more than one
track.


Playback Length
---------------

You can get the total playback time in seconds by accessing the
``length`` property::

   mid.length

This is only supported for type 0 and 1 files. Accessing ``length`` on
a type 2 file will raise ``ValueError``, since it is impossible to
compute the playback time of an asynchronous file.


Meta Messages
-------------

Meta messages behave like normal messages and can be created in the
usual way, for example::

    >>> from mido import MetaMessage
    >>> MetaMessage('key_signature', key='C#', mode='major')
    MetaMessage('key_signature', key='C#', mode='major', time=0)

You can tell meta messages apart from normal messages with::

    if msg.is_meta:
        ...

or if you know the message type you can use the ``type`` attribute::

    if msg.type == 'key_signature':
        ...
    elif msg.type == 'note_on':
        ...

Meta messages **cannot** be sent on ports.

For a list of supported meta messages and their attributes, and also
how to implement new meta messages, see :doc:`../meta_message_types`.


About the Time Attribute
------------------------

The ``time`` attribute is used in several different ways:

* inside a track, it is delta time in ticks. This must be an integer.

* in messages yielded from ``play()``, it is delta time in seconds
  (time elapsed since the last yielded message)

* (only important to implementers) inside certain methods it is
  used for absolute time in ticks or seconds

.. todo: Review implementation to separate concerns and units into dedicated
         attributes.


Tempo and Time Resolution
-------------------------

.. image:: ../images/midi_time.svg

Timing in MIDI files is centered around ticks. Each message in a MIDI file has
a delta time, which tells how many ticks have passed since the last message.

A tick is the smallest unit of time in MIDI and remains fixed throughout the
song. Each quarter notes is divided into a certain number of ticks, often
referred as the resolution of the file or pulses per quarter note (PPQN). This
resolution is stored as ``ticks_per_beat`` in MidiFile objects.

The meaning of this ``ticks_per_beat`` in terms of absolute timing depends on
the tempo and time signature of the file.


MIDI Tempo vs. BPM
^^^^^^^^^^^^^^^^^^

Unlike music, tempo in MIDI is not given as beats per minute (BPM), but rather
in microseconds per quarter note, with a default tempo of 500000 microseconds
per quarter note. Given a default 4/4 time signature where a beat is exactly a
quarter note, this corresponds to 120 beats per minute.

In case of different time signatures, the length of a beat depends on the
denominator of the time signature. E.g. in 2/2 time signature a beat has a
length of a half note, i.e. two quarter notes. Thus the default MIDI tempo of
500000 corresponds to a beat length of 1 second which is 60 BPM.

The meta messages 'set_tempo' and 'time_signature' can be used to change
the tempo and time signature during a song, respectively.

You can use :py:func:`bpm2tempo` and :py:func:`tempo2bpm` to convert to and
from beats per minute. Note that :py:func:`tempo2bpm` may return a floating
point number.


Converting Between Ticks and Seconds
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To convert from MIDI time to absolute time in seconds, the tempo (either
in number of beats per minute (BPM) or microseconds per quarter note, see
`MIDI Tempo vs. BPM`_ above) and ticks per per quarter note have to be decided
upon.

You can use :py:func:`tick2second` and :py:func:`second2tick` to convert to
and from seconds and ticks. Note that integer rounding of the result might be
necessary because MIDI files require ticks to be integers.

If you have a lot of rounding errors you should increase the time resolution
with more ticks per quarter note, by setting MidiFile.ticks_per_beat to a
large number. Typical values range from 96 to 480 but some use even more ticks
per quarter note.