Add some PEP8 happiness
This commit is contained in:
parent
fc5cd7fdbd
commit
489c9c4494
@ -10,17 +10,22 @@
|
||||
# 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
|
||||
# corresponds to one beat. This number is somewhat arbitrary, but should be chosen
|
||||
# to provide adequate temporal resolution.
|
||||
# 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.
|
||||
@ -44,36 +49,40 @@ class MIDIEvent:
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
class GenericEvent():
|
||||
'''The event class from which specific events are derived
|
||||
'''
|
||||
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).
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
# 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:
|
||||
@ -82,7 +91,8 @@ class GenericEvent():
|
||||
if self.tempo != other.tempo:
|
||||
return False
|
||||
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
|
||||
if self.type == 'trackName':
|
||||
if self.trackName != other.trackName:
|
||||
@ -92,6 +102,7 @@ class GenericEvent():
|
||||
self.parameter2 != other.parameter2 or \
|
||||
self.channel != other.channel or \
|
||||
self.eventType != other.eventType:
|
||||
|
||||
return False
|
||||
|
||||
if self.type == 'SysEx':
|
||||
@ -102,6 +113,7 @@ class GenericEvent():
|
||||
if self.code != other.code or \
|
||||
self.subcode != other.subcode or \
|
||||
self.sysExChannel != other.sysExChannel:
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
@ -110,10 +122,13 @@ class GenericEvent():
|
||||
'''
|
||||
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.
|
||||
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)
|
||||
@ -122,8 +137,10 @@ class GenericEvent():
|
||||
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
|
||||
'''
|
||||
@ -132,8 +149,8 @@ class MIDITrack:
|
||||
class note(GenericEvent):
|
||||
'''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)
|
||||
self.pitch = pitch
|
||||
self.duration = duration
|
||||
@ -154,12 +171,11 @@ class MIDITrack:
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class tempo(GenericEvent):
|
||||
'''A class that encapsulates a tempo meta-event
|
||||
'''
|
||||
def __init__(self,time,tempo):
|
||||
|
||||
def __init__(self, time, tempo):
|
||||
GenericEvent.__init__(self, time)
|
||||
self.type = 'tempo'
|
||||
self.tempo = int(60000000 / tempo)
|
||||
@ -188,7 +204,9 @@ class MIDITrack:
|
||||
'''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,)
|
||||
self.type = 'UniversalSysEx'
|
||||
self.realTime = realTime
|
||||
@ -201,7 +219,7 @@ class MIDITrack:
|
||||
'''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,)
|
||||
self.type = 'controllerEvent'
|
||||
self.parameter1 = parameter1
|
||||
@ -217,7 +235,6 @@ class MIDITrack:
|
||||
self.type = 'trackName'
|
||||
self.trackName = trackName
|
||||
|
||||
|
||||
def __init__(self, removeDuplicates, deinterleave):
|
||||
'''Initialize the MIDITrack object.
|
||||
'''
|
||||
@ -231,17 +248,18 @@ class MIDITrack:
|
||||
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))
|
||||
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))
|
||||
self.eventList.append(
|
||||
MIDITrack.ControllerEvent(channel, time, eventType, paramerter1))
|
||||
|
||||
def addTempo(self, time, tempo):
|
||||
'''
|
||||
@ -255,13 +273,14 @@ class MIDITrack:
|
||||
'''
|
||||
self.eventList.append(MIDITrack.SysExEvent(time, manID, payload))
|
||||
|
||||
def addUniversalSysEx(self,time,code, subcode, payload, sysExChannel=0x7F, \
|
||||
realTime=False):
|
||||
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))
|
||||
self.eventList.append(
|
||||
MIDITrack.UniversalSysExEvent(time, realTime, sysExChannel,
|
||||
code, subcode, payload))
|
||||
|
||||
def addProgramChange(self, channel, time, program):
|
||||
'''
|
||||
@ -273,21 +292,25 @@ class MIDITrack:
|
||||
'''
|
||||
Add a track name event.
|
||||
'''
|
||||
|
||||
self.eventList.append(MIDITrack.trackName(time, trackName))
|
||||
|
||||
def changeNoteTuning(self, tunings, sysExChannel=0x7F, realTime=False, \
|
||||
tuningProgam=0):
|
||||
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,\
|
||||
self.eventList.append(
|
||||
MIDITrack.UniversalSysExEvent(0, realTime, sysExChannel,
|
||||
8, 2, payload))
|
||||
|
||||
def processEventList(self):
|
||||
@ -299,7 +322,6 @@ class MIDITrack:
|
||||
'''
|
||||
|
||||
# Loop over all items in the eventList
|
||||
|
||||
for thing in self.eventList:
|
||||
if thing.type == 'note':
|
||||
event = MIDIEvent()
|
||||
@ -380,9 +402,6 @@ class MIDITrack:
|
||||
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:
|
||||
@ -392,12 +411,14 @@ class MIDITrack:
|
||||
'''
|
||||
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.
|
||||
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).
|
||||
# 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:
|
||||
@ -405,11 +426,13 @@ class MIDITrack:
|
||||
|
||||
self.eventList = tempDict.keys()
|
||||
|
||||
# Sort on type, them on time. Necessary because keys() has no requirement to return
|
||||
# things in any order.
|
||||
# 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.
|
||||
|
||||
# A bit of a hack.
|
||||
self.eventList.sort(lambda x, y: int(1000 * (x.time - y.time)))
|
||||
|
||||
def closeTrack(self):
|
||||
'''Called to close a track before writing
|
||||
@ -421,14 +444,14 @@ class MIDITrack:
|
||||
Called by the parent MIDIFile object.
|
||||
'''
|
||||
|
||||
if self.closed == True:
|
||||
if self.closed:
|
||||
return
|
||||
|
||||
self.closed = True
|
||||
|
||||
if self.remdep:
|
||||
self.removeDuplicates()
|
||||
|
||||
|
||||
self.processEventList()
|
||||
|
||||
def writeMIDIStream(self):
|
||||
@ -437,16 +460,12 @@ class MIDITrack:
|
||||
'''
|
||||
|
||||
# Process the events in the eventList
|
||||
|
||||
self.writeEventsToStream()
|
||||
|
||||
# Write MIDI close event.
|
||||
|
||||
self.MIDIdata = self.MIDIdata + struct.pack('BBBB',0x00,0xFF, \
|
||||
0x2F,0x00)
|
||||
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):
|
||||
@ -458,93 +477,120 @@ class MIDITrack:
|
||||
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)
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
self.MIDIdata += struct.pack('>B', code)
|
||||
self.MIDIdata += struct.pack('>B', event.pitch)
|
||||
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)
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
self.MIDIdata += struct.pack('>B', code)
|
||||
self.MIDIdata += struct.pack('>B', event.pitch)
|
||||
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
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
self.MIDIdata += struct.pack('>B', code)
|
||||
self.MIDIdata += struct.pack('>B', subcode)
|
||||
|
||||
# Data length: 3
|
||||
self.MIDIdata += struct.pack('>B', 0x03)
|
||||
|
||||
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)
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
self.MIDIdata += struct.pack('>B', code)
|
||||
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
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
# Meta-event
|
||||
self.MIDIdata += struct.pack('B', 0xFF)
|
||||
|
||||
# Event Type
|
||||
self.MIDIdata += struct.pack('B', 0X03)
|
||||
|
||||
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
|
||||
self.MIDIdata += struct.pack("b", dataLenghtVar[i])
|
||||
|
||||
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)
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
self.MIDIdata += struct.pack('>B', code)
|
||||
self.MIDIdata += struct.pack('>B', event.eventType)
|
||||
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)
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
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)
|
||||
for lenByte in payloadLength:
|
||||
self.MIDIdata += struct.pack('>B', lenByte)
|
||||
|
||||
self.MIDIdata += struct.pack('>B', event.manID)
|
||||
self.MIDIdata += event.payload
|
||||
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)
|
||||
self.MIDIdata += struct.pack('>B', timeByte)
|
||||
|
||||
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)
|
||||
self.MIDIdata += struct.pack('>B', lenByte)
|
||||
|
||||
if event.realTime:
|
||||
self.MIDIdata = self.MIDIdata + struct.pack('>B', 0x7F)
|
||||
self.MIDIdata += struct.pack('>B', 0x7F)
|
||||
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 = 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)
|
||||
self.MIDIdata += struct.pack('>B', event.sysExChannel)
|
||||
self.MIDIdata += struct.pack('>B', event.code)
|
||||
self.MIDIdata += struct.pack('>B', event.subcode)
|
||||
self.MIDIdata += event.payload
|
||||
self.MIDIdata += struct.pack('>B', 0xF7)
|
||||
|
||||
def deInterleaveNotes(self):
|
||||
'''Correct Interleaved notes.
|
||||
@ -559,16 +605,17 @@ class MIDITrack:
|
||||
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)
|
||||
if str(event.pitch) + str(event.channel) in stack:
|
||||
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()
|
||||
event.time = stack[str(event.pitch) + str(event.channel)] \
|
||||
.pop()
|
||||
tempEventList.append(event)
|
||||
else:
|
||||
stack[str(event.pitch)+str(event.channel)].pop()
|
||||
@ -578,11 +625,12 @@ class MIDITrack:
|
||||
|
||||
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.
|
||||
# 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)))
|
||||
@ -630,8 +678,10 @@ class MIDIHeader:
|
||||
|
||||
'''
|
||||
def __init__(self, numTracks):
|
||||
''' Initialize the data structures
|
||||
'''
|
||||
Initialize the data structures
|
||||
'''
|
||||
|
||||
self.headerString = struct.pack('cccc', 'M', 'T', 'h', 'd')
|
||||
self.headerSize = struct.pack('>L', 6)
|
||||
# Format 1 = multi-track file
|
||||
@ -639,7 +689,6 @@ class MIDIHeader:
|
||||
self.numTracks = struct.pack('>H', numTracks)
|
||||
self.ticksPerBeat = struct.pack('>H', TICKSPERBEAT)
|
||||
|
||||
|
||||
def writeFile(self, fileHandle):
|
||||
fileHandle.write(self.headerString)
|
||||
fileHandle.write(self.headerSize)
|
||||
@ -647,8 +696,10 @@ class MIDIHeader:
|
||||
fileHandle.write(self.numTracks)
|
||||
fileHandle.write(self.ticksPerBeat)
|
||||
|
||||
|
||||
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,
|
||||
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
|
||||
|
||||
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.
|
||||
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.
|
||||
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):
|
||||
@ -687,9 +740,8 @@ class MIDIFile:
|
||||
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.
|
||||
# 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):
|
||||
"""
|
||||
@ -706,7 +758,8 @@ class MIDIFile:
|
||||
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)
|
||||
self.tracks[track].addNoteByNumber(channel, pitch, time,
|
||||
duration, volume)
|
||||
|
||||
def addTrackName(self, track, time, trackName):
|
||||
"""
|
||||
@ -756,24 +809,28 @@ class MIDIFile:
|
||||
Add a MIDI controller event.
|
||||
|
||||
Use:
|
||||
MyMIDI.addControllerEvent(track, channel, time, eventType, parameter1)
|
||||
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.
|
||||
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):
|
||||
"""
|
||||
Change a note's tuning using SysEx change tuning program.
|
||||
|
||||
Use:
|
||||
MyMIDI.changeNoteTuning(track,[tunings],realTime=False, tuningProgram=0)
|
||||
MyMIDI.changeNoteTuning(track,[tunings],realTime=False,
|
||||
tuningProgram=0)
|
||||
|
||||
Arguments:
|
||||
track: The track to which the event is added. [Integer, 0-127].
|
||||
@ -781,14 +838,16 @@ class MIDIFile:
|
||||
[[(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]
|
||||
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
|
||||
this standard!
|
||||
Also note that many software packages and hardware packages do
|
||||
not implement this standard!
|
||||
"""
|
||||
self.tracks[track].changeNoteTuning(tunings, sysExChannel, realTime,\
|
||||
self.tracks[track].changeNoteTuning(tunings, sysExChannel, realTime,
|
||||
tuningProgam)
|
||||
|
||||
def writeFile(self, fileHandle):
|
||||
@ -804,7 +863,8 @@ class MIDIFile:
|
||||
|
||||
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()
|
||||
|
||||
# Write the MIDI Events to file.
|
||||
@ -831,7 +891,7 @@ class MIDIFile:
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Add a Universal SysEx event.
|
||||
@ -849,23 +909,27 @@ class MIDIFile:
|
||||
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.
|
||||
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)
|
||||
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.
|
||||
"""
|
||||
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.
|
||||
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
|
||||
|
||||
@ -877,13 +941,10 @@ class MIDIFile:
|
||||
|
||||
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
|
||||
@ -891,19 +952,23 @@ class MIDIFile:
|
||||
# End Public Functions ########################
|
||||
|
||||
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
|
||||
zero-origined, and have the tracks write to their MIDI Stream data structure.
|
||||
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:
|
||||
if self.closed:
|
||||
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
|
||||
|
||||
# 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()
|
||||
@ -914,31 +979,36 @@ class MIDIFile:
|
||||
|
||||
self.closed = True
|
||||
|
||||
|
||||
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
|
||||
|
||||
# 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.
|
||||
# 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
|
||||
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]
|
||||
@ -948,6 +1018,7 @@ def writeVarLength(i):
|
||||
output[count] = result
|
||||
count = count + 1
|
||||
input = input >> 7
|
||||
|
||||
while input > 0:
|
||||
result = input & 0x7F
|
||||
result = result | 0x80
|
||||
@ -959,20 +1030,26 @@ def writeVarLength(i):
|
||||
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
|
||||
'''
|
||||
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)
|
||||
@ -983,11 +1060,17 @@ def frequencyTransform(freq):
|
||||
thirdByte = int(thirdByte)
|
||||
return [firstByte, secondByte, thirdByte]
|
||||
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
return frequency
|
||||
|
Loading…
Reference in New Issue
Block a user