"MIDI files are stored in a compact binary form intended to optimise speed ... While well-suited to its intended purposes, MIDI files are somewhat difficult to read and write without a special-purpose library, particularly in commonly used text processing languages such as Perl and Python which are otherwise ideally suited to the kinds of transformations one often wishes to apply to MIDI-encoded music."
This is intended to be resource guide for anyone interesting in manipulating midi files, especially with scripting languages.
Before we begin experimenting with music files, allow me to mention some basics I've had to learn about music theory, namely, let me mention the Circle of Fifths.
There are a great many kinds of scales in the world. Some examples include lydian, mixolydian, ionian, dorian, aeolian, phrygian, locrian, and of course, major and minor.
My point? Trying to support all these different scales in a computer program would be a daunting task...
But try to do it, and you'll find that many of these patterns are actually one and the same as another. For example, G mixolydian has the same pattern as D dorian, and C major has the same pattern as A minor.
These similarities can be summarized in what's called the Circle of Fifths , which is a much more concise overview of the most common scales.
Various music formats, including MIDI, will often indicate a song's "fifth" number in the header of the file.
The "Circle of Fifths":
Contrast the two files here: scales.zip
Before attempting to process data from a midi file, it can be quite useful to first convert the midi file into a more tangible format. The following describes a few such formats.
The MIDI-CSV format is a format arranged by John Walker which represents MIDI files and their events in a comma separated value format ideal for use with scripting languages. The midicsv program was written in C and can convert MIDI to and from CSV.
Example MIDI CSV snippet:
0, 0, Header, 0, 1, 480 1, 0, Start_track 1, 0, Tempo, 500000 1, 0, Key_signature, 2, "major" 1, 0, Time_signature, 9, 3, 27, 8 1, 0, Title_t, "Another Jig Will Do" 1, 0, Text_t, "Z:id:dc-slipjig-1" 1, 0, Note_on_c, 0, 69, 105 1, 239, Note_off_c, 0, 69, 0 1, 240, Note_on_c, 0, 71, 80 ... ... 1, 34560, End_track 0, 0, End_of_file
This is a MIDI XML definition promoted by Recordare which represents MIDI files and their events in a E(X)tensible (M)ark-up (L)anguage ideal for sharing midi over the web, for use as an intermediary format when converting MIDI to another format, and for web-based scripting languages such as PHP.
There is a PHP MIDI class supplied by Valentin Schmidt which can convert MIDI to MIDI XML format.
Because PHP can be used, not only as a web language, but also as a client-side scripting language, it's fairly easy to arrange a client-side version of the midi2xml webpage, for example: mid2xml-client.zip.
Example MIDI XML snippet:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE MIDIFile PUBLIC "-//Recordare//DTD MusicXML 0.9 MIDI//EN" "http://www.musicxml.org/dtds/midixml.dtd"> <MIDIFile> <Format>0</Format> <TrackCount>1</TrackCount> <TicksPerBeat>480</TicksPerBeat> <TimestampType>Delta</TimestampType> <Track Number="0"> <Event> <Delta>0</Delta> <SetTempo Value="500000"/> </Event> <Event> <Delta>0</Delta> <KeySignature Fifths="1" Mode="1"/> </Event> <Event> <Delta>0</Delta> <TimeSignature Numerator="9" LogDenominator="3" MIDIClocksPerMetronomeClick="27" ThirtySecondsPer24Clocks="8"/> </Event> <Event> <Delta>0</Delta> <TrackName>Butterfly</TrackName> </Event> <Event> <Delta>0</Delta> <TextEvent> This is a slipjig</TextEvent> </Event> <Event> <Delta>0</Delta> <NoteOn Channel="1" Note="71" Velocity="105"/> </Event> ...
There is this script: midi.py by Will Ware, which is the closest thing I've been able to find to a complete MIDI parser for Python.
I've written my own mostly complete (though not entirely complete) MIDI parser library for Python available here: midiparser.zip. The code is loosely based on midi.py and the code from John Walker's midicsv. Unlike midi.py, it is intended to have a structured class interface.
There is MIDI-Perl by Sean M. Burke, which contains documentation and tutorials of its usage. It's also available at CPAN.
Many specs for the MIDI file format can be found at the WOTSIT website. Many more complete specs include the following:
The Linux operating system has a midi player called Timidity++. In order for it to work it needs a set of software instruments. A set of instruments, and example timidity.cfg, files can be found at www.linuxpackages.net.
Timidity has it's own built-in "trace" option, which can show a graphical
representation of a midi file's events as timidity parses it. This
functionality is best represented in its
(see original screenshot).
Timidity can also be used as a software sequencer daemon in conjunction with ALSA drivers. Here is a good tutorial: ALSA Related Stuff.
In order to program with the timidity alsa interface, you'll need to get
familiar with the ALSA sequencer API. The best tutorial for this API that
I've found, is Takashi Iwai's
It's possible to extend Python so that it can access the ALSA Sequencer API.
I've arranged my own wrapper for Python, which wraps just a few of the basic ASLA Sequencer functions like turning notes on and off and changing instruments. It's available here: alsawrap.zip
Another way to output notes without a hardware sequencer is via PySonic, which is a Python wrapper to the FMOD Sound Library, a cross platform audio output API.
It's primarily distributed for Windows, but with enough fiddling I've been able to get it to run on some versions of Linux. In order to compile, you'll need Pyrex and a non-"Ex" edition of FMOD.
PyMidi is one Python library I've found for Windows.
It should also be possible to extend Python so that it can access the Windows MIDI API.
Here's one tutorial on the Windows MIDI API...
Basic MIDI Input/Output Programming
If you decide to use a non-MIDI audio output API, the forumla for converting notes to hertz is fairly simple.
Let's assume you have a sample (that is, a wav file) that represents middle-C. Normally middle-C is approximately 261.6 Hz. But since we're using a wav file, we're going to replace that with 44100 Hz (the typical sampling rate of a high quality wav file.)
let freq = frequency (in hertz) let note = the note, where 0 is middle C freq = 44100 * 2^(note/12) note = (log2(freq / 44100)) / 12
So, in Python, the syntax would be...
sample.Frequency = sample.original_frequency * \ pow(2, float(note_number + (12 * octave)) / 12)
Of course, the octave numbers may vary depending on what octave was used to record the sample for middle-C.
I've arranged a simple PySonic example that uses this formula here: audio.zip
If you're fortunate enough to own a soundcard that has a real hardware sequencer, there are plenty of good programming tutorials. For example, Craig Stuart Sapp has arranged his...
Introduction to MIDI programming in Linux.
In Windows XP, one can choose from any available so-called "wave tables" (be it hardware or software) by going to Control Panel -> Audio Tab -> Midi Music Playback section. The Windows MIDI API should in-turn use the selected wave table.
© Copyright Sean D. Spencer (Last Updated: 4/22/2007) sean DOT spencer AT att DOT net