MIDIUtil¶
Creating the MIDIFile Object¶
The first step in using the library is creating a MIDIFile
object.
There are only a few parameters that need be specified, but they affect
the functioning of the library, so it’s good to understand what they do.
The signature of of the MIDIFile
__init__()
function is
as follows:
def __init__(self, numTracks=1, removeDuplicates=True,
deinterleave=True, adjust_origin=None, file_format=1):
where the parameters do the following:
numTracks¶
numTracks
specifies the number of tracks the MIDI file should have.
It should be set to at least 1 (after all, a MIDI file without tracks isn’t
very useful), but it may be set higher for a multi-track file.
This parameter defaults to 1
.
removeDuplicates¶
If set to True
(the default), duplicate notes will be removed from
the file. This is done on a track-by-track basis.
Notes are considered duplicates if they occur at the same time, and have
equivalent pitch, and MIDI channel. If set to False
no attempt is made
to remove notes which appear to be duplicates.
removeDuplicates()
also attempts to remove other kinds of duplicates. For
example, if there are two tempo events at the same time and same tempo, they
are considered duplicates.
Of course, it’s best not to insert duplicate events in the first place, but this could be unavoidable in some instances – for example, if the software is used in the creation of Generative Music using an algorithm that can create duplication of events.
deinterleave¶
If deinterleave
is set to True
(the default), an attempt will be made
to remove interleaved notes.
To understand what an interleaved note is, it is useful to have some understanding of the MIDI standard.
To make this library more human-centric, one of the fundamental concepts used is that of the note. But the MIDI standard doesn’t have notes; instead, it has note on and note off events. These are correlated by channel and pitch.
So if, for example, you create two notes of duration 1 and separated by 1/2 of a beat, ie:
time = 0
duration = 1
MyMIDI.addNote(track,channel,pitch,time,duration,volume)
time = 0.5
MyMIDI.addNote(track,channel,pitch,time,duration,volume)
you end up with a note on event at 0, another note on event a 0.5, and two note off events, one at 1.0 and one at 1.5. So when the first note off event is processed it raises the question: which note on event does it correspond to? The channel and pitch are the same, so there is some ambiguity in the way that a hardware or software instrument will respond.
if deinterleave
is True
the library tries to disambiguate the situation
by shifting the first note’s off event to be immediately before the second
note’s on event. Thus in the example above the first note on would be at 0,
the first note off would be at 0.5, the second note on would also be at
0.5 (but would be processed after the note off at that time), and the last
note off would be at 1.5.
If this parameter is set to False
no events will be shifted.
adjust_origin¶
If adjust_origin
is True
the library will find the earliest
event in all the tracks and shift all events so that that time is t=0.
If it is False
no time-shifting will occur.
If it is left at it’s default value, None
, adjust_origin
will be
set to True
and a FutureWarning
will be displayed. This is because in
the next release the default behavior will change and no adjustment will be
performed by default.
file_format¶
This specifies the format of the file to be written. Both format 1 (the default) and format 2 files are supported.
In the format 1 file there is a separate “tempo” track to which tempo and
time signature events are written. The calls to create these events –
addTemo()
and addTimeSignature()
accept a track parameter, but in
a format 1 file these are ignored. In format 2 files they are interpreted
literally (and zero-origined, so that a two track file has indices 0
and
1
).
Track indexing is always zero-based, but with the format 1 file the tempo track is not indexed. Thus if you create a one track file:
MyMIDI = MIDIFile(1, file_format=1)
you would only have 0
as a valid index; the tempo track is managed independently
for you. Thus:
track = 0
big_track = 1000
MyMIDI.addTempo(big_track, 0, 120)
MyMIDI.addNote(track, 0, 69, 0, 1, 100)
works, even though “track 0” is really the second track in the file, and there is no track 1000.
Common Events and Function¶
This page lists some of the more common things that a user is likely to do with the MIDI file. It is not exhaustive; see the class reference for a more complete list of public functions.
Adding Notes¶
As the MIDI standard is all about music, creating notes will probably be
the lion’s share of what you’re doing. This is done with the addNote()
function.
-
MIDIFile.
addNote
(track, channel, pitch, time, duration, volume, annotation=None) Add notes to the MIDIFile object
Parameters: - track – The track to which the note is added.
- channel – the MIDI channel to assign to the note. [Integer, 0-15]
- pitch – the MIDI pitch number [Integer, 0-127].
- time – the time (in beats) at which the note sounds [Float].
- duration – the duration of the note (in beats) [Float].
- volume – the volume (velocity) of the note. [Integer, 0-127].
- annotation – Arbitrary data to attach to the note.
The
annotation
parameter attaches arbitrary data to the note. This is not used in the code, but can be useful anyway. As an example, I have created a project that uses MIDIFile to write csound orchestra files directly from the classEventList
.
As an example, the following code-fragment adds two notes to an (already created) MIDIFile object:
track = 0 # Track numbers are zero-origined
channel = 0 # MIDI channel number
pitch = 60 # MIDI note number
time = 0 # In beats
duration = 1 # In beats
volume = 100 # 0-127, 127 being full volume
MyMIDI.addNote(track,channel,pitch,time,duration,volume)
time = 1
pitch = 61
MyMIDI.addNote(track,channel,pitch,time,duration,volume)
Add a Tempo¶
Every track can have tempos specified (the unit of which is beats per minute).
-
MIDIFile.
addTempo
(track, time, tempo) Add notes to the MIDIFile object
Parameters: - track – The track to which the tempo event is added. Note that in a format 1 file this parameter is ignored and the temp is written to the tempo track
- time – The time (in beats) at which tempo event is placed
- tempo – The tempo, in Beats per Minute. [Integer]
Example:
track = 0
time = 0 # beats, beginning of track
tempo = 120 # BPM
MyMIDI.addTempo(track, time, tempo)
Assign a Name to a Track¶
-
MIDIFile.
addTrackName
(track, time, trackName) Name a track.
Parameters: - track – The track to which the name is assigned.
- time – The time (in beats) at which the track name event is placed. In general this should probably be time 0 (the beginning of the track).
- trackName – The name to assign to the track [String]
In general, the time should probably be t=0
Example:
track = 0
time = 0
track_name = "Bassline 1"
MyMIDI.addTrackName(track, time, track_name)
Adding a Program Change Event¶
The program change event tells the the instrument what voice a certain track should sound. As an example, if the instrument you’re using supports General MIDI, you can use the GM numbers to specify the instrument.
Important Note: Within this library program numbers are zero-origined (as they are on a byte-level within the MIDI standard), but most of the documentation you will see is musician-centric, so they are usually given as one-origined. So, for example, if you want to sound a Cello, you would use a program number of 42, not the 43 which is given in the link above.
-
MIDIFile.
addProgramChange
(track, channel, time, program) Add a MIDI program change event.
Parameters: - track – The track to which program change event is added.
- channel – the MIDI channel to assign to the event. [Integer, 0-15]
- time – The time (in beats) at which the program change event is placed [Float].
- program – the program number. [Integer, 0-127].
Example:
track = 0
channel = 0
time = 8 # Eight beats into the composition
program = 42 # A Cello
MyMIDI.addProgramChange(track, channel, time, program)
Writing the File to Disk¶
Ultimately, you’ll need to write your data to disk to use it.
-
MIDIFile.
writeFile
(fileHandle) Write the MIDI File.
Parameters: fileHandle – A file handle that has been opened for binary writing.
Example:
with open("mymidifile.midi", 'wb') as output_file:
MyMIDI.writeFile(output_file)
Additional Public Function¶
The above list is not exhaustive. For example, the library includes methods to create arbitrary channel control events, SysEx and Universal SysEx events, Registered Parameter calls and Non-Registered Parameter calls, etc. Please see the Class Reference for a more complete list of public functions.
Tuning and Micro-tonalities¶
One of my interests is microtonalities/non-standard tunings, so support for such explorations has been included in the library.
There are several ways that tuning data can be specified in the MIDI standard, two of the most common being note pitch-bend and bulk tuning dumps. In this library I have implemented the real-time change note tuning of the MIDI tuning standard. I chose that as a first implementation because most of the soft-synthesizers I use support this standard.
Note, however, that implementation of the MIDI tuning standard is somewhat spotty, so you may want to verify that your hardware and/or software supports it before you spend too much time.
The main function to support a tuning change is changeNoteTuning
.
-
MIDIFile.
changeNoteTuning
(track, tunings, sysExChannel=127, realTime=True, tuningProgam=0)¶ Add a real-time MIDI tuning standard update to a track.
Parameters: - track – The track to which the tuning is applied.
- tunings – A list to tuples representing the tuning. See below for an explanation.
- sysExChannel – The SysEx channel of the event. This is mapped to “manufacturer ID” in the event which is written. Unless there is a specific reason for changing it, it should be left at its default value.
- realTime – Speicifes if the Universal SysEx event should be flagged as real-time or
non-real-time. As with the
sysExChannel
argument, this should in general be left at it’s default value. - tuningProgram – The tuning program number.
This function specifically implements the “real time single note tuning change” (although the name is misleading, as multiple notes can be included in each event). It should be noted that not all hardware or software implements the MIDI tuning standard, and that which does often does not implement it in its entirety.
The
tunings
argument is a list of tuples, in (note number, frequency) format. As an example, if one wanted to change the frequency on MIDI note 69 to 500 (it is normally 440 Hz), one could do it thus:from midiutil.MidiFile import MIDIFile MyMIDI = MIDIFile(1) tuning = [(69, 500)] MyMIDI.changeNoteTuning(0, tuning, tuningProgam=0)
Tuning Program¶
With some instruments, such as timidity, this
is all you need to do: timidity will apply the tuning change to the notes.
Other instruments, such as fluidsynth, require
that the tuning program be explicitly assigned. This is done with the
changeTuningProgram
function:
-
MIDIFile.
changeTuningProgram
(track, channel, time, program, time_order=False)¶ Change the tuning program for a selected track
Parameters: - track – The track to which the data should be written
- channel – The channel for the event
- time – The time of the event
- program – The tuning program number (0-127)
- time_order – Preserve the ordering of the component events by ordering
in time. See
makeRPNCall()
for a discussion of when this may be necessary
Note that this is a convenience function, as the same functionality is available from directly sequencing controller events.
The specified tuning should already have been written to the stream with
changeNoteTuning
.
Tuning Bank¶
The tuning bank can also be specified (fluidsynth assumes that any tuning
you transmit via changeNoteTuning
is assigned to bank zero):
-
MIDIFile.
changeTuningBank
(track, channel, time, bank, time_order=False)¶ Change the tuning bank for a selected track
Parameters: - track – The track to which the data should be written
- channel – The channel for the event
- time – The time of the event
- bank – The tuning bank (0-127)
- time_order – Preserve the ordering of the component events by ordering
in time. See
makeRPNCall()
for a discussion of when this may be necessary
Note that this is a convenience function, as the same functionality is available from directly sequencing controller events.
The specified tuning should already have been written to the stream with
changeNoteTuning
.
An Example¶
So, as a complete example, the following code fragment would get rid of that pesky 440 Hz A and tell the instrument to use the tuning that you just transmitted:
track = 0
channel = 0
tuning = [(69, 500)]
program = 0
bank = 0
time = 0
MyMIDI.changeNoteTuning(track, tuning, tuningProgam=program)
MyMIDI.changeTuningBank(track, channel, time, bank) # may or may not be needed
MyMIDI.changeTuningProgram(track, channel, time, program) # ditto
To Do¶
- Implement the tuning change with bank select event type.
Extending the Library¶
The choice of MIDI event types included in the library is somewhat idiosyncratic; I included the events I needed for another software project I was wrote. You may find that you need additional events in your work. For this reason I am including some instructions on extending the library. The process isn’t too hard (provided you have a working knowledge of Python and the MIDI standard), so the task shouldn’t present a competent coder too much difficulty. Alternately (if, for example, you don’t have a working knowledge of MIDI and don’t desire to gain it), you can submit new feature requests to me, and I will include them into the development branch of the code, subject to the constraints of time.
To illustrate the process I show below how the MIDI tempo event is incorporated into the code. This is a relatively simple event, so while it may not illustrate some of the subtleties of MIDI programing, it provides a good, illustrative case.
Create a New Event Type¶
The first order of business is to create a new subclass of the GnericEvent object of the MIDIFile module. This subclass initializes any specific instance data that is needed for the MIDI event to be written. In the case of the tempo event, it is the actual tempo (which is defined in the MIDI standard to be 60000000 divided by the tempo in beats per minute). This class should also call the superclass’ initializer with the event time, ordinal, and insertion order, and set the event type (a unique string used internally by the software). In the case of the tempo event:
class Tempo(GenericEvent):
'''A class that encapsulates a tempo meta-event
'''
def __init__(self,time,tempo, ordinal=3, insertion_order=0):
self.tempo = int(60000000 / tempo)
super(Tempo, self).__init__('tempo', time, ordinal, insertion_order)
Any class that you define should include a type, time, ordinal (see below), and an insertion order.
self.ord
and self.insertion_order
are used to order the events
in the MIDI stream. Events are first ordered in time. Events at the
same time are then ordered by self.ord
, with lower numbers appearing
in the stream first. The extant classes in the code all allow the user
to specify an ordinal for the object, but they include default values
that are meant to be reasonable.
Lastly events are sorted on the self.insertion_order
member. This
makes it possible to, say, create a Registered Parameter Number call
from a collection of Control Change events. Since all the CC events will
have the same time and class (and therefore default ordinal), you can control
the order of the events by the order to such you add them to the MIDIFile.
Next, if you want the code to be able to de-duplicate events which may
lay over top of one another, the parent class, GenericEvent
, has a
member function called __eq__()
. If two events do not coincide in
time or type they are not equal, but it they do the __eq__
function
must be modified to show equality. In the case of the Tempo
class,
two tempo events are considered equivalent if they are the same tempo.
In other words, if there are two tempo events at the same time and
the same tempo, one will be removed in the de-duplication process
(which is the default behavious for MIDIFile
, but it can be
turned off). From GenericEvent.__eq__()
:
if self.type == 'tempo':
if self.tempo != other.tempo:
return False
If events are not equivalent, the code should return False
. If they are, the
code can be allowed to fall through to its default return of True
.
Create an Accessor Function¶
Next, an accessor function should be added to MIDITrack to create an event of this type. Continuing the example of the tempo event:
def addTempo(self,time,tempo, insertion_order=0):
'''
Add a tempo change (or set) event.
'''
self.eventList.append(Tempo(time,tempo, insertion_order = insertion_order))
(Most/many MIDI events require a channel specification, but the tempo event does not.)
The public accessor function is via the MIDIFile object, and must include
the track number to which the event is written. So in MIDIFile
:
def addTempo(self,track, time,tempo):
self.tracks[track].addTempo(time,tempo, insertion_order = self.event_counter)
self.event_counter = self.event_counter + 1
Note that a track has been added (which is zero-origined and needs to be
constrained by the number of tracks that the MIDIFile
was created with),
and insertion_order
is taken from the class event_counter
data member. This should be followed in each function you add.
This is the function you will use in your code to create an event of the desired type.
Modify processEventList()¶
Next, the logic pertaining to the new event type should be added to
processEventList()
function of the MIDITrack
class. In general this code
will create a MIDIEvent object and set its type, time, ordinality, and
any specific information that is needed for the event type. This object
is then added to the MIDIEventList.
The relevant section for the tempo event is:
elif thing.type == 'tempo':
event = MIDIEvent("Tempo", thing.time * TICKSPERBEAT, thing.ord, thing.insertion_order)
event.tempo = thing.tempo
self.MIDIEventList.append(event)
THe MIDIEvent
class is expected to have a type
, time
(which should be converted from beats to ticks as above), ordinal, and an
insertion order, which are similar to the values in the GenericEvent
class.
You are free, of course, to add any other data items that need to be specified.
In the case of Tempo
this is the tempo to be written.
Write the Event Data to the MIDI Stream¶
The last step is to modify the MIDIFile.writeEventsToStream()
function;
here is where some understanding of the MIDI standard is necessary. The
following code shows the creation of a MIDI tempo event:
elif event.type == "Tempo":
code = 0xFF
subcode = 0x51
fourbite = struct.pack('>L', event.tempo)
threebite = fourbite[1:4] # Just discard the MSB
varTime = writeVarLength(event.time)
for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte)
self.MIDIdata = self.MIDIdata + struct.pack('>B',code)
self.MIDIdata = self.MIDIdata + struct.pack('>B',subcode)
self.MIDIdata = self.MIDIdata + struct.pack('>B', 0x03)
self.MIDIdata = self.MIDIdata + threebite
The event.type string (“Tempo”) was the one chosen in the processEventList logic.
The code and sub-code are binary values that come from the MIDI specification.
Next the data is packed into a three byte structure (or a four byte structure, discarding the most significant byte). Again, the MIDI specification determines the number of bytes used in the data payload.
All MIDI events begin with a time, which is stored in a slightly bizarre
variable-length format. This time should be converted to MIDI variable-length
data with the writeVarLength()
function before writing to the stream.
In the MIDI standard’s variable length data only seven bits of a word are
used to store data; the eighth bit signifies if more bytes encoding the
value follow. The total length may be 1 to 3 bytes, depending upon the size of
the value encoded. The writeVarLength()
function takes care of this
conversion for you.
Now the data is written to the binary object self.MIDIdata
, which is
the actual MIDI-encoded data stream. As per the MIDI standard, first we
write our variable-length time value. Next we add the event type code and
sub-code. Then we write the length of the data payload, which in the case
of the tempo event is three bytes. Lastly, we write the actual payload,
which has been packed into the variable threebite
.
The reason that there are separate classes for GenericEvent
and MIDIEvent
is that there need not be a one-to-one correspondance. For example, the
code defines a Note
object, but when this is processed in
processEventList()
two MIDIEvent
objects are created, one for
the note on
event, one for the note off
event.
if thing.type == 'note':
event = MIDIEvent("NoteOn", thing.time * TICKSPERBEAT,
thing.ord, thing.insertion_order)
event.pitch = thing.pitch
event.volume = thing.volume
event.channel = thing.channel
self.MIDIEventList.append(event)
event = MIDIEvent("NoteOff", (thing.time+ thing.duration) * TICKSPERBEAT,
thing.ord -0.1,
thing.insertion_order)
event.pitch = thing.pitch
event.volume = thing.volume
event.channel = thing.channel
self.MIDIEventList.append(event)
Note that the NoteOff
event is created with a slightly lower ordinality
than the NoteOn
event. This is so that at any given time the note off
events will be processed before the note on events.
Write Some Tests¶
Yea, it’s a hassle, but you know it’s the right thing to do!
Class Reference¶
-
class
midiutil.MidiFile.
MIDIFile
(numTracks=1, removeDuplicates=True, deinterleave=True, adjust_origin=None, file_format=1)¶ A class that encapsulates a full, well-formed MIDI file object.
This is a container object that contains a header (
MIDIHeader
), one or more tracks (class:MIDITrack), and the data associated with a proper and well-formed MIDI file.-
__init__
(numTracks=1, removeDuplicates=True, deinterleave=True, adjust_origin=None, file_format=1)¶ Initialize the MIDIFile class
Parameters: - numTracks – The number of tracks the file contains. Integer, one or greater
- removeDuplicates – If set to
True
remove duplicate events before writing to disk - deinterleave – If set to
True
deinterleave the notes in the stream - adjust_origin – If set to
True
(of left at the default ofNone
) shift all the events in the tracks so that the first event takes place at time t=0 - file_format – The format of the multi-track file. This should either be
1
(the default, and the most widely supported format) or2
.
Note that the default for
adjust_origin
will change in a future release, so one should probably explicitly set it.Example:
# Create a two-track MIDIFile from midiutil.MidiFile import MIDIFile midi_file = MIDIFile(2)
In previous versions of this code the file written was format 2 (which can be thought of as a collection of independent tracks) but was identified as format 1. In this version one can specify either format 1 or 2.
In format 1 files there is a separate tempo track which contains tempo and time signature data, but contains no note data. If one creates a single track format 1 file the actual file has two tracks – one for tempo data and one for note data. In the track indexing the tempo track can be ignored. In other words track 0 is the note track (the second track in the file). However, tempo and time signature data will be written to the first, tempo track. This is done to try and preserve as much interoperability with previous versions as possible.
In a format 2 file all tracks are indexed and the track parameter is interpreted literally.
-
addControllerEvent
(track, channel, time, controller_number, parameter)¶ Add a channel control event
Parameters: - track – The track to which the event is added.
- channel – the MIDI channel to assign to the event. [Integer, 0-15]
- time – The time (in beats) at which the event is placed [Float].
- controller_number – The controller ID of the event.
- parameter – The event’s parameter, the meaning of which varies by event type.
-
addCopyright
(track, time, notice)¶ Add a copyright notice to the MIDIFile object
Parameters: - track – The track to which the notice is added.
- time – The time (in beats) at which notice event is placed. In general this sould be time t=0
- notice – The copyright notice [String]
-
addKeySignature
(track, time, accidentals, accidental_type, mode, insertion_order=0)¶ Add a Key Signature to a track
Parameters: - track – The track to which this should be added
- time – The time at which the signature should be placed
- accidentals – The number of accidentals in the key signature
- accidental_type – The type of accidental
- mode – The mode of the scale
The easiest way to use this function is to make sure that the symbolic constants for accidental_type and mode are imported. By doing this:
from midiutil.MidiFile import *
one gets the following constants defined:
SHARPS
FLATS
MAJOR
MINOR
So, for example, if one wanted to create a key signature for a minor scale with three sharps:
MyMIDI.addKeySignature(0, 0, 3, SHARPS, MINOR)
-
addNote
(track, channel, pitch, time, duration, volume, annotation=None)¶ Add notes to the MIDIFile object
Parameters: - track – The track to which the note is added.
- channel – the MIDI channel to assign to the note. [Integer, 0-15]
- pitch – the MIDI pitch number [Integer, 0-127].
- time – the time (in beats) at which the note sounds [Float].
- duration – the duration of the note (in beats) [Float].
- volume – the volume (velocity) of the note. [Integer, 0-127].
- annotation – Arbitrary data to attach to the note.
The
annotation
parameter attaches arbitrary data to the note. This is not used in the code, but can be useful anyway. As an example, I have created a project that uses MIDIFile to write csound orchestra files directly from the classEventList
.
-
addProgramChange
(track, channel, time, program)¶ Add a MIDI program change event.
Parameters: - track – The track to which program change event is added.
- channel – the MIDI channel to assign to the event. [Integer, 0-15]
- time – The time (in beats) at which the program change event is placed [Float].
- program – the program number. [Integer, 0-127].
-
addSysEx
(track, time, manID, payload)¶ Add a System Exclusive event.
Parameters: - track – The track to which the event should be written
- time – The time of the event.
- manID – The manufacturer ID for the event
- payload – The payload for the event. This should be a binary-packed value, and will vary for each type and function.
Note: This is a low-level MIDI function, so care must be used in constructing the payload. It is recommended that higher-level helper functions be written to wrap this function and construct the payload if a developer finds him or herself using the function heavily.
-
addTempo
(track, time, tempo)¶ Add notes to the MIDIFile object
Parameters: - track – The track to which the tempo event is added. Note that in a format 1 file this parameter is ignored and the temp is written to the tempo track
- time – The time (in beats) at which tempo event is placed
- tempo – The tempo, in Beats per Minute. [Integer]
-
addText
(track, time, text)¶ Add a text event
Parameters: - track – The track to which the notice is added.
- time – The time (in beats) at which text event is placed.
- text – The text to adde [SASCII tring]
-
addTimeSignature
(track, time, numerator, denominator, clocks_per_tick, notes_per_quarter=8)¶ Add a time signature event.
Parameters: - track – The track to which the signature is assigned. Note that in a format 1 file this parameter is ignored and the event is written to the tempo track
- time – The time (in beats) at which the event is placed. In general this should probably be time 0 (the beginning of the track).
- numerator – The numerator of the time signature. [Int]
- denominator – The denominator of the time signature, expressed as a power of two (see below). [Int]
- clocks_per_tick – The number of MIDI clock ticks per metronome click (see below).
- notes_per_quarter – The number of annotated 32nd notes in a MIDI quarter note. This is almost always 8 (the default), but some sequencers allow this value to be changed. Unless you know that your sequencing software supports it, this should be left at its default value.
The data format for this event is a little obscure.
The
denominator
should be specified as a power of 2, with a half note being one, a quarter note being two, and eight note being three, etc. Thus, for example, a 4/4 time signature would have anumerator
of 4 and adenominator
of 2. A 7/8 time signature would be anumerator
of 7 and adenominator
of 3.The
clocks_per_tick
argument specifies the number of clock ticks per metronome click. By definition there are 24 ticks in a quarter note, so a metronome click per quarter note would be 24. A click every third eighth note would be 3 * 12 = 36.
-
addTrackName
(track, time, trackName)¶ Name a track.
Parameters: - track – The track to which the name is assigned.
- time – The time (in beats) at which the track name event is placed. In general this should probably be time 0 (the beginning of the track).
- trackName – The name to assign to the track [String]
-
addUniversalSysEx
(track, time, code, subcode, payload, sysExChannel=127, realTime=False)¶ Add a Univeral System Exclusive event.
Parameters: - track – The track to which the event should be written
- time – The time of the event, in beats.
- code – The event code. [Integer]
- subcode – The event sub-code [Integer]
- payload – The payload for the event. This should be a binary-packed value, and will vary for each type and function.
- sysExChannel – The SysEx channel.
- realTime – Sets the real-time flag. Defaults to non-real-time.
- manID – The manufacturer ID for the event
Note: This is a low-level MIDI function, so care must be used in constructing the payload. It is recommended that higher-level helper functions be written to wrap this function and construct the payload if a developer finds him or herself using the function heavily. As an example of such a helper function, see the
changeNoteTuning()
function, which uses the event to create a real-time note tuning update.
-
changeNoteTuning
(track, tunings, sysExChannel=127, realTime=True, tuningProgam=0)¶ Add a real-time MIDI tuning standard update to a track.
Parameters: - track – The track to which the tuning is applied.
- tunings – A list to tuples representing the tuning. See below for an explanation.
- sysExChannel – The SysEx channel of the event. This is mapped to “manufacturer ID” in the event which is written. Unless there is a specific reason for changing it, it should be left at its default value.
- realTime – Speicifes if the Universal SysEx event should be flagged as real-time or
non-real-time. As with the
sysExChannel
argument, this should in general be left at it’s default value. - tuningProgram – The tuning program number.
This function specifically implements the “real time single note tuning change” (although the name is misleading, as multiple notes can be included in each event). It should be noted that not all hardware or software implements the MIDI tuning standard, and that which does often does not implement it in its entirety.
The
tunings
argument is a list of tuples, in (note number, frequency) format. As an example, if one wanted to change the frequency on MIDI note 69 to 500 (it is normally 440 Hz), one could do it thus:from midiutil.MidiFile import MIDIFile MyMIDI = MIDIFile(1) tuning = [(69, 500)] MyMIDI.changeNoteTuning(0, tuning, tuningProgam=0)
-
changeTuningBank
(track, channel, time, bank, time_order=False)¶ Change the tuning bank for a selected track
Parameters: - track – The track to which the data should be written
- channel – The channel for the event
- time – The time of the event
- bank – The tuning bank (0-127)
- time_order – Preserve the ordering of the component events by ordering
in time. See
makeRPNCall()
for a discussion of when this may be necessary
Note that this is a convenience function, as the same functionality is available from directly sequencing controller events.
The specified tuning should already have been written to the stream with
changeNoteTuning
.
-
changeTuningProgram
(track, channel, time, program, time_order=False)¶ Change the tuning program for a selected track
Parameters: - track – The track to which the data should be written
- channel – The channel for the event
- time – The time of the event
- program – The tuning program number (0-127)
- time_order – Preserve the ordering of the component events by ordering
in time. See
makeRPNCall()
for a discussion of when this may be necessary
Note that this is a convenience function, as the same functionality is available from directly sequencing controller events.
The specified tuning should already have been written to the stream with
changeNoteTuning
.
-
makeNRPNCall
(track, channel, time, controller_msb, controller_lsb, data_msb, data_lsb, time_order=False)¶ Perform a Non-Registered Parameter Number Call
Parameters: - track – The track to which this applies
- channel – The channel to which this applies
- time – The time of the event
- controller_msb – The Most significant byte of thecontroller. In common usage this will usually be 0
- controller_lsb – The least significant byte for the controller message. For example, for a fine-tunning change this would be 01.
- data_msb – The most significant byte of the controller’s parameter.
- data_lsb – The least significant byte of the controller’s parameter. If none is needed this
should be set to
None
- time_order – Order the control events in time (see below)
The
time_order
parameter is something of a work-around for sequencers that do not preserve the order of events from the MIDI files they import. Within this code care is taken to preserve the order of events as specified, but some sequencers seem to transmit events occurring at the same time in an arbitrary order. By setting this parameter toTrue
something of a work-around is performed: each successive event (of which there are three or four for this event type) is placed in the time stream a small delta from the preceding one. Thus, for example, the controllers are set before the data bytes in this call.
-
makeRPNCall
(track, channel, time, controller_msb, controller_lsb, data_msb, data_lsb, time_order=False)¶ Perform a Registered Parameter Number Call
Parameters: - track – The track to which this applies
- channel – The channel to which this applies
- time – The time of the event
- controller_msb – The Most significant byte of the controller. In common usage this will usually be 0
- controller_lsb – The Least significant Byte for the controller message. For example, for a fine-tuning change this would be 01.
- data_msb – The Most Significant Byte of the controller’s parameter.
- data_lsb – The Least Significant Byte of the controller’s parameter. If non needed this
should be set to
None
- time_order – Order the control events in time (see below)
As an example, if one were to change a channel’s tuning program:
makeRPNCall(track, channel, time, 0, 3, 0, program)
(Note, however, that there is a convenience function,
changeTuningProgram
, that does this for you.)The
time_order
parameter is something of a work-around for sequencers that do not preserve the order of events from the MIDI files they import. Within this code care is taken to preserve the order of events as specified, but some sequencers seem to transmit events occurring at the same time in an arbitrary order. By setting this parameter toTrue
something of a work-around is performed: each successive event (of which there are three or four for this event type) is placed in the time stream a small delta from the preceding one. Thus, for example, the controllers are set before the data bytes in this call.
-
writeFile
(fileHandle)¶ Write the MIDI File.
Parameters: fileHandle – A file handle that has been opened for binary writing.
-
Introduction¶
MIDIUtil is a pure Python library that allows one to write multi-track Musical Instrument Digital Interface (MIDI) files from within Python programs (both format 1 and format 2 files are now supported). It is object-oriented and allows one to create and write these files with a minimum of fuss.
MIDIUtil isn’t a full implementation of the MIDI specification. The actual specification is a large, sprawling document which has organically grown over the course of decades. I have selectively implemented some of the more useful and common aspects of the specification. The choices have been somewhat idiosyncratic; I largely implemented what I needed. When I decided that it could be of use to other people I fleshed it out a bit, but there are still things missing. Regardless, the code is fairly easy to understand and well structured. Additions can be made to the library by anyone with a good working knowledge of the MIDI file format and a good, working knowledge of Python. Documentation for extending the library is provided.
This software was originally developed with Python 2.5.2 and made use of some features that were introduced in 2.5. More recently Python 2 and 3 support has been unified, so the code should work in both environments. However, support for versions of Python previous to 2.6 has been dropped. I have verified correct functioning with Python 2.6.8, but it is possible that it will work with earlier versions of 2.6.
This software is distributed under an Open Source license and you are free to use it as you see fit, provided that attribution is maintained. See License.txt in the source distribution for details.
Installation¶
he latest, stable version of MIDIUtil is hosted at the Python Package Index and can be installed via the normal channels:
pip install MIDIUtil
Source code is available on Github , and be cloned with one of the following URLS:
git clone git@github.com:MarkCWirt/MIDIUtil.git
# or
git clone https://github.com/MarkCWirt/MIDIUtil.git
depending on if you want to use SSH or HTTPS. (The source code for stable releases can also be downloaded from the Releases page.)
To use the library one can either install it on one’s system:
python setup.py install
or point your $PYTHONPATH
environment variable to the directory
containing midiutil
(i.e., src
).
MIDIUtil is pure Python and should work on any platform to which Python has been ported.
If you’re using this software in your own projects you may want to consider distributing the library bundled with yours; the library is small and self-contained, and such bundling makes things more convenient for your users. The best way of doing this is probably to copy the midiutil directory directly to your package directory and then refer to it with a fully qualified name. This will prevent it from conflicting with any version of the software that may be installed on the target system.
Quick Start¶
Using the software is easy:
- The package must be imported into your namespace
- A MIDIFile object is created
- Events (notes, tempo-changes, etc.) are added to the object
- The MIDI file is written to disk.
Detailed documentation is provided; what follows is a simple example to get you going quickly. In this example we’ll create a one track MIDI File, assign a tempo to the track, and write a C-Major scale. Then we write it to disk.
#!/usr/bin/env python
from midiutil.MidiFile import MIDIFile
degrees = [60, 62, 64, 65, 67, 69, 71, 72] # MIDI note number
track = 0
channel = 0
time = 0 # In beats
duration = 1 # In beats
tempo = 60 # In BPM
volume = 100 # 0-127, as per the MIDI standard
MyMIDI = MIDIFile(1) # One track, defaults to format 1 (tempo track
# automatically created)
MyMIDI.addTempo(track,time, tempo)
for pitch in degrees:
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
time = time + 1
with open("major-scale.mid", "wb") as output_file:
MyMIDI.writeFile(output_file)
There are several additional event types that can be added and there are various options available for creating the MIDIFile object, but the above is sufficient to begin using the library and creating note sequences.
The above code is found in machine-readable form in the examples directory. A detailed class reference and documentation describing how to extend the library is provided in the documentation directory.
Have fun!
Thank You¶
I’d like to mention the following people who have given feedback, but fixes, and suggestions on the library:
- Bram de Jong
- Mike Reeves-McMillan
- Egg Syntax
- Nils Gey
- Francis G.