Add some PEP8 happiness

This commit is contained in:
Gergely Polonkai 2016-05-27 17:09:34 +02:00
parent fc5cd7fdbd
commit 489c9c4494

View File

@ -10,17 +10,22 @@
# software is distributed. # software is distributed.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
import struct, sys, math import struct
import sys
import math
# TICKSPERBEAT is the number of "ticks" (time measurement in the MIDI file) that # TICKSPERBEAT is the number of "ticks" (time measurement in the MIDI
# corresponds to one beat. This number is somewhat arbitrary, but should be chosen # file) that corresponds to one beat. This number is somewhat
# to provide adequate temporal resolution. # arbitrary, but should be chosen to provide adequate temporal
# resolution.
TICKSPERBEAT = 128 TICKSPERBEAT = 128
controllerEventTypes = { controllerEventTypes = {
'pan': 0x0a 'pan': 0x0a
} }
class MIDIEvent: class MIDIEvent:
''' '''
The class to contain the MIDI Event (placed on MIDIEventList. The class to contain the MIDI Event (placed on MIDIEventList.
@ -44,36 +49,40 @@ class MIDIEvent:
else: else:
return 0 return 0
class GenericEvent(): class GenericEvent():
'''The event class from which specific events are derived
''' '''
The event class from which specific events are derived
'''
def __init__(self, time): def __init__(self, time):
self.time = time self.time = time
self.type = 'Unknown' self.type = 'Unknown'
def __eq__(self, other): def __eq__(self, other):
''' '''
Equality operator for Generic Events and derived classes. Equality operator for Generic Events and derived classes.
In the processing of the event list, we have need to remove duplicates. To do this In the processing of the event list, we have need to remove
we rely on the fact that the classes are hashable, and must therefore have an duplicates. To do this we rely on the fact that the classes
equality operator (__hash__() and __eq__() must both be defined). 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 This is the most embarrassing portion of the code, and anyone
programming would find this almost unbelievable. Here we have a base class that who knows about OO programming would find this almost
knows specifics about derived classes, thus breaking the very spirit of unbelievable. Here we have a base class that knows specifics
OO programming. 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 I suppose I should go back and restructure the code, perhaps
classes altogether. At some point perhaps I will. removing the derived classes altogether. At some point perhaps
I will.
''' '''
if self.time != other.time or self.type != other.type: if self.time != other.time or self.type != other.type:
return False return False
# What follows is code that encodes the concept of equality for each derived # What follows is code that encodes the concept of equality
# class. Believe it f you dare. # for each derived class. Believe it f you dare.
if self.type == 'note': if self.type == 'note':
if self.pitch != other.pitch or self.channel != other.channel: if self.pitch != other.pitch or self.channel != other.channel:
@ -82,7 +91,8 @@ class GenericEvent():
if self.tempo != other.tempo: if self.tempo != other.tempo:
return False return False
if self.type == 'programChange': if self.type == 'programChange':
if self.programNumber != other.programNumber or self.channel != other.channel: if self.programNumber != other.programNumber or \
self.channel != other.channel:
return False return False
if self.type == 'trackName': if self.type == 'trackName':
if self.trackName != other.trackName: if self.trackName != other.trackName:
@ -92,6 +102,7 @@ class GenericEvent():
self.parameter2 != other.parameter2 or \ self.parameter2 != other.parameter2 or \
self.channel != other.channel or \ self.channel != other.channel or \
self.eventType != other.eventType: self.eventType != other.eventType:
return False return False
if self.type == 'SysEx': if self.type == 'SysEx':
@ -102,6 +113,7 @@ class GenericEvent():
if self.code != other.code or \ if self.code != other.code or \
self.subcode != other.subcode or \ self.subcode != other.subcode or \
self.sysExChannel != other.sysExChannel: self.sysExChannel != other.sysExChannel:
return False return False
return True return True
@ -110,10 +122,13 @@ class GenericEvent():
''' '''
Return a hash code for the object. Return a hash code for the object.
This is needed for the removal of duplicate objects from the event list. The only This is needed for the removal of duplicate objects from the
real requirement for the algorithm is that the hash of equal objects must be equal. event list. The only real requirement for the algorithm is
There is probably great opportunity for improvements in the hashing function. 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. # Robert Jenkin's 32 bit hash.
a = int(self.time) a = int(self.time)
a = (a + 0x7ed55d16) + (a << 12) a = (a + 0x7ed55d16) + (a << 12)
@ -122,8 +137,10 @@ class GenericEvent():
a = (a + 0xd3a2646c) ^ (a << 9) a = (a + 0xd3a2646c) ^ (a << 9)
a = (a + 0xfd7046c5) + (a << 3) a = (a + 0xfd7046c5) + (a << 3)
a = (a ^ 0xb55a4f09) ^ (a >> 16) a = (a ^ 0xb55a4f09) ^ (a >> 16)
return a return a
class MIDITrack: class MIDITrack:
'''A class that encapsulates a MIDI track '''A class that encapsulates a MIDI track
''' '''
@ -132,8 +149,8 @@ class MIDITrack:
class note(GenericEvent): class note(GenericEvent):
'''A class that encapsulates a note '''A class that encapsulates a note
''' '''
def __init__(self,channel, pitch,time,duration,volume):
def __init__(self, channel, pitch, time, duration, volume):
GenericEvent.__init__(self, time) GenericEvent.__init__(self, time)
self.pitch = pitch self.pitch = pitch
self.duration = duration self.duration = duration
@ -154,12 +171,11 @@ class MIDITrack:
else: else:
return False return False
class tempo(GenericEvent): class tempo(GenericEvent):
'''A class that encapsulates a tempo meta-event '''A class that encapsulates a tempo meta-event
''' '''
def __init__(self,time,tempo):
def __init__(self, time, tempo):
GenericEvent.__init__(self, time) GenericEvent.__init__(self, time)
self.type = 'tempo' self.type = 'tempo'
self.tempo = int(60000000 / tempo) self.tempo = int(60000000 / tempo)
@ -188,7 +204,9 @@ class MIDITrack:
'''A class that encapsulates a Universal System Exclusive event. '''A class that encapsulates a Universal System Exclusive event.
''' '''
def __init__(self, time, realTime, sysExChannel, code, subcode, payload): def __init__(self,
time, realTime, sysExChannel,
code, subcode, payload):
GenericEvent.__init__(self, time,) GenericEvent.__init__(self, time,)
self.type = 'UniversalSysEx' self.type = 'UniversalSysEx'
self.realTime = realTime self.realTime = realTime
@ -201,7 +219,7 @@ class MIDITrack:
'''A class that encapsulates a program change event. '''A class that encapsulates a program change event.
''' '''
def __init__(self, channel, time, eventType, parameter1,): def __init__(self, channel, time, eventType, parameter1):
GenericEvent.__init__(self, time,) GenericEvent.__init__(self, time,)
self.type = 'controllerEvent' self.type = 'controllerEvent'
self.parameter1 = parameter1 self.parameter1 = parameter1
@ -217,7 +235,6 @@ class MIDITrack:
self.type = 'trackName' self.type = 'trackName'
self.trackName = trackName self.trackName = trackName
def __init__(self, removeDuplicates, deinterleave): def __init__(self, removeDuplicates, deinterleave):
'''Initialize the MIDITrack object. '''Initialize the MIDITrack object.
''' '''
@ -231,17 +248,18 @@ class MIDITrack:
self.deinterleave = deinterleave self.deinterleave = deinterleave
def addNoteByNumber(self, channel, pitch, time, duration, volume): 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)) Add a note by chromatic MIDI number
'''
self.eventList.append(MIDITrack.note(channel, pitch,
time, duration, volume))
def addControllerEvent(self, channel, time, eventType, paramerter1): def addControllerEvent(self, channel, time, eventType, paramerter1):
''' '''
Add a controller event. Add a controller event.
''' '''
self.eventList.append(
self.eventList.append(MIDITrack.ControllerEvent(channel,time,eventType, \ MIDITrack.ControllerEvent(channel, time, eventType, paramerter1))
paramerter1))
def addTempo(self, time, tempo): def addTempo(self, time, tempo):
''' '''
@ -255,13 +273,14 @@ class MIDITrack:
''' '''
self.eventList.append(MIDITrack.SysExEvent(time, manID, payload)) self.eventList.append(MIDITrack.SysExEvent(time, manID, payload))
def addUniversalSysEx(self,time,code, subcode, payload, sysExChannel=0x7F, \ def addUniversalSysEx(self, time, code, subcode,
realTime=False): payload, sysExChannel=0x7F, realTime=False):
''' '''
Add a Universal SysEx event. Add a Universal SysEx event.
''' '''
self.eventList.append(MIDITrack.UniversalSysExEvent(time, realTime, \ self.eventList.append(
sysExChannel, code, subcode, payload)) MIDITrack.UniversalSysExEvent(time, realTime, sysExChannel,
code, subcode, payload))
def addProgramChange(self, channel, time, program): def addProgramChange(self, channel, time, program):
''' '''
@ -273,21 +292,25 @@ class MIDITrack:
''' '''
Add a track name event. Add a track name event.
''' '''
self.eventList.append(MIDITrack.trackName(time, trackName)) self.eventList.append(MIDITrack.trackName(time, trackName))
def changeNoteTuning(self, tunings, sysExChannel=0x7F, realTime=False, \ def changeNoteTuning(self, tunings, sysExChannel=0x7F,
tuningProgam=0): realTime=False, tuningProgam=0):
'''Change the tuning of MIDI notes '''Change the tuning of MIDI notes
''' '''
payload = struct.pack('>B', tuningProgam) payload = struct.pack('>B', tuningProgam)
payload = payload + struct.pack('>B', len(tunings)) payload = payload + struct.pack('>B', len(tunings))
for (noteNumber, frequency) in tunings: for (noteNumber, frequency) in tunings:
payload = payload + struct.pack('>B', noteNumber) payload = payload + struct.pack('>B', noteNumber)
MIDIFreqency = frequencyTransform(frequency) MIDIFreqency = frequencyTransform(frequency)
for byte in MIDIFreqency: for byte in MIDIFreqency:
payload = payload + struct.pack('>B', byte) payload = payload + struct.pack('>B', byte)
self.eventList.append(MIDITrack.UniversalSysExEvent(0, realTime, sysExChannel,\ self.eventList.append(
MIDITrack.UniversalSysExEvent(0, realTime, sysExChannel,
8, 2, payload)) 8, 2, payload))
def processEventList(self): def processEventList(self):
@ -299,7 +322,6 @@ class MIDITrack:
''' '''
# Loop over all items in the eventList # Loop over all items in the eventList
for thing in self.eventList: for thing in self.eventList:
if thing.type == 'note': if thing.type == 'note':
event = MIDIEvent() event = MIDIEvent()
@ -380,9 +402,6 @@ class MIDITrack:
print "Error in MIDITrack: Unknown event type" print "Error in MIDITrack: Unknown event type"
sys.exit(2) 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))) self.MIDIEventList.sort(lambda x, y: int(1000 * (x.time - y.time)))
if self.deinterleave: if self.deinterleave:
@ -392,12 +411,14 @@ class MIDITrack:
''' '''
Remove duplicates from the eventList. Remove duplicates from the eventList.
This function will remove duplicates from the eventList. This is necessary This function will remove duplicates from the eventList. This
because we the MIDI event stream can become confused otherwise. 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 # For this algorithm to work, the events in the eventList must
# (that is, they must have a __hash__() and __eq__() function defined). # be hashable (that is, they must have a __hash__() and
# __eq__() function defined).
tempDict = {} tempDict = {}
for item in self.eventList: for item in self.eventList:
@ -405,11 +426,13 @@ class MIDITrack:
self.eventList = tempDict.keys() self.eventList = tempDict.keys()
# Sort on type, them on time. Necessary because keys() has no requirement to return # Sort on type, them on time. Necessary because keys() has no
# things in any order. # requirement to return things in any order.
self.eventList.sort(lambda x, y: cmp(x.type, y.type)) 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.
# A bit of a hack.
self.eventList.sort(lambda x, y: int(1000 * (x.time - y.time)))
def closeTrack(self): def closeTrack(self):
'''Called to close a track before writing '''Called to close a track before writing
@ -421,14 +444,14 @@ class MIDITrack:
Called by the parent MIDIFile object. Called by the parent MIDIFile object.
''' '''
if self.closed == True: if self.closed:
return return
self.closed = True self.closed = True
if self.remdep: if self.remdep:
self.removeDuplicates() self.removeDuplicates()
self.processEventList() self.processEventList()
def writeMIDIStream(self): def writeMIDIStream(self):
@ -437,16 +460,12 @@ class MIDITrack:
''' '''
# Process the events in the eventList # Process the events in the eventList
self.writeEventsToStream() self.writeEventsToStream()
# Write MIDI close event. # Write MIDI close event.
self.MIDIdata += struct.pack('BBBB', 0x00, 0xFF, 0x2F, 0x00)
self.MIDIdata = self.MIDIdata + struct.pack('BBBB',0x00,0xFF, \
0x2F,0x00)
# Calculate the entire length of the data and write to the header # Calculate the entire length of the data and write to the header
self.dataLength = struct.pack('>L', len(self.MIDIdata)) self.dataLength = struct.pack('>L', len(self.MIDIdata))
def writeEventsToStream(self): def writeEventsToStream(self):
@ -458,93 +477,120 @@ class MIDITrack:
if event.type == "NoteOn": if event.type == "NoteOn":
code = 0x9 << 4 | event.channel code = 0x9 << 4 | event.channel
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) 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 += struct.pack('>B', code)
self.MIDIdata = self.MIDIdata + struct.pack('>B',event.volume) self.MIDIdata += struct.pack('>B', event.pitch)
self.MIDIdata += struct.pack('>B', event.volume)
elif event.type == "NoteOff": elif event.type == "NoteOff":
code = 0x8 << 4 | event.channel code = 0x8 << 4 | event.channel
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) 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 += struct.pack('>B', code)
self.MIDIdata = self.MIDIdata + struct.pack('>B',event.volume) self.MIDIdata += struct.pack('>B', event.pitch)
self.MIDIdata += struct.pack('>B', event.volume)
elif event.type == "Tempo": elif event.type == "Tempo":
code = 0xFF code = 0xFF
subcode = 0x51 subcode = 0x51
fourbite = struct.pack('>L', event.tempo) fourbite = struct.pack('>L', event.tempo)
threebite = fourbite[1:4] # Just discard the MSB threebite = fourbite[1:4] # Just discard the MSB
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) self.MIDIdata += struct.pack('>B', timeByte)
self.MIDIdata = self.MIDIdata + struct.pack('>B',code)
self.MIDIdata = self.MIDIdata + struct.pack('>B',subcode) self.MIDIdata += struct.pack('>B', code)
self.MIDIdata = self.MIDIdata + struct.pack('>B', 0x03) # Data length: 3 self.MIDIdata += struct.pack('>B', subcode)
self.MIDIdata = self.MIDIdata + threebite
# Data length: 3
self.MIDIdata += struct.pack('>B', 0x03)
self.MIDIdata += threebite
elif event.type == 'ProgramChange': elif event.type == 'ProgramChange':
code = 0xC << 4 | event.channel code = 0xC << 4 | event.channel
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) self.MIDIdata += struct.pack('>B', timeByte)
self.MIDIdata = self.MIDIdata + struct.pack('>B',code)
self.MIDIdata = self.MIDIdata + struct.pack('>B',event.programNumber) self.MIDIdata += struct.pack('>B', code)
self.MIDIdata += struct.pack('>B', event.programNumber)
elif event.type == 'TrackName': elif event.type == 'TrackName':
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) 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 # Meta-event
self.MIDIdata += struct.pack('B', 0xFF)
# Event Type
self.MIDIdata += struct.pack('B', 0X03)
dataLength = len(event.trackName) dataLength = len(event.trackName)
dataLenghtVar = writeVarLength(dataLength) dataLenghtVar = writeVarLength(dataLength)
for i in range(0, len(dataLenghtVar)): for i in range(0, len(dataLenghtVar)):
self.MIDIdata = self.MIDIdata + struct.pack("b",dataLenghtVar[i]) self.MIDIdata += struct.pack("b", dataLenghtVar[i])
self.MIDIdata = self.MIDIdata + event.trackName
self.MIDIdata += event.trackName
elif event.type == "ControllerEvent": elif event.type == "ControllerEvent":
code = 0xB << 4 | event.channel code = 0xB << 4 | event.channel
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) 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 += struct.pack('>B', code)
self.MIDIdata = self.MIDIdata + struct.pack('>B',event.paramerter1) self.MIDIdata += struct.pack('>B', event.eventType)
self.MIDIdata += struct.pack('>B', event.paramerter1)
elif event.type == "SysEx": elif event.type == "SysEx":
code = 0xF0 code = 0xF0
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) self.MIDIdata += struct.pack('>B', timeByte)
self.MIDIdata = self.MIDIdata + struct.pack('>B', code)
self.MIDIdata += struct.pack('>B', code)
payloadLength = writeVarLength(len(event.payload) + 2) 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) for lenByte in payloadLength:
self.MIDIdata = self.MIDIdata + event.payload self.MIDIdata += struct.pack('>B', lenByte)
self.MIDIdata = self.MIDIdata + struct.pack('>B',0xF7)
self.MIDIdata += struct.pack('>B', event.manID)
self.MIDIdata += event.payload
self.MIDIdata += struct.pack('>B', 0xF7)
elif event.type == "UniversalSysEx": elif event.type == "UniversalSysEx":
code = 0xF0 code = 0xF0
varTime = writeVarLength(event.time) varTime = writeVarLength(event.time)
for timeByte in varTime: for timeByte in varTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B',timeByte) self.MIDIdata += struct.pack('>B', timeByte)
self.MIDIdata = self.MIDIdata + struct.pack('>B', code)
self.MIDIdata += struct.pack('>B', code)
# Do we need to add a length? # Do we need to add a length?
payloadLength = writeVarLength(len(event.payload) + 5) payloadLength = writeVarLength(len(event.payload) + 5)
for lenByte in payloadLength: for lenByte in payloadLength:
self.MIDIdata = self.MIDIdata + struct.pack('>B',lenByte) self.MIDIdata += struct.pack('>B', lenByte)
if event.realTime: if event.realTime:
self.MIDIdata = self.MIDIdata + struct.pack('>B', 0x7F) self.MIDIdata += struct.pack('>B', 0x7F)
else: else:
self.MIDIdata = self.MIDIdata + struct.pack('>B', 0x7E) self.MIDIdata += struct.pack('>B', 0x7E)
self.MIDIdata = self.MIDIdata + struct.pack('>B', event.sysExChannel) self.MIDIdata += struct.pack('>B', event.sysExChannel)
self.MIDIdata = self.MIDIdata + struct.pack('>B', event.code) self.MIDIdata += struct.pack('>B', event.code)
self.MIDIdata = self.MIDIdata + struct.pack('>B', event.subcode) self.MIDIdata += struct.pack('>B', event.subcode)
self.MIDIdata = self.MIDIdata + event.payload self.MIDIdata += event.payload
self.MIDIdata = self.MIDIdata + struct.pack('>B',0xF7) self.MIDIdata += struct.pack('>B', 0xF7)
def deInterleaveNotes(self): def deInterleaveNotes(self):
'''Correct Interleaved notes. '''Correct Interleaved notes.
@ -559,16 +605,17 @@ class MIDITrack:
stack = {} stack = {}
for event in self.MIDIEventList: for event in self.MIDIEventList:
if event.type == 'NoteOn': if event.type == 'NoteOn':
if stack.has_key(str(event.pitch)+str(event.channel)): if str(event.pitch) + str(event.channel) in stack:
stack[str(event.pitch)+str(event.channel)].append(event.time) stack[str(event.pitch) + str(event.channel)] \
.append(event.time)
else: else:
stack[str(event.pitch)+str(event.channel)] = [event.time] stack[str(event.pitch)+str(event.channel)] = [event.time]
tempEventList.append(event) tempEventList.append(event)
elif event.type == 'NoteOff': elif event.type == 'NoteOff':
if len(stack[str(event.pitch)+str(event.channel)]) > 1: if len(stack[str(event.pitch)+str(event.channel)]) > 1:
event.time = stack[str(event.pitch)+str(event.channel)].pop() event.time = stack[str(event.pitch) + str(event.channel)] \
.pop()
tempEventList.append(event) tempEventList.append(event)
else: else:
stack[str(event.pitch)+str(event.channel)].pop() stack[str(event.pitch)+str(event.channel)].pop()
@ -578,11 +625,12 @@ class MIDITrack:
self.MIDIEventList = tempEventList self.MIDIEventList = tempEventList
# A little trickery here. We want to make sure that NoteOff events appear # A little trickery here. We want to make sure that NoteOff
# before NoteOn events, so we'll do two sorts -- on on type, one on time. # events appear before NoteOn events, so we'll do two sorts --
# This may have to be revisited, as it makes assumptions about how # on on type, one on time. This may have to be revisited, as
# the internal sort works, and is in essence creating a sort on a primary # it makes assumptions about how the internal sort works, and
# and secondary key. # 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: cmp(x.type, y.type))
self.MIDIEventList.sort(lambda x, y: int(1000 * (x.time - y.time))) self.MIDIEventList.sort(lambda x, y: int(1000 * (x.time - y.time)))
@ -630,8 +678,10 @@ class MIDIHeader:
''' '''
def __init__(self, numTracks): def __init__(self, numTracks):
''' Initialize the data structures
''' '''
Initialize the data structures
'''
self.headerString = struct.pack('cccc', 'M', 'T', 'h', 'd') self.headerString = struct.pack('cccc', 'M', 'T', 'h', 'd')
self.headerSize = struct.pack('>L', 6) self.headerSize = struct.pack('>L', 6)
# Format 1 = multi-track file # Format 1 = multi-track file
@ -639,7 +689,6 @@ class MIDIHeader:
self.numTracks = struct.pack('>H', numTracks) self.numTracks = struct.pack('>H', numTracks)
self.ticksPerBeat = struct.pack('>H', TICKSPERBEAT) self.ticksPerBeat = struct.pack('>H', TICKSPERBEAT)
def writeFile(self, fileHandle): def writeFile(self, fileHandle):
fileHandle.write(self.headerString) fileHandle.write(self.headerString)
fileHandle.write(self.headerSize) fileHandle.write(self.headerSize)
@ -647,8 +696,10 @@ class MIDIHeader:
fileHandle.write(self.numTracks) fileHandle.write(self.numTracks)
fileHandle.write(self.ticksPerBeat) fileHandle.write(self.ticksPerBeat)
class MIDIFile: class MIDIFile:
'''Class that represents a full, well-formed MIDI pattern. '''
Class that represents a full, well-formed MIDI pattern.
This is a container object that contains a header, one or more tracks, 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. and the data associated with a proper and well-formed MIDI pattern.
@ -665,13 +716,15 @@ class MIDIFile:
tracks: The number of tracks this object contains tracks: The number of tracks this object contains
removeDuplicates: If true (the default), the software will remove duplicate removeDuplicates: If true (the default), the software will
events which have been added. For example, two notes at the same channel, remove duplicate events which have been added. For example,
time, pitch, and duration would be considered duplicate. two notes at the same channel, time, pitch, and duration would
be considered duplicate.
deinterleave: If True (the default), overlapping notes (same pitch, same deinterleave: If True (the default), overlapping notes (same
channel) will be modified so that they do not overlap. Otherwise the sequencing pitch, same channel) will be modified so that they do not
software will need to figure out how to interpret NoteOff events upon playback. 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): def __init__(self, numTracks, removeDuplicates=True, deinterleave=True):
@ -687,9 +740,8 @@ class MIDIFile:
for i in range(0, numTracks): for i in range(0, numTracks):
self.tracks.append(MIDITrack(removeDuplicates, deinterleave)) self.tracks.append(MIDITrack(removeDuplicates, deinterleave))
# Public Functions. These (for the most part) wrap the MIDITrack
# Public Functions. These (for the most part) wrap the MIDITrack functions, where most # functions, where most Processing takes place.
# Processing takes place.
def addNote(self, track, channel, pitch, time, duration, volume): def addNote(self, track, channel, pitch, time, duration, volume):
""" """
@ -706,7 +758,8 @@ class MIDIFile:
duration: the duration of the note (in beats) [Float]. duration: the duration of the note (in beats) [Float].
volume: the volume (velocity) of the note. [Integer, 0-127]. volume: the volume (velocity) of the note. [Integer, 0-127].
""" """
self.tracks[track].addNoteByNumber(channel, pitch, time, duration, volume) self.tracks[track].addNoteByNumber(channel, pitch, time,
duration, volume)
def addTrackName(self, track, time, trackName): def addTrackName(self, track, time, trackName):
""" """
@ -756,24 +809,28 @@ class MIDIFile:
Add a MIDI controller event. Add a MIDI controller event.
Use: Use:
MyMIDI.addControllerEvent(track, channel, time, eventType, parameter1) MyMIDI.addControllerEvent(track, channel, time,
eventType, parameter1)
Arguments: Arguments:
track: The track to which the event is added. [Integer, 0-127]. track: The track to which the event is added. [Integer, 0-127].
channel: The channel the event is assigned to. [Integer, 0-15]. channel: The channel the event is assigned to. [Integer, 0-15].
time: The time at which the event is added, in beats. [Float]. time: The time at which the event is added, in beats. [Float].
eventType: the controller event type. eventType: the controller event type.
parameter1: The event's parameter. The meaning of which varies by event type. parameter1: The event's parameter. The meaning of which
varies by event type.
""" """
self.tracks[track].addControllerEvent(channel,time,eventType, paramerter1) self.tracks[track].addControllerEvent(channel, time,
eventType, paramerter1)
def changeNoteTuning(self, track, tunings, sysExChannel=0x7F, \ def changeNoteTuning(self, track, tunings, sysExChannel=0x7F,
realTime=False, tuningProgam=0): realTime=False, tuningProgam=0):
""" """
Change a note's tuning using SysEx change tuning program. Change a note's tuning using SysEx change tuning program.
Use: Use:
MyMIDI.changeNoteTuning(track,[tunings],realTime=False, tuningProgram=0) MyMIDI.changeNoteTuning(track,[tunings],realTime=False,
tuningProgram=0)
Arguments: Arguments:
track: The track to which the event is added. [Integer, 0-127]. track: The track to which the event is added. [Integer, 0-127].
@ -781,14 +838,16 @@ class MIDIFile:
[[(Integer,Float]] [[(Integer,Float]]
realTime: Boolean which sets the real-time flag. Defaults to false. realTime: Boolean which sets the real-time flag. Defaults to false.
sysExChannel: do note use (see below). sysExChannel: do note use (see below).
tuningProgram: Tuning program to assign. Defaults to zero. [Integer, 0-127] tuningProgram: Tuning program to assign. Defaults to
zero. [Integer, 0-127]
In general the sysExChannel should not be changed (parameter will be depreciated). In general the sysExChannel should not be changed (parameter
will be depreciated).
Also note that many software packages and hardware packages do not implement Also note that many software packages and hardware packages do
this standard! not implement this standard!
""" """
self.tracks[track].changeNoteTuning(tunings, sysExChannel, realTime,\ self.tracks[track].changeNoteTuning(tunings, sysExChannel, realTime,
tuningProgam) tuningProgam)
def writeFile(self, fileHandle): def writeFile(self, fileHandle):
@ -804,7 +863,8 @@ class MIDIFile:
self.header.writeFile(fileHandle) self.header.writeFile(fileHandle)
#Close the tracks and have them create the MIDI event data structures. # Close the tracks and have them create the MIDI event data
# structures.
self.close() self.close()
# Write the MIDI Events to file. # Write the MIDI Events to file.
@ -831,7 +891,7 @@ class MIDIFile:
""" """
self.tracks[track].addSysEx(time, manID, payload) self.tracks[track].addSysEx(time, manID, payload)
def addUniversalSysEx(self,track, time,code, subcode, payload, \ def addUniversalSysEx(self, track, time, code, subcode, payload,
sysExChannel=0x7F, realTime=False): sysExChannel=0x7F, realTime=False):
""" """
Add a Universal SysEx event. Add a Universal SysEx event.
@ -849,23 +909,27 @@ class MIDIFile:
sysExChannel: The SysEx channel. sysExChannel: The SysEx channel.
realTime: Sets the real-time flag. Defaults to zero. realTime: Sets the real-time flag. Defaults to zero.
Note: This is a low-level MIDI function, so care must be used in Note: This is a low-level MIDI function, so care must be used
constructing the payload. It is recommended that higher-level helper in constructing the payload. It is recommended that
functions be written to wrap this function and construct the payload if higher-level helper functions be written to wrap this function
a developer finds him or herself using the function heavily. As an example and construct the payload if a developer finds him or herself
of such a helper function, see the changeNoteTuning function, both here and using the function heavily. As an example of such a helper
in MIDITrack. function, see the changeNoteTuning function, both here and in
MIDITrack.
""" """
self.tracks[track].addUniversalSysEx(time,code, subcode, payload, sysExChannel,\ self.tracks[track].addUniversalSysEx(time, code, subcode, payload,
realTime) sysExChannel, realTime)
def shiftTracks(self, offset=0): def shiftTracks(self, offset=0):
"""Shift tracks to be zero-origined, or origined at offset. """
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 Note that the shifting of the time in the tracks uses the
words it is assumed to be called in the stage where the MIDIEventList has been MIDIEventList -- in other words it is assumed to be called in
created. This function, however, it meant to operate on the eventList itself. 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 origin = 1000000 # A little silly, but we'll assume big enough
@ -877,13 +941,10 @@ class MIDIFile:
for track in self.tracks: for track in self.tracks:
tempEventList = [] tempEventList = []
#runningTime = 0
for event in track.eventList: for event in track.eventList:
adjustedTime = event.time - origin adjustedTime = event.time - origin
#event.time = adjustedTime - runningTime + offset
event.time = adjustedTime + offset event.time = adjustedTime + offset
#runningTime = adjustedTime
tempEventList.append(event) tempEventList.append(event)
track.eventList = tempEventList track.eventList = tempEventList
@ -891,19 +952,23 @@ class MIDIFile:
# End Public Functions ######################## # End Public Functions ########################
def close(self): def close(self):
'''Close the MIDIFile for further writing. '''
Close the MIDIFile for further writing.
To close the File for events, we must close the tracks, adjust the time to be To close the File for events, we must close the tracks, adjust
zero-origined, and have the tracks write to their MIDI Stream data structure. the time to be zero-origined, and have the tracks write to
their MIDI Stream data structure.
''' '''
if self.closed == True: if self.closed:
return return
for i in range(0, self.numTracks): for i in range(0, self.numTracks):
self.tracks[i].closeTrack() 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 # 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() self.tracks[i].MIDIEventList.sort()
origin = self.findOrigin() origin = self.findOrigin()
@ -914,31 +979,36 @@ class MIDIFile:
self.closed = True self.closed = True
def findOrigin(self): def findOrigin(self):
'''Find the earliest time in the file's tracks.append. '''
Find the earliest time in the file's tracks.append.
''' '''
origin = 1000000 # A little silly, but we'll assume big enough 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 # Note: This code assumes that the MIDIEventList has been
# before it is called. It is probably a poor design to do this. # sorted, so this should be insured before it is
# TODO: -- Consider making this less efficient but more robust by not assuming the list to be sorted. # 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: for track in self.tracks:
if len(track.MIDIEventList) > 0: if len(track.MIDIEventList) > 0:
if track.MIDIEventList[0].time < origin: if track.MIDIEventList[0].time < origin:
origin = track.MIDIEventList[0].time origin = track.MIDIEventList[0].time
return origin 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 def writeVarLength(i):
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 Accept an input, and write a MIDI-compatible variable length
byte in question is the last in the stream 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) input = int(i)
output = [0, 0, 0, 0] output = [0, 0, 0, 0]
@ -948,6 +1018,7 @@ def writeVarLength(i):
output[count] = result output[count] = result
count = count + 1 count = count + 1
input = input >> 7 input = input >> 7
while input > 0: while input > 0:
result = input & 0x7F result = input & 0x7F
result = result | 0x80 result = result | 0x80
@ -959,20 +1030,26 @@ def writeVarLength(i):
reversed[1] = output[2] reversed[1] = output[2]
reversed[2] = output[1] reversed[2] = output[1]
reversed[3] = output[0] reversed[3] = output[0]
return reversed[4-count:4] return reversed[4-count:4]
def frequencyTransform(freq): def frequencyTransform(freq):
'''Returns a three-byte transform of a frequencyTransform
''' '''
Returns a three-byte transform of a frequencyTransform
'''
resolution = 16384 resolution = 16384
freq = float(freq) freq = float(freq)
dollars = 69 + 12 * math.log(freq/(float(440)), 2) dollars = 69 + 12 * math.log(freq/(float(440)), 2)
firstByte = int(dollars) firstByte = int(dollars)
lowerFreq = 440 * pow(2.0, ((float(firstByte) - 69.0)/12.0)) lowerFreq = 440 * pow(2.0, ((float(firstByte) - 69.0)/12.0))
if freq != lowerFreq: if freq != lowerFreq:
centDif = 1200 * math.log((freq/lowerFreq), 2) centDif = 1200 * math.log((freq/lowerFreq), 2)
else: else:
centDif = 0 centDif = 0
cents = round(centDif/100 * resolution) # round? cents = round(centDif/100 * resolution) # round?
secondByte = min([int(cents) >> 7, 0x7F]) secondByte = min([int(cents) >> 7, 0x7F])
thirdByte = cents - (secondByte << 7) thirdByte = cents - (secondByte << 7)
@ -983,11 +1060,17 @@ def frequencyTransform(freq):
thirdByte = int(thirdByte) thirdByte = int(thirdByte)
return [firstByte, secondByte, thirdByte] return [firstByte, secondByte, thirdByte]
def returnFrequency(freqBytes): def returnFrequency(freqBytes):
'''The reverse of frequencyTransform. Given a byte stream, return a frequency.
''' '''
The reverse of frequencyTransform. Given a byte stream, return a
frequency.
'''
resolution = 16384.0 resolution = 16384.0
baseFrequency = 440 * pow(2.0, (float(freqBytes[0]-69.0)/12.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 frac = (float((int(freqBytes[1]) << 7) +
int(freqBytes[2])) * 100.0) / resolution
frequency = baseFrequency * pow(2.0, frac/1200.0) frequency = baseFrequency * pow(2.0, frac/1200.0)
return frequency return frequency