Commits

Jacob Smullyan committed 522a834

Imported from svn by Bitbucket

  • Participants

Comments (0)

Files changed (13)

+Date:       20 October 2009
+Version:    0.87
+
+    First public release.
+
+    * Tweaked email address in contact information.
+    * Added/updated documentation.
+    * Tweaked the setup.py file to produce better distributions.
+
+Date:       9 October 2009
+Version:    0.86
+
+    * added addNote as main interface into package (not
+      addNoteByNumber). It's been a while since I've cut a release,
+      so there may be other things that have happened.
+
+    * Created distutils package.
+
+    * Minor code clean-up.
+
+    * Added documentation in-line and in text (MIDIFile.txt).
+
+    * All public functions should now be accessed thought
+      MIDIFile directly, and not the component tracks.
+
+Date:    15 January 2009
+Version:    0.85
+
+    * Split out from existing work as a separate project.
+
+--------------------------------------------------------------------------
+MIDUTIL, Copyright (c) 2009, Mark Conway Wirt 
+                       <emergentmusics) at (gmail . com>
+
+This software is distributed under an Open Source license, the
+details of which follow.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+--------------------------------------------------------------------------
+
+README.txt
+setup.py
+License.txt
+CHANGELOG
+VERSION
+MANIFEST
+src/midiutil/MidiFile.py
+src/midiutil/MidiFile3.py
+src/midiutil/__init__.py
+examples/single-note-example.py
+documentation/Extending.txt
+documentation/ClassReference.txt
+src/unittests/miditest.py
+========
+MIDIUtil
+========
+
+------------
+Introduction
+------------
+
+MIDIUtil is a pure Python library that allows one to write muti-track
+Musical Instrument Digital Interface (MIDI) files from within Python
+programs. 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 it makes use
+of some features that were introduced in 2.5. I have used it extensively
+in Python 2.6.
+
+Included in this version is an intitial port to Python 3 (but which should
+work in 2.6.X also). The file is called MidiFile3.py. To use it, use
+the following import line in your code:
+
+        from midiutil.MidiFile3 import MIDIFile
+
+(This assumes that the code has been installed into your system path or that
+the midiutil directory is copied into your script's working directory.)
+
+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
+------------
+
+To use the library one can either install it on one's system or
+copy the midiutil directory of the source distribution to your
+project's directory (or to any directory pointed to  by the PYTHONPATH
+environment variable). For the Windows platforms an executable installer
+is provided. Alternately the source distribution can be downloaded,
+un-zipped (or un-tarred), and installed in the standard way:
+
+    python setup.py install
+
+On non-Windows platforms (Linux, MacOS, etc.) the software should be
+installed in this way. MIDIUtil is pure Python and should work on any
+platform to which Python has been ported.
+
+If you do not wish to install in on your system, just copy the
+src/midiutil directory to your project's directory or elsewhere on
+your PYTHONPATH. 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:
+
+    o The package must be imported into your namespace
+    o A MIDIFile object is created
+    o Events (notes, tempo-changes, etc.) are added to the object
+    o 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 name and tempo to the track, add a one beat middle-C to
+the track, and write it to disk.
+
+        #Import the library
+        from midiutil.MidiFile import MIDIFile
+
+        # Create the MIDIFile Object with 1 track
+        MyMIDI = MIDIFile(1)
+
+        # Tracks are numbered from zero. Times are measured in beats.
+        track = 0
+        time = 0
+
+        # Add track name and tempo.
+        MyMIDI.addTrackName(track,time,"Sample Track")
+        MyMIDI.addTempo(track,time,120)
+
+        # Add a note. addNote expects the following information:
+        track = 0
+        channel = 0
+        pitch = 60
+        time = 0
+        duration = 1
+        volume = 100
+
+        # Now add the note.
+        MyMIDI.addNote(track,channel,pitch,time,duration,volume)
+
+        # And write it to disk.
+        binfile = open("output.mid", 'wb')
+        MyMIDI.writeFile(binfile)
+        binfile.close()
+
+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!
+
+This is version HEAD.
+

File documentation/ClassReference.txt

+========================
+MIDIUtil Class Reference
+========================
+
+--------------
+class MIDIFile
+--------------
+
+        A class that represents a full, well-formed MIDI pattern.
+
+        This is a container object that contains a header, one or more
+        tracks, and the data associated with a proper and well-formed
+        MIDI pattern.
+
+    Calling
+
+        MyMIDI = MidiFile(tracks, removeDuplicates=True,  deinterleave=True)
+
+    normally
+
+        MyMIDI = MidiFile(tracks)
+
+    Arguments
+
+        o tracks: The number of tracks this object contains
+
+        o removeDuplicates: If true (the default), the software will
+          remove duplicate events which have been added. For example,
+          two notes at the same channel, time, pitch, and duration would
+          be considered duplicate.
+
+        o deinterleave: If True (the default), overlapping notes
+          (same pitch, same channel) will be modified so that they do
+          not overlap. Otherwise the sequencing software will need to
+          figure out how to interpret NoteOff events upon playback.
+
+================
+Public Functions
+================
+
+    ---------------------------------------------------
+    addNote(track, channel, pitch,time,duration,volume)
+    ---------------------------------------------------
+
+        Add notes to the MIDIFile object
+
+    Use
+
+        MyMIDI.addNotes(track,channel,pitch,time, duration, volume)
+
+    Arguments
+
+        o track: The track to which the note is added.
+        o channel: the MIDI channel to assign to the note. [Integer, 0-15]
+        o pitch: the MIDI pitch number [Integer, 0-127].
+        o time: the time (in beats) at which the note sounds [Float].
+        o duration: the duration of the note (in beats) [Float].
+        o lume: the volume (velocity) of the note. [Integer, 0-127].
+
+
+    ----------------------------------
+    addTrackName(track, time,trackName)
+    ----------------------------------
+
+        Add a track name to a MIDI track.
+
+    Use
+
+        MyMIDI.addTrackName(track,time,trackName)
+
+    Arguments
+
+        o track: The track to which the name is added. [Integer, 0-127].
+        o time: The time at which the track name is added, in beats
+          [Float].
+        o trackName: The track name. [String].
+
+    ---------------------------
+    addTempo(track, time,tempo)
+    ---------------------------
+
+        Add a tempo event.
+
+    Use
+
+        MyMIDI.addTempo(track, time, tempo)
+
+    Arguments
+
+        o track: The track to which the event is added. [Integer, 0-127]
+        o time: The time at which the event is added, in beats. [Float]
+        o tempo: The tempo, in Beats per Minute. [Integer]
+
+
+    -----------------------------------------------
+    addProgramChange(track, channel, time, program)
+    -----------------------------------------------
+
+        Add a MIDI program change event.
+
+    Use
+
+        MyMIDI.addProgramChange(track,channel, time, program)
+
+    Arguments
+
+        o track: The track to which the event is added. [Integer, 0-127]
+        o channel: The channel the event is assigned to. [Integer, 0-15]
+        o time: The time at which the event is added, in beats. [Float]
+        o program: the program number. [Integer, 0-127]
+
+
+    --------------------------------------------------------------
+    addControllerEvent(track, channel,time,eventType, paramerter1)
+    --------------------------------------------------------------
+
+        Add a MIDI controller event.
+
+    Use
+
+        MyMIDI.addControllerEvent(track, channel, time, eventType, \
+            parameter1)
+
+    Arguments
+
+        o track: The track to which the event is added. [Integer, 0-127]
+        o channel: The channel the event is assigned to. [Integer, 0-15]
+        o time: The time at which the event is added, in beats. [Float]
+        o eventType: the controller event type.
+        o parameter1: The event's parameter. The meaning of which varies
+          by event type.
+
+    ---------------------------------------------------------------------
+    changeNoteTuning(track, tunings, sysExChannel=0x7F, realTime=False, \
+        tuningProgam=0)
+    ---------------------------------------------------------------------
+
+        Change a note's tuning using sysEx change tuning program.
+
+    Use
+
+        MyMIDI.changeNoteTuning(track,[tunings],realTime=False, \
+            tuningProgram=0)
+
+    Arguments
+
+        o track: The track to which the event is added. [Integer, 0-127].
+        o tunings: A list of tuples in the form (pitchNumber,
+          frequency).  [[(Integer,Float]]
+        o realTime: Boolean which sets the real-time flag. Defaults to false.
+        o sysExChannel: do note use (see below).
+        o tuningProgram: Tuning program to assign. Defaults to
+          zero. [Integer, 0-127]
+
+    In general the sysExChannel should not be changed (parameter will
+    be depreciated).
+
+    Also note that many software packages and hardware packages do not
+    implement this standard!
+
+
+    ---------------------
+    writeFile(fileHandle)
+    ---------------------
+
+        Write the MIDI File.
+
+    Use
+
+        MyMIDI.writeFile(filehandle)
+
+    Arguments
+
+        o filehandle: a file handle that has been opened for binary
+          writing.
+
+
+    -------------------------------------
+    addSysEx(track, time, manID, payload)
+    -------------------------------------
+
+        Add a SysEx event
+
+    Use
+
+        MyMIDI.addSysEx(track,time,ID,payload)
+
+    Arguments
+
+        o track: The track to which the event is added. [Integer, 0-127].
+        o time: The time at which the event is added, in beats. [Float].
+        o ID: The SysEx ID number
+        o payload: the event payload.
+
+    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.
+
+
+    ---------------------------------------------------------
+    addUniversalSysEx(track,  time,code, subcode, payload,  \
+        sysExChannel=0x7F,  realTime=False)}f
+    ---------------------------------------------------------
+
+        Add a Universal SysEx event.
+
+    Use
+
+        MyMIDI.addUniversalSysEx(track, time, code, subcode, payload, \
+            sysExChannel=0x7f, realTime=False)
+
+    Arguments
+
+        o track: The track to which the event is added. [Integer, 0-127].
+        o time: The time at which the event is added, in beats. [Float].
+        o code: The event code. [Integer]
+        o subcode The event sub-code [Integer]
+        o payload: The event payload. [Binary string]
+        o sysExChannel: The SysEx channel.
+        o realTime: Sets the real-time flag. Defaults to zero.
+
+    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,
+    both here and in MIDITrack.
+

File documentation/Extending.txt

+=====================
+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 and set the event type (a unique string used internally by
+the software) in the __init__() function. In the case of the tempo event:
+
+    class tempo(GenericEvent):
+        def __init__(self,time,tempo):
+            GenericEvent.__init__(self,time)
+            self.type = 'tempo'
+            self.tempo = int(60000000 / tempo)
+
+Next (and this is an embarrassing break of OO programming) the __eq__()
+function of the GenericEvent class should be modified so that equality
+of these types of events can be calculated. In calculating equivalence
+time is always checked, so two tempo events are considered the same if
+the have the same tempo value. Thus the following snippet of code from
+GenericEvent's _eq__() function accomplishes this goal:
+
+
+    if self.type == 'tempo':
+        if self.tempo != other.tempo:
+            return False
+
+
+If events are equivalent, the code should return False. If they are not
+equivalent no return should be called.
+
+---------------------------
+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):
+        self.eventList.append(MIDITrack.tempo(time,tempo))
+
+
+The public accessor function is via the MIDIFile object, and must include
+the track number to which the event is written:
+
+
+    def addTempo(self,track,time,tempo):
+        self.tracks[track].addTempo(time,tempo)
+
+
+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 ordinality (self.ord) is a number that tells the software how to
+sequence MIDI events that occur at the same time. The higher the number,
+the later in the sequence the event will be written in comparison to
+other, simultaneous events.
+
+The relevant section for the tempo event is:
+
+
+elif thing.type == 'tempo':
+    event = MIDIEvent()
+    event.type = "Tempo"
+    event.time = thing.time * TICKSPERBEAT
+    event.tempo = thing.tempo
+    event.ord = 3
+    self.MIDIEventList.append(event)
+
+
+Thus if other events occur at the same time, type which have an ordinality
+of 1 or 2 will be written to the stream first.
+
+Time needs to be converted from beats (which the accessor function uses)
+and MIDI time by multiplying by the constant TICKSPERBEAT. The value
+of thing.type is the unique string you defined above, and event.type
+is another unique things (they can--and probably should--be the same,
+although the coding here is a little sloppy and changes case of the
+string).
+
+
+----------------------------------------
+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 subcode 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.
+
+The event time should be converted to MIDI variable-length data with the
+writeVarLength() function before writing to the stream (as shown above).
+The MIDI standard utilizes a slightly bizarre variable length data
+record. In it, 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
+subcode. 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.
+
+Clear as mud!

File examples/single-note-example.py

+############################################################################
+# A sample program to create a single-track MIDI file, add a note,
+# and write to disk.
+############################################################################
+
+#Import the library
+from midiutil.MidiFile import MIDIFile
+
+# Create the MIDIFile Object
+MyMIDI = MIDIFile(1)
+
+# Add track name and tempo. The first argument to addTrackName and
+# addTempo is the time to write the event.
+track = 0
+time = 0
+MyMIDI.addTrackName(track,time,"Sample Track")
+MyMIDI.addTempo(track,time, 120)
+
+# Add a note. addNote expects the following information:
+channel = 0
+pitch = 60
+duration = 1
+volume = 100
+
+# Now add the note.
+MyMIDI.addNote(track,channel,pitch,time,duration,volume)
+
+# And write it to disk.
+binfile = open("output.mid", 'wb')
+MyMIDI.writeFile(binfile)
+binfile.close()
+
+from distutils.core import setup
+
+setup(name='MIDIUtil',
+      version='HEAD',
+      description='MIDIUtil, a MIDI Interface for Python',
+      author='Mark Conway Wirt',
+      author_email='emergentmusics) at (gmail . com',
+      license='Copyright (C) 2009, Mark Conway Wirt. See License.txt for details.',
+      url='www.emergentmusics.org',
+      packages=["midiutil"],
+      package_dir = {'midiutil': 'src/midiutil'},
+      package_data={'midiutil' : ['../../documentation/*']},
+      scripts=['examples/single-note-example.py'],
+      platforms='Platform Independent',
+      long_description='''
+This package provides a simple interface to allow Python programs to
+write multi-track MIDI files.'''
+     )

File src/midiutil/MidiFile.py

+#-----------------------------------------------------------------------------
+# Name:        MidiFile.py
+# Purpose:     MIDI file manipulation utilities
+#
+# Author:      Mark Conway Wirt <emergentmusics) at (gmail . com>
+#
+# Created:     2008/04/17
+# Copyright:   (c) 2009 Mark Conway Wirt
+# License:     Please see License.txt for the terms under which this
+#              software is distributed.
+#-----------------------------------------------------------------------------
+
+import struct,  sys,  math
+
+# TICKSPERBEAT is the number of "ticks" (time measurement in the MIDI file) that
+# corresponds to one beat. This number is somewhat arbitrary, but should be chosen
+# to provide adequate temporal resolution.
+
+TICKSPERBEAT = 128
+
+controllerEventTypes = {
+                        'pan' : 0x0a
+                        }
+class MIDIEvent:
+    '''
+    The class to contain the MIDI Event (placed on MIDIEventList.
+    '''
+    def __init__(self):
+        self.type='unknown'
+        self.time=0
+        self.ord = 0
+        
+    def __cmp__(self, other):
+        ''' Sorting function for events.'''
+        if self.time < other.time:
+            return -1
+        elif self.time > other.time:
+            return 1
+        else:
+            if self.ord < other.ord:
+                return -1
+            elif self.ord > other.ord:
+                return 1
+            else:
+                return 0
+
+class GenericEvent():
+    '''The event class from which specific events are derived
+    '''
+    def __init__(self,time):
+        self.time = time 
+        self.type = 'Unknown'
+
+
+        
+    def __eq__(self, other):
+        '''
+        Equality operator for Generic Events and derived classes.
+        
+        In the processing of the event list, we have need to remove duplicates. To do this
+        we rely on the fact that the classes are hashable, and must therefore have an 
+        equality operator (__hash__() and __eq__() must both be defined).
+        
+        This is the most embarrassing portion of the code, and anyone who knows about OO
+        programming would find this almost unbelievable. Here we have a base class that
+        knows specifics about derived classes, thus breaking the very spirit of 
+        OO programming.
+        
+        I suppose I should go back and restructure the code, perhaps removing the derived
+        classes altogether. At some point perhaps I will.
+        '''
+        if self.time != other.time or self.type != other.type:
+            return False
+            
+        # What follows is code that encodes the concept of equality for each derived 
+        # class. Believe it f you dare.
+        
+        if self.type == 'note':
+            if self.pitch != other.pitch or self.channel != other.channel:
+                return False
+        if self.type == 'tempo':
+            if self.tempo != other.tempo:
+                return False
+        if self.type == 'programChange':
+            if self.programNumber != other.programNumber or self.channel != other.channel:
+                return False
+        if self.type == 'trackName':
+            if self.trackName != other.trackName:
+                return False
+        if self.type == 'controllerEvent':
+            if self.parameter1 != other.parameter1 or \
+                self.parameter2 != other.parameter2 or \
+                self.channel != other.channel or \
+                self.eventType != other.eventType:
+                return False
+                
+        if self.type == 'SysEx':
+            if self.manID != other.manID:
+                return False
+                
+        if self.type == 'UniversalSysEx':
+            if self.code != other.code or\
+                self.subcode != other.subcode or \
+                self.sysExChannel != other.sysExChannel:
+                return False
+                
+        return True
+        
+    def __hash__(self):
+        '''
+        Return a hash code for the object.
+        
+        This is needed for the removal of duplicate objects from the event list. The only
+        real requirement for the algorithm is that the hash of equal objects must be equal.
+        There is probably great opportunity for improvements in the hashing function.
+        '''
+        # Robert Jenkin's 32 bit hash.
+        a = int(self.time)
+        a = (a+0x7ed55d16) + (a<<12)
+        a = (a^0xc761c23c) ^ (a>>19)
+        a = (a+0x165667b1) + (a<<5)
+        a = (a+0xd3a2646c) ^ (a<<9)
+        a = (a+0xfd7046c5) + (a<<3)
+        a = (a^0xb55a4f09) ^ (a>>16)
+        return a
+
+class MIDITrack:
+    '''A class that encapsulates a MIDI track
+    '''
+    # Nested class definitions.
+    
+    class note(GenericEvent):
+        '''A class that encapsulates a note
+        '''
+        def __init__(self,channel, pitch,time,duration,volume):
+            
+            GenericEvent.__init__(self,time)
+            self.pitch = pitch
+            self.duration = duration
+            self.volume = volume
+            self.type = 'note'
+            self.channel = channel
+            
+        def compare(self, other):
+            '''Compare two notes for equality.
+            '''
+            if self.pitch == other.pitch and \
+                self.time == other.time and \
+                self.duration == other.duration and \
+                self.volume == other.volume and \
+                self.type == other.type and \
+                self.channel == other.channel:
+                    return True
+            else:
+                    return False
+                    
+            
+    class tempo(GenericEvent):
+        '''A class that encapsulates a tempo meta-event
+        '''
+        def __init__(self,time,tempo):
+            
+            GenericEvent.__init__(self,time)
+            self.type = 'tempo'
+            self.tempo = int(60000000 / tempo)
+            
+    class programChange(GenericEvent):
+        '''A class that encapsulates a program change event.
+        '''
+        
+        def __init__(self,  channel,  time,  programNumber):
+            GenericEvent.__init__(self, time,)
+            self.type = 'programChange'
+            self.programNumber = programNumber
+            self.channel = channel
+            
+    class SysExEvent(GenericEvent):
+        '''A class that encapsulates a System Exclusive  event.
+        '''
+        
+        def __init__(self,  time,  manID,  payload):
+            GenericEvent.__init__(self, time,)
+            self.type = 'SysEx'
+            self.manID = manID
+            self.payload = payload
+            
+    class UniversalSysExEvent(GenericEvent):
+        '''A class that encapsulates a Universal System Exclusive  event.
+        '''
+        
+        def __init__(self,  time,  realTime,  sysExChannel,  code,  subcode,  payload):
+            GenericEvent.__init__(self, time,)
+            self.type = 'UniversalSysEx'
+            self.realTime = realTime
+            self.sysExChannel = sysExChannel
+            self.code = code
+            self.subcode = subcode
+            self.payload = payload
+            
+    class ControllerEvent(GenericEvent):
+        '''A class that encapsulates a program change event.
+        '''
+        
+        def __init__(self,  channel,  time,  eventType,  parameter1,):
+            GenericEvent.__init__(self, time,)
+            self.type = 'controllerEvent'
+            self.parameter1 = parameter1
+            self.channel = channel
+            self.eventType = eventType
+
+    class trackName(GenericEvent):
+        '''A class that encapsulates a program change event.
+        '''
+        
+        def __init__(self,  time,  trackName):
+            GenericEvent.__init__(self, time,)
+            self.type = 'trackName'
+            self.trackName = trackName
+
+            
+    def __init__(self, removeDuplicates,  deinterleave):
+        '''Initialize the MIDITrack object.
+        '''
+        self.headerString = struct.pack('cccc','M','T','r','k')
+        self.dataLength = 0 # Is calculated after the data is in place
+        self.MIDIdata = ""
+        self.closed = False
+        self.eventList = []
+        self.MIDIEventList = []
+        self.remdep = removeDuplicates
+        self.deinterleave = deinterleave
+        
+    def addNoteByNumber(self,channel, pitch,time,duration,volume):
+        '''Add a note by chromatic MIDI number
+        '''
+        self.eventList.append(MIDITrack.note(channel, pitch,time,duration,volume))
+        
+    def addControllerEvent(self,channel,time,eventType, paramerter1):
+        '''
+        Add a controller event.
+        '''
+        
+        self.eventList.append(MIDITrack.ControllerEvent(channel,time,eventType, \
+                                             paramerter1))
+        
+    def addTempo(self,time,tempo):
+        '''
+        Add a tempo change (or set) event.
+        '''
+        self.eventList.append(MIDITrack.tempo(time,tempo))
+        
+    def addSysEx(self,time,manID, payload):
+        '''
+        Add a SysEx event.
+        '''
+        self.eventList.append(MIDITrack.SysExEvent(time, manID,  payload))
+        
+    def addUniversalSysEx(self,time,code, subcode, payload,  sysExChannel=0x7F,  \
+        realTime=False):
+        '''
+        Add a Universal SysEx event.
+        '''
+        self.eventList.append(MIDITrack.UniversalSysExEvent(time, realTime,  \
+            sysExChannel,  code,  subcode, payload))
+        
+    def addProgramChange(self,channel, time, program):
+        '''
+        Add a program change event.
+        '''
+        self.eventList.append(MIDITrack.programChange(channel, time, program))
+        
+    def addTrackName(self,time,trackName):
+        '''
+        Add a track name event.
+        '''
+        self.eventList.append(MIDITrack.trackName(time,trackName))
+        
+    def changeNoteTuning(self,  tunings,   sysExChannel=0x7F,  realTime=False,  \
+        tuningProgam=0):
+        '''Change the tuning of MIDI notes
+        '''
+        payload = struct.pack('>B',  tuningProgam)
+        payload = payload + struct.pack('>B',  len(tunings))
+        for (noteNumber,  frequency) in tunings:
+            payload = payload + struct.pack('>B',  noteNumber)
+            MIDIFreqency = frequencyTransform(frequency)
+            for byte in MIDIFreqency:
+                payload = payload + struct.pack('>B',  byte)
+                
+        self.eventList.append(MIDITrack.UniversalSysExEvent(0, realTime,  sysExChannel,\
+            8,  2, payload))
+    
+    def processEventList(self):
+        '''
+        Process the event list, creating a MIDIEventList
+        
+        For each item in the event list, one or more events in the MIDIEvent
+        list are created.
+        '''
+        
+        # Loop over all items in the eventList
+        
+        for thing in self.eventList:
+            if thing.type == 'note':
+                event = MIDIEvent()
+                event.type = "NoteOn"
+                event.time = thing.time * TICKSPERBEAT
+                event.pitch = thing.pitch
+                event.volume = thing.volume
+                event.channel = thing.channel
+                event.ord = 3
+                self.MIDIEventList.append(event)
+
+                event = MIDIEvent()
+                event.type = "NoteOff"
+                event.time = (thing.time + thing.duration) * TICKSPERBEAT
+                event.pitch = thing.pitch
+                event.volume = thing.volume
+                event.channel = thing.channel
+                event.ord = 2
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'tempo':
+                event = MIDIEvent()
+                event.type = "Tempo"
+                event.time = thing.time * TICKSPERBEAT
+                event.tempo = thing.tempo
+                event.ord = 3
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'programChange':
+                event = MIDIEvent()
+                event.type = "ProgramChange"
+                event.time = thing.time * TICKSPERBEAT
+                event.programNumber = thing.programNumber
+                event.channel = thing.channel
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'trackName':
+                event = MIDIEvent()
+                event.type = "TrackName"
+                event.time = thing.time * TICKSPERBEAT
+                event.trackName = thing.trackName
+                event.ord = 0
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'controllerEvent':
+                event = MIDIEvent()
+                event.type = "ControllerEvent"
+                event.time = thing.time * TICKSPERBEAT
+                event.eventType = thing.eventType
+                event.channel = thing.channel
+                event.paramerter1 = thing.parameter1
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'SysEx':
+                event = MIDIEvent()
+                event.type = "SysEx"
+                event.time = thing.time * TICKSPERBEAT
+                event.manID = thing.manID
+                event.payload = thing.payload
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'UniversalSysEx':
+                event = MIDIEvent()
+                event.type = "UniversalSysEx"
+                event.realTime = thing.realTime
+                event.sysExChannel = thing.sysExChannel
+                event.time = thing.time * TICKSPERBEAT
+                event.code = thing.code
+                event.subcode = thing.subcode
+                event.payload = thing.payload
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            else:
+                print "Error in MIDITrack: Unknown event type"
+                sys.exit(2)
+            
+        # Assumptions in the code expect the list to be time-sorted.
+        # self.MIDIEventList.sort(lambda x, y: x.time - y.time)
+
+        self.MIDIEventList.sort(lambda x, y: int( 1000 * (x.time - y.time)))
+
+        if self.deinterleave:    
+            self.deInterleaveNotes()
+
+    def removeDuplicates(self):
+        '''
+        Remove duplicates from the eventList.
+        
+        This function will remove duplicates from the eventList. This is necessary
+        because we the MIDI event stream can become confused otherwise.
+        '''
+        
+        # For this algorithm to work, the events in the eventList must be hashable 
+        # (that is, they must have a __hash__() and __eq__() function defined).
+        
+        tempDict = {}
+        for item in self.eventList:
+            tempDict[item] = 1
+            
+        self.eventList = tempDict.keys()
+        
+        # Sort on type, them on time. Necessary because keys() has no requirement to return
+        # things in any order.
+        
+        self.eventList.sort(lambda x, y: cmp(x.type ,  y.type))
+        self.eventList.sort(lambda x, y: int( 1000 * (x.time - y.time))) #A bit of a hack.
+
+    def closeTrack(self):
+        '''Called to close a track before writing
+        
+        This function should be called to "close a track," that is to
+        prepare the actual data stream for writing. Duplicate events are
+        removed from the eventList, and the MIDIEventList is created.
+        
+        Called by the parent MIDIFile object.
+        '''
+
+        if self.closed == True:
+            return
+        self.closed = True
+        
+        if self.remdep:
+            self.removeDuplicates()
+            
+
+        self.processEventList()
+        
+    def writeMIDIStream(self):
+        '''
+        Write the meta data and note data to the packed MIDI stream.
+        '''
+
+        #Process the events in the eventList
+
+        self.writeEventsToStream()
+
+        # Write MIDI close event.
+
+        self.MIDIdata = self.MIDIdata + struct.pack('BBBB',0x00,0xFF, \
+            0x2F,0x00)
+        
+        # Calculate the entire length of the data and write to the header
+        
+        self.dataLength = struct.pack('>L',len(self.MIDIdata))
+
+    def writeEventsToStream(self):
+        '''
+        Write the events in MIDIEvents to the MIDI stream.
+        '''
+        
+        for event in self.MIDIEventList:
+            if event.type == "NoteOn":
+                code = 0x9 << 4 | event.channel
+                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',event.pitch)
+                self.MIDIdata = self.MIDIdata + struct.pack('>B',event.volume)
+            elif event.type == "NoteOff":
+                code = 0x8 << 4 | event.channel
+                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',event.pitch)
+                self.MIDIdata = self.MIDIdata + struct.pack('>B',event.volume)
+            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) # Data length: 3
+                self.MIDIdata = self.MIDIdata + threebite
+            elif event.type == 'ProgramChange':
+                code = 0xC << 4 | event.channel
+                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',event.programNumber)
+            elif event.type == 'TrackName':
+                varTime = writeVarLength(event.time)
+                for timeByte in varTime:
+                    self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte)
+                self.MIDIdata = self.MIDIdata + struct.pack('B',0xFF) # Meta-event
+                self.MIDIdata = self.MIDIdata + struct.pack('B',0X03) # Event Type
+                dataLength = len(event.trackName)
+                dataLenghtVar = writeVarLength(dataLength)
+                for i in range(0,len(dataLenghtVar)):
+                    self.MIDIdata = self.MIDIdata + struct.pack("b",dataLenghtVar[i])
+                self.MIDIdata = self.MIDIdata + event.trackName
+            elif event.type == "ControllerEvent":
+                code = 0xB << 4 | event.channel
+                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',event.eventType)
+                self.MIDIdata = self.MIDIdata + struct.pack('>B',event.paramerter1)
+            elif event.type == "SysEx":
+                code = 0xF0
+                varTime = writeVarLength(event.time)
+                for timeByte in varTime:
+                    self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte)
+                self.MIDIdata = self.MIDIdata + struct.pack('>B', code)
+                
+                payloadLength = writeVarLength(len(event.payload)+2)
+                for lenByte in payloadLength:
+                    self.MIDIdata = self.MIDIdata + struct.pack('>B',lenByte)
+                    
+                self.MIDIdata = self.MIDIdata + struct.pack('>B', event.manID)
+                self.MIDIdata = self.MIDIdata + event.payload
+                self.MIDIdata = self.MIDIdata + struct.pack('>B',0xF7)
+            elif event.type == "UniversalSysEx":
+                code = 0xF0
+                varTime = writeVarLength(event.time)
+                for timeByte in varTime:
+                    self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte)
+                self.MIDIdata = self.MIDIdata + struct.pack('>B', code)
+                
+                # Do we need to add a length?
+                payloadLength = writeVarLength(len(event.payload)+5)
+                for lenByte in payloadLength:
+                    self.MIDIdata = self.MIDIdata + struct.pack('>B',lenByte)
+                
+                if event.realTime :
+                    self.MIDIdata = self.MIDIdata + struct.pack('>B', 0x7F)
+                else:
+                    self.MIDIdata = self.MIDIdata + struct.pack('>B', 0x7E)
+                    
+                self.MIDIdata = self.MIDIdata + struct.pack('>B', event.sysExChannel)
+                self.MIDIdata = self.MIDIdata + struct.pack('>B', event.code)
+                self.MIDIdata = self.MIDIdata + struct.pack('>B', event.subcode)
+                self.MIDIdata = self.MIDIdata + event.payload
+                self.MIDIdata = self.MIDIdata + struct.pack('>B',0xF7)
+        
+    def deInterleaveNotes(self):
+        '''Correct Interleaved notes.
+        
+        Because we are writing multiple notes in no particular order, we
+        can have notes which are interleaved with respect to their start
+        and stop times. This method will correct that. It expects that the
+        MIDIEventList has been time-ordered.
+        '''
+        
+        tempEventList = []
+        stack = {}
+        
+        for event in self.MIDIEventList:
+            
+            if event.type == 'NoteOn':
+                if stack.has_key(str(event.pitch)+str(event.channel)):
+                    stack[str(event.pitch)+str(event.channel)].append(event.time)
+                else:
+                    stack[str(event.pitch)+str(event.channel)] = [event.time]
+                tempEventList.append(event)
+            elif event.type == 'NoteOff':
+                if len(stack[str(event.pitch)+str(event.channel)]) > 1:
+                    event.time = stack[str(event.pitch)+str(event.channel)].pop()
+                    tempEventList.append(event)
+                else:
+                    stack[str(event.pitch)+str(event.channel)].pop()
+                    tempEventList.append(event)
+            else:
+                tempEventList.append(event)
+                    
+        self.MIDIEventList = tempEventList
+        
+        # A little trickery here. We want to make sure that NoteOff events appear 
+        # before NoteOn events, so we'll do two sorts -- on on type, one on time. 
+        # This may have to be revisited, as it makes assumptions about how 
+        # the internal sort works, and is in essence creating a sort on a primary 
+        # and secondary key.
+        
+        self.MIDIEventList.sort(lambda x, y: cmp(x.type ,  y.type))
+        self.MIDIEventList.sort(lambda x, y: int( 1000 * (x.time - y.time)))
+
+    def adjustTime(self,origin):
+        '''
+        Adjust Times to be relative, and zero-origined
+        '''
+        
+        if len(self.MIDIEventList) == 0:
+            return
+        tempEventList = []
+    
+        runningTime = 0 
+        
+        for event in self.MIDIEventList:
+            adjustedTime = event.time - origin
+            event.time = adjustedTime - runningTime
+            runningTime = adjustedTime
+            tempEventList.append(event)
+            
+        self.MIDIEventList = tempEventList
+        
+    def writeTrack(self,fileHandle):
+        '''
+        Write track to disk.
+        '''
+        
+        if not self.closed:
+            self.closeTrack()
+            
+        fileHandle.write(self.headerString)
+        fileHandle.write(self.dataLength)
+        fileHandle.write(self.MIDIdata)
+
+
+class MIDIHeader:
+    '''
+    Class to encapsulate the MIDI header structure.
+    
+    This class encapsulates a MIDI header structure. It isn't used for much,
+    but it will create the appropriately packed identifier string that all
+    MIDI files should contain. It is used by the MIDIFile class to create a
+    complete and well formed MIDI pattern.
+    
+    '''
+    def __init__(self,numTracks):
+        ''' Initialize the data structures
+        '''
+        self.headerString = struct.pack('cccc','M','T','h','d')
+        self.headerSize = struct.pack('>L',6)
+        # Format 1 = multi-track file
+        self.format = struct.pack('>H',1)
+        self.numTracks = struct.pack('>H',numTracks)
+        self.ticksPerBeat = struct.pack('>H',TICKSPERBEAT)
+    
+
+    def writeFile(self,fileHandle):
+        fileHandle.write(self.headerString)
+        fileHandle.write(self.headerSize)
+        fileHandle.write(self.format)
+        fileHandle.write(self.numTracks)
+        fileHandle.write(self.ticksPerBeat)
+
+class MIDIFile:
+    '''Class that represents a full, well-formed MIDI pattern.
+    
+    This is a container object that contains a header, one or more tracks,
+    and the data associated with a proper and well-formed MIDI pattern.
+    
+    Calling:
+    
+        MyMIDI = MidiFile(tracks, removeDuplicates=True,  deinterleave=True)
+        
+        normally
+        
+        MyMIDI = MidiFile(tracks)
+        
+    Arguments:
+    
+        tracks: The number of tracks this object contains
+            
+        removeDuplicates: If true (the default), the software will remove duplicate
+        events which have been added. For example, two notes at the same channel,
+        time, pitch, and duration would be considered duplicate.
+        
+        deinterleave: If True (the default), overlapping notes (same pitch, same
+        channel) will be modified so that they do not overlap. Otherwise the sequencing
+        software will need to figure out how to interpret NoteOff events upon playback.
+    '''
+    
+    def __init__(self, numTracks, removeDuplicates=True,  deinterleave=True):
+        '''
+        Initialize the class
+        '''
+        self.header = MIDIHeader(numTracks)
+        
+        self.tracks = list()
+        self.numTracks = numTracks
+        self.closed = False
+        
+        for i in range(0,numTracks):
+            self.tracks.append(MIDITrack(removeDuplicates,  deinterleave))
+            
+            
+    # Public Functions. These (for the most part) wrap the MIDITrack functions, where most
+    # Processing takes place.
+    
+    def addNote(self,track, channel, pitch,time,duration,volume):
+        """
+        Add notes to the MIDIFile object
+        
+        Use:
+            MyMIDI.addNotes(track,channel,pitch,time, duration, volume)
+            
+        Arguments:
+            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].
+        """
+        self.tracks[track].addNoteByNumber(channel, pitch, time, duration, volume)
+
+    def addTrackName(self,track, time,trackName):
+        """
+        Add a track name to a MIDI track.
+        
+        Use:
+            MyMIDI.addTrackName(track,time,trackName)
+            
+        Argument:
+            track: The track to which the name is added. [Integer, 0-127].
+            time: The time at which the track name is added, in beats [Float].
+            trackName: The track name. [String].
+        """
+        self.tracks[track].addTrackName(time,trackName)
+        
+    def addTempo(self,track, time,tempo):
+        """
+        Add a tempo event.
+        
+        Use:
+            MyMIDI.addTempo(track, time, tempo)
+            
+        Arguments:
+            track: The track to which the event is added. [Integer, 0-127].
+            time: The time at which the event is added, in beats. [Float].
+            tempo: The tempo, in Beats per Minute. [Integer]
+        """
+        self.tracks[track].addTempo(time,tempo)
+        
+    def addProgramChange(self,track, channel, time, program):
+        """
+        Add a MIDI program change event.
+        
+        Use:
+            MyMIDI.addProgramChange(track,channel, time, program)
+            
+        Arguments:
+            track: The track to which the event is added. [Integer, 0-127].
+            channel: The channel the event is assigned to. [Integer, 0-15].
+            time: The time at which the event is added, in beats. [Float].
+            program: the program number. [Integer, 0-127].
+        """
+        self.tracks[track].addProgramChange(channel, time, program)
+    
+    def addControllerEvent(self,track, channel,time,eventType, paramerter1):
+        """
+        Add a MIDI controller event.
+        
+        Use:
+            MyMIDI.addControllerEvent(track, channel, time, eventType, parameter1)
+            
+        Arguments:
+            track: The track to which the event is added. [Integer, 0-127].
+            channel: The channel the event is assigned to. [Integer, 0-15].
+            time: The time at which the event is added, in beats. [Float].
+            eventType: the controller event type.
+            parameter1: The event's parameter. The meaning of which varies by event type.
+        """
+        self.tracks[track].addControllerEvent(channel,time,eventType, paramerter1)
+        
+    def changeNoteTuning(self,  track,  tunings,   sysExChannel=0x7F,  \
+                         realTime=False,  tuningProgam=0):
+        """
+        Change a note's tuning using SysEx change tuning program.
+            
+        Use:
+            MyMIDI.changeNoteTuning(track,[tunings],realTime=False, tuningProgram=0)
+            
+        Arguments:
+            track: The track to which the event is added. [Integer, 0-127].
+            tunings: A list of tuples in the form (pitchNumber, frequency). 
+                     [[(Integer,Float]]
+            realTime: Boolean which sets the real-time flag. Defaults to false.
+            sysExChannel: do note use (see below).
+            tuningProgram: Tuning program to assign. Defaults to zero. [Integer, 0-127]
+            
+        In general the sysExChannel should not be changed (parameter will be depreciated).
+        
+        Also note that many software packages and hardware packages do not implement
+        this standard!
+        """
+        self.tracks[track].changeNoteTuning(tunings,   sysExChannel,  realTime,\
+                                              tuningProgam)
+  
+    def writeFile(self,fileHandle):
+        '''
+        Write the MIDI File.
+        
+        Use:
+            MyMIDI.writeFile(filehandle)
+        
+        Arguments:
+            filehandle: a file handle that has been opened for binary writing.
+        '''
+        
+        self.header.writeFile(fileHandle)
+        
+        #Close the tracks and have them create the MIDI event data structures.
+        self.close()
+        
+        #Write the MIDI Events to file.
+        for i in range(0,self.numTracks):
+            self.tracks[i].writeTrack(fileHandle)
+
+    def addSysEx(self,track, time, manID, payload):
+        """
+        Add a SysEx event
+        
+        Use:
+            MyMIDI.addSysEx(track,time,ID,payload)
+            
+        Arguments:
+            track: The track to which the event is added. [Integer, 0-127].
+            time: The time at which the event is added, in beats. [Float].
+            ID: The SysEx ID number
+            payload: the event payload.
+            
+        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.
+        """
+        self.tracks[track].addSysEx(time,manID, payload)
+    
+    def addUniversalSysEx(self,track,  time,code, subcode, payload,  \
+                          sysExChannel=0x7F,  realTime=False):
+        """
+        Add a Universal SysEx event.
+        
+        Use:
+            MyMIDI.addUniversalSysEx(track, time, code, subcode, payload,\
+                                      sysExChannel=0x7f, realTime=False)
+                    
+        Arguments:
+            track: The track to which the event is added. [Integer, 0-127].
+            time: The time at which the event is added, in beats. [Float].
+            code: The even code. [Integer]
+            subcode The event sub-code [Integer]
+            payload: The event payload. [Binary string]
+            sysExChannel: The SysEx channel.
+            realTime: Sets the real-time flag. Defaults to zero.
+        
+        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, both here and
+        in MIDITrack.
+        """
+        
+        self.tracks[track].addUniversalSysEx(time,code, subcode, payload,  sysExChannel,\
+                                               realTime)
+                                               
+    def shiftTracks(self,  offset=0):
+        """Shift tracks to be zero-origined, or origined at offset.
+        
+        Note that the shifting of the time in the tracks uses the MIDIEventList -- in other
+        words it is assumed to be called in the stage where the MIDIEventList has been
+        created. This function, however, it meant to operate on the eventList itself.
+        """
+        origin = 1000000 # A little silly, but we'll assume big enough
+
+        for track in self.tracks:
+                if len(track.eventList) > 0:
+                    for event in track.eventList:
+                        if event.time < origin:
+                            origin = event.time
+        
+        for track in self.tracks:
+            tempEventList = []
+            #runningTime = 0 
+        
+            for event in track.eventList:
+                adjustedTime = event.time - origin
+                #event.time = adjustedTime - runningTime + offset
+                event.time = adjustedTime + offset
+                #runningTime = adjustedTime
+                tempEventList.append(event)
+            
+            track.eventList = tempEventList
+
+    #End Public Functions ########################
+    
+    def close(self):
+        '''Close the MIDIFile for further writing.
+        
+        To close the File for events, we must close the tracks, adjust the time to be
+        zero-origined, and have the tracks write to their MIDI Stream data structure.
+        '''
+        
+        if self.closed == True:
+            return
+                
+        for i in range(0,self.numTracks):
+            self.tracks[i].closeTrack()
+            # We want things like program changes to come before notes when they are at the
+            # same time, so we sort the MIDI events by their ordinality
+            self.tracks[i].MIDIEventList.sort()
+            
+        origin = self.findOrigin()
+
+        for i in range(0,self.numTracks):
+            self.tracks[i].adjustTime(origin)
+            self.tracks[i].writeMIDIStream()
+            
+        self.closed = True
+    
+    
+    def findOrigin(self):
+        '''Find the earliest time in the file's tracks.append.
+        '''
+        origin = 1000000 # A little silly, but we'll assume big enough
+
+    # Note: This code assumes that the MIDIEventList has been sorted, so this should be insured
+    # before it is called. It is probably a poor design to do this. 
+    # TODO: -- Consider making this less efficient but more robust by not assuming the list to be sorted.
+    
+        for track in self.tracks:
+                if len(track.MIDIEventList) > 0:
+                    if track.MIDIEventList[0].time < origin:
+                        origin = track.MIDIEventList[0].time
+                        
+        
+        return origin
+            
+def writeVarLength(i):
+    '''Accept an input, and write a MIDI-compatible variable length stream
+    
+    The MIDI format is a little strange, and makes use of so-called variable
+    length quantities. These quantities are a stream of bytes. If the most
+    significant bit is 1, then more bytes follow. If it is zero, then the
+    byte in question is the last in the stream
+    '''
+    input = int(i)
+    output = [0,0,0,0]
+    reversed = [0,0,0,0]
+    count = 0
+    result = input & 0x7F
+    output[count] = result
+    count = count + 1
+    input = input >> 7
+    while input > 0:
+        result = input & 0x7F 
+        result = result | 0x80
+        output[count] = result
+        count = count + 1
+        input = input >> 7  
+
+    reversed[0] = output[3]
+    reversed[1] = output[2]
+    reversed[2] = output[1]
+    reversed[3] = output[0]
+    return reversed[4-count:4]
+
+def frequencyTransform(freq):
+    '''Returns a three-byte transform of a frequencyTransform
+    '''
+    resolution = 16384
+    freq = float(freq)
+    dollars = 69 + 12 * math.log(freq/(float(440)), 2)
+    firstByte = int(dollars)
+    lowerFreq = 440 * pow(2.0, ((float(firstByte) - 69.0)/12.0))
+    if freq != lowerFreq:
+        centDif = 1200 * math.log( (freq/lowerFreq), 2)
+    else:
+        centDif = 0
+    cents = round(centDif/100 * resolution) # round?
+    secondByte = min([int(cents)>>7, 0x7F])
+    thirdByte = cents - (secondByte << 7)
+    thirdByte = min([thirdByte, 0x7f])
+    if thirdByte == 0x7f and secondByte == 0x7F and firstByte == 0x7F:
+        thirdByte = 0x7e
+        
+    thirdByte = int(thirdByte)
+    return [firstByte,  secondByte,  thirdByte]
+    
+def returnFrequency(freqBytes):
+    '''The reverse of frequencyTransform. Given a byte stream, return a frequency.
+    '''
+    resolution = 16384.0
+    baseFrequency = 440 * pow(2.0, (float(freqBytes[0]-69.0)/12.0))
+    frac = (float((int(freqBytes[1]) << 7) + int(freqBytes[2])) * 100.0) / resolution
+    frequency = baseFrequency * pow(2.0, frac/1200.0)
+    return frequency

File src/midiutil/MidiFile3.py

+#-----------------------------------------------------------------------------
+# Name:        MidiFile.py
+# Purpose:     MIDI file manipulation utilities
+#
+# Author:      Mark Conway Wirt <emergentmusics) at (gmail . com>
+#
+# Created:     2008/04/17
+# Copyright:   (c) 2009 Mark Conway Wirt
+# License:     Please see License.txt for the terms under which this
+#              software is distributed.
+#-----------------------------------------------------------------------------
+
+import struct,  sys,  math
+
+# TICKSPERBEAT is the number of "ticks" (time measurement in the MIDI file) that
+# corresponds to one beat. This number is somewhat arbitrary, but should be chosen
+# to provide adequate temporal resolution.
+
+TICKSPERBEAT = 128
+
+controllerEventTypes = {
+                        'pan' : 0x0a
+                        }
+class MIDIEvent:
+    '''
+    The class to contain the MIDI Event (placed on MIDIEventList.
+    '''
+    def __init__(self):
+        self.type='unknown'
+        self.time=0
+        self.ord = 0
+        
+    def __lt__(self, other):
+        ''' Sorting function for events.'''
+        if self.time < other.time:
+            return True
+        elif self.time > other.time:
+            return False
+        else:
+            if self.ord < other.ord:
+                return True
+            elif self.ord > other.ord:
+                return False
+            else:
+                return False
+
+    def __cmp__(self, other):
+        ''' Sorting function for events.'''
+        if self.time < other.time:
+            return -1
+        elif self.time > other.time:
+            return 1
+        else:
+            if self.ord < other.ord:
+                return -1
+            elif self.ord > other.ord:
+                return 1
+            else:
+                return 0
+
+class GenericEvent():
+    '''The event class from which specific events are derived
+    '''
+    def __init__(self,time):
+        self.time = time 
+        self.type = 'Unknown'
+
+
+        
+    def __eq__(self, other):
+        '''
+        Equality operator for Generic Events and derived classes.
+        
+        In the processing of the event list, we have need to remove duplicates. To do this
+        we rely on the fact that the classes are hashable, and must therefore have an 
+        equality operator (__hash__() and __eq__() must both be defined).
+        
+        This is the most embarrassing portion of the code, and anyone who knows about OO
+        programming would find this almost unbelievable. Here we have a base class that
+        knows specifics about derived classes, thus breaking the very spirit of 
+        OO programming.
+        
+        I suppose I should go back and restructure the code, perhaps removing the derived
+        classes altogether. At some point perhaps I will.
+        '''
+        if self.time != other.time or self.type != other.type:
+            return False
+            
+        # What follows is code that encodes the concept of equality for each derived 
+        # class. Believe it f you dare.
+        
+        if self.type == 'note':
+            if self.pitch != other.pitch or self.channel != other.channel:
+                return False
+        if self.type == 'tempo':
+            if self.tempo != other.tempo:
+                return False
+        if self.type == 'programChange':
+            if self.programNumber != other.programNumber or self.channel != other.channel:
+                return False
+        if self.type == 'trackName':
+            if self.trackName != other.trackName:
+                return False
+        if self.type == 'controllerEvent':
+            if self.parameter1 != other.parameter1 or \
+                self.parameter2 != other.parameter2 or \
+                self.channel != other.channel or \
+                self.eventType != other.eventType:
+                return False
+                
+        if self.type == 'SysEx':
+            if self.manID != other.manID:
+                return False
+                
+        if self.type == 'UniversalSysEx':
+            if self.code != other.code or\
+                self.subcode != other.subcode or \
+                self.sysExChannel != other.sysExChannel:
+                return False
+                
+        return True
+        
+    def __hash__(self):
+        '''
+        Return a hash code for the object.
+        
+        This is needed for the removal of duplicate objects from the event list. The only
+        real requirement for the algorithm is that the hash of equal objects must be equal.
+        There is probably great opportunity for improvements in the hashing function.
+        '''
+        # Robert Jenkin's 32 bit hash.
+        a = int(self.time)
+        a = (a+0x7ed55d16) + (a<<12)
+        a = (a^0xc761c23c) ^ (a>>19)
+        a = (a+0x165667b1) + (a<<5)
+        a = (a+0xd3a2646c) ^ (a<<9)
+        a = (a+0xfd7046c5) + (a<<3)
+        a = (a^0xb55a4f09) ^ (a>>16)
+        return a
+
+class MIDITrack:
+    '''A class that encapsulates a MIDI track
+    '''
+    # Nested class definitions.
+    
+    class note(GenericEvent):
+        '''A class that encapsulates a note
+        '''
+        def __init__(self,channel, pitch,time,duration,volume):
+            
+            GenericEvent.__init__(self,time)
+            self.pitch = pitch
+            self.duration = duration
+            self.volume = volume
+            self.type = 'note'
+            self.channel = channel
+            
+        def compare(self, other):
+            '''Compare two notes for equality.
+            '''
+            if self.pitch == other.pitch and \
+                self.time == other.time and \
+                self.duration == other.duration and \
+                self.volume == other.volume and \
+                self.type == other.type and \
+                self.channel == other.channel:
+                    return True
+            else:
+                    return False
+                    
+            
+    class tempo(GenericEvent):
+        '''A class that encapsulates a tempo meta-event
+        '''
+        def __init__(self,time,tempo):
+            
+            GenericEvent.__init__(self,time)
+            self.type = 'tempo'
+            self.tempo = int(60000000 / tempo)
+            
+    class programChange(GenericEvent):
+        '''A class that encapsulates a program change event.
+        '''
+        
+        def __init__(self,  channel,  time,  programNumber):
+            GenericEvent.__init__(self, time,)
+            self.type = 'programChange'
+            self.programNumber = programNumber
+            self.channel = channel
+            
+    class SysExEvent(GenericEvent):
+        '''A class that encapsulates a System Exclusive  event.
+        '''
+        
+        def __init__(self,  time,  manID,  payload):
+            GenericEvent.__init__(self, time,)
+            self.type = 'SysEx'
+            self.manID = manID
+            self.payload = payload
+            
+    class UniversalSysExEvent(GenericEvent):
+        '''A class that encapsulates a Universal System Exclusive  event.
+        '''
+        
+        def __init__(self,  time,  realTime,  sysExChannel,  code,  subcode,  payload):
+            GenericEvent.__init__(self, time,)
+            self.type = 'UniversalSysEx'
+            self.realTime = realTime
+            self.sysExChannel = sysExChannel
+            self.code = code
+            self.subcode = subcode
+            self.payload = payload
+            
+    class ControllerEvent(GenericEvent):
+        '''A class that encapsulates a program change event.
+        '''
+        
+        def __init__(self,  channel,  time,  eventType,  parameter1,):
+            GenericEvent.__init__(self, time,)
+            self.type = 'controllerEvent'
+            self.parameter1 = parameter1
+            self.channel = channel
+            self.eventType = eventType
+
+    class trackName(GenericEvent):
+        '''A class that encapsulates a program change event.
+        '''
+        
+        def __init__(self,  time,  trackName):
+            GenericEvent.__init__(self, time,)
+            self.type = 'trackName'
+            self.trackName = trackName
+
+            
+    def __init__(self, removeDuplicates,  deinterleave):
+        '''Initialize the MIDITrack object.
+        '''
+        self.headerString = struct.pack('cccc','M','T','r','k')
+        self.dataLength = 0 # Is calculated after the data is in place
+        self.MIDIdata = b""
+        self.closed = False
+        self.eventList = []
+        self.MIDIEventList = []
+        self.remdep = removeDuplicates
+        self.deinterleave = deinterleave
+        
+    def addNoteByNumber(self,channel, pitch,time,duration,volume):
+        '''Add a note by chromatic MIDI number
+        '''
+        self.eventList.append(MIDITrack.note(channel, pitch,time,duration,volume))
+        
+    def addControllerEvent(self,channel,time,eventType, paramerter1):
+        '''
+        Add a controller event.
+        '''
+        
+        self.eventList.append(MIDITrack.ControllerEvent(channel,time,eventType, \
+                                             paramerter1))
+        
+    def addTempo(self,time,tempo):
+        '''
+        Add a tempo change (or set) event.
+        '''
+        self.eventList.append(MIDITrack.tempo(time,tempo))
+        
+    def addSysEx(self,time,manID, payload):
+        '''
+        Add a SysEx event.
+        '''
+        self.eventList.append(MIDITrack.SysExEvent(time, manID,  payload))
+        
+    def addUniversalSysEx(self,time,code, subcode, payload,  sysExChannel=0x7F,  \
+        realTime=False):
+        '''
+        Add a Universal SysEx event.
+        '''
+        self.eventList.append(MIDITrack.UniversalSysExEvent(time, realTime,  \
+            sysExChannel,  code,  subcode, payload))
+        
+    def addProgramChange(self,channel, time, program):
+        '''
+        Add a program change event.
+        '''
+        self.eventList.append(MIDITrack.programChange(channel, time, program))
+        
+    def addTrackName(self,time,trackName):
+        '''
+        Add a track name event.
+        '''
+        self.eventList.append(MIDITrack.trackName(time,trackName))
+        
+    def changeNoteTuning(self,  tunings,   sysExChannel=0x7F,  realTime=False,  \
+        tuningProgam=0):
+        '''Change the tuning of MIDI notes
+        '''
+        payload = struct.pack('>B',  tuningProgam)
+        payload = payload + struct.pack('>B',  len(tunings))
+        for (noteNumber,  frequency) in tunings:
+            payload = payload + struct.pack('>B',  noteNumber)
+            MIDIFreqency = frequencyTransform(frequency)
+            for byte in MIDIFreqency:
+                payload = payload + struct.pack('>B',  byte)
+                
+        self.eventList.append(MIDITrack.UniversalSysExEvent(0, realTime,  sysExChannel,\
+            8,  2, payload))
+    
+    def processEventList(self):
+        '''
+        Process the event list, creating a MIDIEventList
+        
+        For each item in the event list, one or more events in the MIDIEvent
+        list are created.
+        '''
+        
+        # Loop over all items in the eventList
+        
+        for thing in self.eventList:
+            if thing.type == 'note':
+                event = MIDIEvent()
+                event.type = "NoteOn"
+                event.time = thing.time * TICKSPERBEAT
+                event.pitch = thing.pitch
+                event.volume = thing.volume
+                event.channel = thing.channel
+                event.ord = 3
+                self.MIDIEventList.append(event)
+
+                event = MIDIEvent()
+                event.type = "NoteOff"
+                event.time = (thing.time + thing.duration) * TICKSPERBEAT
+                event.pitch = thing.pitch
+                event.volume = thing.volume
+                event.channel = thing.channel
+                event.ord = 2
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'tempo':
+                event = MIDIEvent()
+                event.type = "Tempo"
+                event.time = thing.time * TICKSPERBEAT
+                event.tempo = thing.tempo
+                event.ord = 3
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'programChange':
+                event = MIDIEvent()
+                event.type = "ProgramChange"
+                event.time = thing.time * TICKSPERBEAT
+                event.programNumber = thing.programNumber
+                event.channel = thing.channel
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'trackName':
+                event = MIDIEvent()
+                event.type = "TrackName"
+                event.time = thing.time * TICKSPERBEAT
+                event.trackName = thing.trackName
+                event.ord = 0
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'controllerEvent':
+                event = MIDIEvent()
+                event.type = "ControllerEvent"
+                event.time = thing.time * TICKSPERBEAT
+                event.eventType = thing.eventType
+                event.channel = thing.channel
+                event.paramerter1 = thing.parameter1
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'SysEx':
+                event = MIDIEvent()
+                event.type = "SysEx"
+                event.time = thing.time * TICKSPERBEAT
+                event.manID = thing.manID
+                event.payload = thing.payload
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            elif thing.type == 'UniversalSysEx':
+                event = MIDIEvent()
+                event.type = "UniversalSysEx"
+                event.realTime = thing.realTime
+                event.sysExChannel = thing.sysExChannel
+                event.time = thing.time * TICKSPERBEAT
+                event.code = thing.code
+                event.subcode = thing.subcode
+                event.payload = thing.payload
+                event.ord = 1
+                self.MIDIEventList.append(event)
+
+            else:
+                print ("Error in MIDITrack: Unknown event type")
+                sys.exit(2)
+            
+        # Assumptions in the code expect the list to be time-sorted.
+        # self.MIDIEventList.sort(lambda x, y: x.time - y.time)
+
+        self.MIDIEventList.sort(key=lambda x: (x.time))
+
+        if self.deinterleave:    
+            self.deInterleaveNotes()
+
+    def removeDuplicates(self):
+        '''
+        Remove duplicates from the eventList.
+        
+        This function will remove duplicates from the eventList. This is necessary
+        because we the MIDI event stream can become confused otherwise.
+        '''
+        
+        # For this algorithm to work, the events in the eventList must be hashable 
+        # (that is, they must have a __hash__() and __eq__() function defined).
+        
+        tempDict = {}
+        for item in self.eventList:
+            tempDict[item] = 1
+            
+        self.eventList = list(tempDict.keys())
+        
+        # Sort on type, them on time. Necessary because keys() has no requirement to return
+        # things in any order.
+        
+        self.eventList.sort(key=lambda x: (x.type))
+        self.eventList.sort(key=lambda x: (x.time)) #A bit of a hack.
+
+    def closeTrack(self):