Home | History | Annotate | Download | only in JetCreator
      1 """
      2  File:  
      3  midifile.py
      4  
      5  Contents and purpose:
      6  Utilities used throughout JetCreator
      7  
      8  Copyright (c) 2008 Android Open Source Project
      9  
     10  Licensed under the Apache License, Version 2.0 (the "License");
     11  you may not use this file except in compliance with the License.
     12  You may obtain a copy of the License at
     13  
     14       http://www.apache.org/licenses/LICENSE-2.0
     15  
     16  Unless required by applicable law or agreed to in writing, software
     17  distributed under the License is distributed on an "AS IS" BASIS,
     18  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     19  See the License for the specific language governing permissions and
     20  limitations under the License.
     21 """
     22 
     23 import logging
     24 import struct
     25 import copy
     26 import array
     27 
     28 # JET events

     29 JET_EVENT_MARKER = 102
     30 JET_MARKER_LOOP_END = 0
     31 JET_EVENT_TRIGGER_CLIP = 103
     32 
     33 # header definitions

     34 SMF_HEADER_FMT = '>4slHHH'
     35 SMF_RIFF_TAG = 'MThd'
     36 
     37 SMF_TRACK_HEADER_FMT = '>4sl'
     38 SMF_TRACK_RIFF_TAG = 'MTrk'
     39 
     40 # defaults

     41 DEFAULT_PPQN = 120
     42 DEFAULT_BEATS_PER_MEASURE = 4
     43 DEFAULT_TIME_FORMAT = '%03d:%02d:%03d'
     44 
     45 # force note-offs to end of list

     46 MAX_SEQ_NUM = 0x7fffffff
     47 
     48 # MIDI messages

     49 NOTE_OFF = 0x80
     50 NOTE_ON = 0x90
     51 POLY_KEY_PRESSURE = 0xa0
     52 CONTROL_CHANGE = 0xb0
     53 PROGRAM_CHANGE = 0xc0
     54 CHANNEL_PRESSURE = 0xd0
     55 PITCH_BEND = 0xe0
     56 
     57 # System common messages

     58 SYSEX = 0xf0
     59 MIDI_TIME_CODE = 0xf1
     60 SONG_POSITION_POINTER = 0xf2
     61 SONG_SELECT = 0xf3
     62 RESERVED_F4 = 0xf4
     63 RESERVED_F5 = 0xf5
     64 TUNE_REQUEST = 0xf6
     65 END_SYSEX = 0xf7
     66 
     67 # System real-time messages

     68 TIMING_CLOCK = 0xf8
     69 RESERVED_F9 = 0xf9
     70 START = 0xfa
     71 CONTINUE = 0xfb
     72 STOP = 0xfc
     73 RESERVED_FD = 0xfd
     74 ACTIVE_SENSING = 0xfe
     75 SYSTEM_RESET = 0xff
     76 
     77 ONE_BYTE_MESSAGES = (
     78 	TUNE_REQUEST,
     79 	TIMING_CLOCK, 
     80 	RESERVED_F9, 
     81 	START, 
     82 	CONTINUE, 
     83 	STOP,
     84 	RESERVED_FD,
     85 	ACTIVE_SENSING,
     86 	SYSTEM_RESET)
     87 
     88 THREE_BYTE_MESSAGES = (
     89 	NOTE_OFF, 
     90 	NOTE_ON, 
     91 	POLY_KEY_PRESSURE, 
     92 	CONTROL_CHANGE, 
     93 	PITCH_BEND)
     94 
     95 MIDI_MESSAGES = (
     96 	NOTE_OFF, 
     97 	NOTE_ON, 
     98 	POLY_KEY_PRESSURE, 
     99 	CONTROL_CHANGE, 
    100 	CHANNEL_PRESSURE, 
    101 	PITCH_BEND, 
    102 	SYSEX)
    103 
    104 # Meta-events

    105 META_EVENT = 0xff
    106 META_EVENT_SEQUENCE_NUMBER = 0x00
    107 META_EVENT_TEXT_EVENT = 0x01
    108 META_EVENT_COPYRIGHT_NOTICE = 0x02
    109 META_EVENT_SEQUENCE_TRACK_NAME = 0x03
    110 META_EVENT_INSTRUMENT_NAME = 0x04
    111 META_EVENT_LYRIC = 0x05
    112 META_EVENT_MARKER = 0x06
    113 META_EVENT_CUE_POINT = 0x07
    114 META_EVENT_MIDI_CHANNEL_PREFIX = 0x20
    115 META_EVENT_END_OF_TRACK = 0x2f
    116 META_EVENT_SET_TEMPO = 0x51
    117 META_EVENT_SMPTE_OFFSET = 0x54
    118 META_EVENT_TIME_SIGNATURE = 0x58
    119 META_EVENT_KEY_SIGNATURE = 0x59
    120 META_EVENT_SEQUENCER_SPECIFIC = 0x7f
    121 
    122 # recurring error messages

    123 MSG_NOT_SMF_FILE = 'Not an SMF file - aborting parse!'
    124 MSG_INVALID_TRACK_HEADER = 'Track header is invalid'
    125 MSG_TYPE_MISMATCH = 'msg_type does not match event type'
    126 
    127 LARGE_TICK_WARNING = 1000
    128 
    129 # default control values

    130 CTRL_BANK_SELECT_MSB = 0
    131 CTRL_MOD_WHEEL = 1
    132 CTRL_RPN_DATA_MSB = 6
    133 CTRL_VOLUME = 7
    134 CTRL_PAN = 10
    135 CTRL_EXPRESSION = 11
    136 CTRL_BANK_SELECT_LSB = 32
    137 CTRL_RPN_DATA_LSB = 38
    138 CTRL_SUSTAIN = 64
    139 CTRL_RPN_LSB = 100
    140 CTRL_RPN_MSB = 101
    141 CTRL_RESET_CONTROLLERS = 121
    142 
    143 RPN_PITCH_BEND_SENSITIVITY = 0
    144 RPN_FINE_TUNING = 1
    145 RPN_COARSE_TUNING = 2
    146 
    147 MONITOR_CONTROLLERS = (
    148 	CTRL_BANK_SELECT_MSB, 
    149 	CTRL_MOD_WHEEL, 
    150 	CTRL_RPN_DATA_MSB,
    151 	CTRL_VOLUME, 
    152 	CTRL_PAN, 
    153 	CTRL_EXPRESSION, 
    154 	CTRL_BANK_SELECT_LSB,
    155 	CTRL_RPN_DATA_LSB,
    156 	CTRL_SUSTAIN,
    157 	CTRL_RPN_LSB,
    158 	CTRL_RPN_MSB)
    159 
    160 MONITOR_RPNS = (
    161 	RPN_PITCH_BEND_SENSITIVITY,
    162 	RPN_FINE_TUNING,
    163 	RPN_COARSE_TUNING)
    164 
    165 RPN_PITCH_BEND_SENSITIVITY = 0
    166 RPN_FINE_TUNING = 1
    167 RPN_COARSE_TUNING = 2
    168 
    169 DEFAULT_CONTROLLER_VALUES = {
    170 	CTRL_BANK_SELECT_MSB : 121,
    171 	CTRL_MOD_WHEEL : 0,
    172 	CTRL_RPN_DATA_MSB : 0,
    173 	CTRL_VOLUME : 100,
    174 	CTRL_PAN : 64,
    175 	CTRL_EXPRESSION : 127,
    176 	CTRL_RPN_DATA_LSB : 0,
    177 	CTRL_BANK_SELECT_LSB : 0,
    178 	CTRL_SUSTAIN : 0,
    179 	CTRL_RPN_LSB : 0x7f,
    180 	CTRL_RPN_MSB : 0x7f}
    181 
    182 DEFAULT_RPN_VALUES = {
    183 	RPN_PITCH_BEND_SENSITIVITY : 0x100,
    184 	RPN_FINE_TUNING : 0,
    185 	RPN_COARSE_TUNING : 1}
    186 
    187 # initialize logger

    188 midi_file_logger = logging.getLogger('MIDI_file')
    189 midi_file_logger.setLevel(logging.NOTSET)
    190 
    191 
    192 class trackGrid(object):
    193 	def __init__ (self, track, channel, name, empty):
    194 		self.track = track
    195 		self.channel = channel
    196 		self.name = name
    197 		self.empty = empty
    198 	def __str__ (self):
    199 		return "['%s', '%s', '%s']" % (self.track, self.channel, self.name)
    200 		
    201 
    202 #---------------------------------------------------------------

    203 # MIDIFileException

    204 #---------------------------------------------------------------

    205 class MIDIFileException (Exception):
    206 	def __init__ (self, stream, msg):
    207 		stream.error_loc = stream.tell()
    208 		self.stream = stream
    209 		self.msg = msg
    210 	def __str__ (self):
    211 		return '[%d]: %s' % (self.stream.error_loc, self.msg)
    212 
    213 #---------------------------------------------------------------

    214 # TimeBase

    215 #---------------------------------------------------------------

    216 class TimeBase (object):
    217 	def __init__ (self, ppqn=DEFAULT_PPQN, beats_per_measure=DEFAULT_BEATS_PER_MEASURE):
    218 		self.ppqn = ppqn
    219 		self.beats_per_measure = beats_per_measure
    220 
    221 	def ConvertToTicks (self, measures, beats, ticks):
    222 		total_beats = beats + (measures * self.beats_per_measure)
    223 		total_ticks = ticks + (total_beats * self.ppqn)
    224 		return total_ticks
    225 
    226 	def ConvertTicksToMBT (self, ticks):
    227 		beats = ticks / self.ppqn
    228 		ticks -= beats * self.ppqn
    229 		measures = beats / self.beats_per_measure
    230 		beats -= measures * self.beats_per_measure
    231 		return (measures, beats, ticks)
    232 
    233 	def ConvertTicksToStr (self, ticks, format=DEFAULT_TIME_FORMAT):
    234 		measures, beats, ticks = self.ConvertTicksToMBT(ticks)
    235 		return format % (measures, beats, ticks)
    236 
    237 	def ConvertStrTimeToTuple(self, s):
    238 		try:
    239 			measures, beats, ticks = s.split(':',3)
    240 			return (int(measures), int(beats), int(ticks))
    241 		except:
    242 			return (0,0,0)
    243 
    244 	def ConvertStrTimeToTicks(self, s):
    245 		measures, beats, ticks = self.ConvertStrTimeToTuple(s)
    246 		return self.ConvertToTicks(measures, beats, ticks)
    247 	
    248 	def MbtDifference(self, mbt1, mbt2):
    249 		t1 = self.ConvertToTicks(mbt1[0], mbt1[1], mbt1[2])
    250 		t2 = self.ConvertToTicks(mbt2[0], mbt2[1], mbt2[2])
    251 		return abs(t1-t2)
    252 	
    253 		
    254 #---------------------------------------------------------------

    255 # Helper functions

    256 #---------------------------------------------------------------

    257 def ReadByte (stream):
    258 	try:
    259 		return ord(stream.read(1))
    260 	except TypeError:
    261 		stream.error_loc = stream.tell()
    262 		raise MIDIFileException(stream, 'Unexpected EOF')
    263 
    264 def ReadBytes (stream, length):
    265 	bytes = []
    266 	for i in range(length):
    267 		bytes.append(ReadByte(stream))
    268 	return bytes
    269 
    270 def ReadVarLenQty (stream):
    271 	value = 0
    272 	while 1:
    273 		byte = ReadByte(stream)
    274 		value = (value << 7) + (byte & 0x7f)
    275 		if byte & 0x80 == 0:
    276 			return value
    277 
    278 def WriteByte (stream, value):
    279 	stream.write(chr(value))
    280 	
    281 def WriteBytes (stream, bytes):
    282 	for byte in bytes:
    283 		WriteByte(stream, byte)			
    284 	
    285 def WriteVarLenQty (stream, value):
    286 	bytes = [value & 0x7f]
    287 	value = value >> 7
    288 	while value > 0:
    289 		bytes.append((value & 0x7f) | 0x80)
    290 		value = value >> 7
    291 	bytes.reverse()
    292 	WriteBytes(stream, bytes)
    293 
    294 #---------------------------------------------------------------

    295 # EventFilter

    296 #---------------------------------------------------------------

    297 class EventFilter (object):
    298 	pass
    299 
    300 class EventTypeFilter (object):
    301 	def __init__ (self, events, exclude=True):
    302 		self.events = events
    303 		self.exclude = exclude
    304 	def Check (self, event):
    305 		if event.msg_type in self.events:
    306 			return not self.exclude
    307 		return self.exclude
    308 	
    309 class NoteFilter (EventFilter):
    310 	def __init__ (self, notes, exclude=True):
    311 		self.notes = notes
    312 		self.exclude = exclude
    313 	def Check (self, event):
    314 		if event.msg_type in (NOTE_ON, NOTE_OFF):
    315 			if event.note in self.notes:
    316 				return not self.exclude
    317 		return self.exclude
    318 
    319 class ChannelFilter (EventFilter):
    320 	def __init__ (self, channel, exclude=True):
    321 		self.channel = channel
    322 		self.exclude = exclude
    323 	def Check (self, event):
    324 		if event.msg_type in (NOTE_ON, NOTE_OFF, POLY_KEY_PRESSURE, CONTROL_CHANGE, CHANNEL_PRESSURE, PITCH_BEND):
    325 			if event.channel in self.channel:
    326 				return not self.exclude
    327 		return self.exclude
    328 
    329 #---------------------------------------------------------------

    330 # MIDIEvent

    331 #---------------------------------------------------------------

    332 class MIDIEvent (object):
    333 	"""Factory for creating MIDI events from a stream."""
    334 	@staticmethod
    335 	def ReadFromStream (stream, seq, ticks, msg_type):
    336 		if msg_type == SYSEX:
    337 			return SysExEvent.ReadFromStream(stream, seq, ticks, msg_type)
    338 		elif msg_type == END_SYSEX:
    339 			return SysExContEvent.ReadFromStream(stream, seq, ticks, msg_type)
    340 		elif msg_type == META_EVENT:
    341 			return MetaEvent.ReadFromStream(stream, seq, ticks, msg_type)
    342 		else:
    343 			high_nibble = msg_type & 0xf0
    344 			if high_nibble == NOTE_OFF:
    345 				return NoteOffEvent.ReadFromStream(stream, seq, ticks, msg_type)
    346 			elif high_nibble == NOTE_ON:
    347 				return NoteOnEvent.ReadFromStream(stream, seq, ticks, msg_type)
    348 			elif high_nibble == POLY_KEY_PRESSURE:
    349 				return PolyKeyPressureEvent.ReadFromStream(stream, seq, ticks, msg_type)
    350 			elif high_nibble == CONTROL_CHANGE:
    351 				return ControlChangeEvent.ReadFromStream(stream, seq, ticks, msg_type)
    352 			elif high_nibble == PROGRAM_CHANGE:
    353 				return ProgramChangeEvent.ReadFromStream(stream, seq, ticks, msg_type)
    354 			elif high_nibble == CHANNEL_PRESSURE:
    355 				return ChannelPressureEvent.ReadFromStream(stream, seq, ticks, msg_type)
    356 			elif high_nibble == PITCH_BEND:
    357 				return PitchBendEvent.ReadFromStream(stream, seq, ticks, msg_type)
    358 			else:
    359 				stream.Warning('Ignoring unexpected message type 0x%02x' % msg_type)
    360 	def WriteTicks (self, stream, track):
    361 		WriteVarLenQty(stream, self.ticks - track.ticks)
    362 		track.ticks = self.ticks
    363 	def WriteRunningStatus (self, stream, track, filters, msg, data1, data2=None):
    364 		if not self.CheckFilters(filters):
    365 			return
    366 		self.WriteTicks(stream, track)
    367 		status = msg + self.channel
    368 		if track.running_status != status:
    369 			WriteByte(stream, status)
    370 			track.running_status = status
    371 		WriteByte(stream, data1)
    372 		if data2 is not None:
    373 			WriteByte(stream, data2)
    374 	def CheckFilters (self, filters):
    375 		if filters is None or not len(filters):
    376 			return True
    377 			
    378 		# never filter meta-events

    379 		if (self.msg_type == META_EVENT) and (self.meta_type == META_EVENT_END_OF_TRACK):
    380 			return True
    381 
    382 		# check all filters 

    383 		for f in filters:
    384 			if not f.Check(self):
    385 				return False
    386 		return True
    387 
    388 	def TimeEventStr (self, timebase):
    389 		return '[%s]: %s' % (timebase.ConvertTicksToStr(self.ticks), self.__str__())
    390 
    391 #---------------------------------------------------------------

    392 # NoteOffEvent

    393 #---------------------------------------------------------------

    394 class NoteOffEvent (MIDIEvent):
    395 	def __init__ (self, ticks, seq, channel, note, velocity):
    396 		self.name = 'NoteOff'
    397 		self.msg_type = NOTE_OFF
    398 		self.seq = seq
    399 		self.ticks = ticks
    400 		self.channel = channel
    401 		self.note = note
    402 		self.velocity = velocity
    403 	@staticmethod
    404 	def ReadFromStream (stream, seq, ticks, msg_type):
    405 		ticks = ticks
    406 		channel = msg_type & 0x0f
    407 		note = ReadByte(stream)
    408 		velocity = ReadByte(stream)
    409 		if msg_type & 0xf0 != NOTE_OFF:
    410 			stream.seek(-2,1)
    411 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    412 		return NoteOffEvent(ticks, seq, channel, note, velocity)
    413 	def WriteToStream (self, stream, track, filters=None):
    414 		# special case for note-off using zero velocity

    415 		if self.velocity > 0:
    416 			self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
    417 		if track.running_status == (NOTE_OFF + self.channel):
    418 			self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
    419 		else:
    420 			self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, 0)
    421 	def __str__ (self):
    422 		return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.velocity)
    423 
    424 #---------------------------------------------------------------

    425 # NoteOnEvent

    426 #---------------------------------------------------------------

    427 class NoteOnEvent (MIDIEvent):
    428 	def __init__ (self, ticks, seq, channel, note, velocity, note_length, note_off_velocity):
    429 		self.name = 'NoteOn'
    430 		self.msg_type = NOTE_ON
    431 		self.ticks = ticks
    432 		self.seq = seq
    433 		self.channel = channel
    434 		self.note = note
    435 		self.velocity = velocity
    436 		self.note_length = note_length
    437 		self.note_off_velocity = note_off_velocity
    438 	@staticmethod
    439 	def ReadFromStream (stream, seq, ticks, msg_type):
    440 		channel = msg_type & 0x0f
    441 		note = ReadByte(stream)
    442 		velocity = ReadByte(stream)
    443 		if msg_type & 0xf0 != NOTE_ON:
    444 			stream.seek(-2,1)
    445 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    446 		if velocity == 0:
    447 			return NoteOffEvent(ticks, seq, channel, note, velocity)
    448 		return NoteOnEvent(ticks, seq, channel, note, velocity, None, None)
    449 	def WriteToStream (self, stream, track, filters=None):
    450 		self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
    451 	def __str__ (self):
    452 		if self.note_length is not None:
    453 			return '%s: ch=%d n=%d v=%d l=%d' % (self.name, self.channel, self.note, self.velocity, self.note_length)
    454 		else:
    455 			return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.velocity)
    456 
    457 #---------------------------------------------------------------

    458 # PolyKeyPressureEvent

    459 #---------------------------------------------------------------

    460 class PolyKeyPressureEvent (MIDIEvent):
    461 	def __init__ (self, ticks, seq, channel, note, value):
    462 		self.name = 'PolyKeyPressure'
    463 		self.msg_type = POLY_KEY_PRESSURE
    464 		self.ticks = ticks
    465 		self.seq = seq
    466 		self.channel = channel
    467 		self.note = note
    468 		self.value = value
    469 	@staticmethod
    470 	def ReadFromStream (stream, seq, ticks, msg_type):
    471 		channel = msg_type & 0x0f
    472 		note = ReadByte(stream)
    473 		value = ReadByte(stream)
    474 		if msg_type & 0xf0 != POLY_KEY_PRESSURE:
    475 			stream.seek(-2,1)
    476 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    477 		return PolyKeyPressureEvent(ticks, seq, channel, note, value)
    478 	def WriteToStream (self, stream, track, filters=None):
    479 		self.WriteRunningStatus(stream, track, filters, POLY_KEY_PRESSURE, self.note, self.value)
    480 	def __str__ (self):
    481 		return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.value)
    482 
    483 #---------------------------------------------------------------

    484 # ControlChangeEvent

    485 #---------------------------------------------------------------

    486 class ControlChangeEvent (MIDIEvent):
    487 	def __init__ (self, ticks, seq, channel, controller, value):
    488 		self.name = 'ControlChange'
    489 		self.msg_type = CONTROL_CHANGE
    490 		self.ticks = ticks
    491 		self.seq = seq
    492 		self.channel = channel
    493 		self.controller = controller
    494 		self.value = value
    495 	@staticmethod
    496 	def ReadFromStream (stream, seq, ticks, msg_type):
    497 		channel = msg_type & 0x0f
    498 		controller = ReadByte(stream)
    499 		value = ReadByte(stream)
    500 		if msg_type & 0xf0 != CONTROL_CHANGE:
    501 			stream.seek(-2,1)
    502 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    503 		if controller >= 120:
    504 			return ChannelModeEvent(ticks, seq, channel, controller, value)
    505 		return ControlChangeEvent(ticks, seq, channel, controller, value)
    506 	def WriteToStream (self, stream, track, filters=None):
    507 		self.WriteRunningStatus(stream, track, filters, CONTROL_CHANGE, self.controller, self.value)
    508 	def __str__ (self):
    509 		return '%s: ch=%d c=%d v=%d' % (self.name, self.channel, self.controller, self.value)
    510 
    511 #---------------------------------------------------------------

    512 # ChannelModeEvent

    513 #---------------------------------------------------------------

    514 class ChannelModeEvent (MIDIEvent):
    515 	def __init__ (self, ticks, seq, channel, controller, value):
    516 		self.name = 'ChannelMode'
    517 		self.msg_type = CONTROL_CHANGE
    518 		self.ticks = ticks
    519 		self.seq = seq
    520 		self.channel = channel
    521 		self.controller = controller
    522 		self.value = value
    523 	@staticmethod
    524 	def ReadFromStream (stream, seq, ticks, msg_type):
    525 		channel = msg_type & 0x0f
    526 		controller = ReadByte(stream)
    527 		value = ReadByte(stream)
    528 		if msg_type & 0xf0 != CONTROL_CHANGE:
    529 			stream.seek(-2,1)
    530 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    531 		if  controller < 120:
    532 			return ControlChangeEvent(ticks, seq, channel, controller, value)
    533 		return ChannelModeEvent(ticks, seq, channel, value)
    534 	def WriteToStream (self, stream, track, filters=None):
    535 		self.WriteRunningStatus(stream, track, filters, CONTROL_CHANGE, self.controller, self.value)
    536 	def __str__ (self):
    537 		return '%s: ch=%d c=%d v=%d' % (self.name, self.channel, self.controller, self.value)
    538 
    539 #---------------------------------------------------------------

    540 # ProgramChangeEvent

    541 #---------------------------------------------------------------

    542 class ProgramChangeEvent (MIDIEvent):
    543 	def __init__ (self, ticks, seq, channel, program):
    544 		self.name = 'ProgramChange'
    545 		self.msg_type = PROGRAM_CHANGE
    546 		self.ticks = ticks
    547 		self.seq = seq
    548 		self.channel = channel
    549 		self.program = program
    550 	@staticmethod
    551 	def ReadFromStream (stream, seq, ticks, msg_type):
    552 		channel = msg_type & 0x0f
    553 		program = ReadByte(stream)
    554 		if msg_type & 0xf0 != PROGRAM_CHANGE:
    555 			stream.seek(-1,1)
    556 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    557 		return ProgramChangeEvent(ticks, seq, channel, program)
    558 	def WriteToStream (self, stream, track, filters=None):
    559 		self.WriteRunningStatus(stream, track, filters, PROGRAM_CHANGE, self.program)
    560 	def __str__ (self):
    561 		return '%s: ch=%d p=%d' % (self.name, self.channel, self.program)
    562 
    563 #---------------------------------------------------------------

    564 # ChannelPressureEvent

    565 #---------------------------------------------------------------

    566 class ChannelPressureEvent (MIDIEvent):
    567 	def __init__ (self, ticks, seq, channel, value):
    568 		self.name = 'ChannelPressure'
    569 		self.msg_type = CHANNEL_PRESSURE
    570 		self.ticks = ticks
    571 		self.seq = seq
    572 		self.channel = channel
    573 		self.value = value
    574 	@staticmethod
    575 	def ReadFromStream (stream, seq, ticks, msg_type):
    576 		channel = msg_type & 0x0f
    577 		value = ReadByte(stream)
    578 		if msg_type & 0xf0 != CHANNEL_PRESSURE:
    579 			stream.seek(-1,1)
    580 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    581 		return ChannelPressureEvent(ticks, seq, channel, value)
    582 	def WriteToStream (self, stream, track, filters=None):
    583 		self.WriteRunningStatus(stream, track, filters, CHANNEL_PRESSURE, self.value)
    584 	def __str__ (self):
    585 		return '%s: ch=%d v=%d' % (self.name, self.channel, self.value)
    586 
    587 #---------------------------------------------------------------

    588 # PitchBendEvent

    589 #---------------------------------------------------------------

    590 class PitchBendEvent (MIDIEvent):
    591 	def __init__ (self, ticks, seq, channel, value):
    592 		self.name = 'PitchBend'
    593 		self.msg_type = PITCH_BEND
    594 		self.ticks = ticks
    595 		self.seq = seq
    596 		self.channel = channel
    597 		self.value = value
    598 	@staticmethod
    599 	def ReadFromStream (stream, seq, ticks, msg_type):
    600 		channel = msg_type & 0x0f
    601 		value = (ReadByte(stream) << 7) + ReadByte(stream) - 0x2000
    602 		if msg_type & 0xf0 != PITCH_BEND:
    603 			stream.seek(-2,1)
    604 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    605 		return PitchBendEvent(ticks, seq, channel, value)
    606 	def WriteToStream (self, stream, track, filters=None):
    607 		value = self.value + 0x2000
    608 		if value < 0:
    609 			value = 0
    610 		if value > 0x3fff:
    611 			value = 0x3fff
    612 		self.WriteRunningStatus(stream, track, filters, PITCH_BEND, value >> 7, value & 0x7f)
    613 	def __str__ (self):
    614 		return '%s: ch=%d v=%d' % (self.name, self.channel, self.value)
    615 
    616 #---------------------------------------------------------------

    617 # SysExEvent

    618 #---------------------------------------------------------------

    619 class SysExEvent (MIDIEvent):
    620 	def __init__ (self, ticks, seq, msg):
    621 		self.name = 'SysEx'
    622 		self.msg_type = SYSEX
    623 		self.ticks = ticks
    624 		self.seq = seq
    625 		self.length = len(msg)
    626 		self.msg = msg
    627 	@staticmethod
    628 	def ReadFromStream (stream, seq, ticks, msg_type):
    629 		pos = stream.tell()
    630 		length = ReadVarLenQty(stream)
    631 		msg = ReadBytes(stream, length)
    632 		if msg_type != SYSEX:
    633 			stream.seek(pos,0)
    634 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    635 		return SysExEvent(ticks, seq, msg)
    636 	def WriteToStream (self, stream, track, filters=None):
    637 		if not self.CheckFilters(filters):
    638 			return
    639 		self.WriteTicks(stream, track)
    640 		WriteByte(stream, SYSEX)
    641 		WriteVarLenQty(stream, self.length)
    642 		WriteBytes(stream, self.msg)
    643 		track.running_status = None
    644 	def __str__ (self):
    645 		fmt_str = '%s: f0' + ' %02x'*self.length
    646 		return fmt_str % ((self.name,) + tuple(self.msg))
    647 
    648 #---------------------------------------------------------------

    649 # SysExContEvent

    650 #---------------------------------------------------------------

    651 class SysExContEvent (MIDIEvent):
    652 	def __init__ (self, ticks, seq, msg):
    653 		self.name = 'SysEx+'
    654 		self.msg_type = END_SYSEX
    655 		self.ticks = ticks
    656 		self.seq = seq
    657 		self.length = len(msg)
    658 		self.msg = msg
    659 	@staticmethod
    660 	def ReadFromStream (stream, seq, ticks, msg_type):
    661 		pos = stream.tell()
    662 		length = ReadVarLenQty(stream)
    663 		msg = ReadBytes(stream, length)
    664 		if msg_type != END_SYSEX:
    665 			stream.seek(pos,0)
    666 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    667 		return SysExContEvent(ticks, seq, msg)
    668 	def WriteToStream (self, stream, track, filters=None):
    669 		if not self.CheckFilters(filters):
    670 			return
    671 		self.WriteTicks(stream, track)
    672 		WriteByte(stream, END_SYSEX)
    673 		WriteVarLenQty(stream, self.length)
    674 		WriteBytes(stream, self.msg)
    675 		track.running_status = None
    676 	def __str__ (self):
    677 		fmt_str = '%s:' + ' %02x'*self.length
    678 		return fmt_str % ((self.name,) + tuple(self.msg))
    679 
    680 #---------------------------------------------------------------

    681 # MetaEvent

    682 #---------------------------------------------------------------

    683 class MetaEvent (MIDIEvent):
    684 	def __init__ (self, ticks, seq, meta_type, msg):
    685 		self.name = 'MetaEvent'
    686 		self.msg_type = META_EVENT
    687 		self.ticks = ticks
    688 		self.seq = seq
    689 		self.meta_type = meta_type
    690 		self.length = len(msg)
    691 		self.msg = msg
    692 	@staticmethod
    693 	def ReadFromStream (stream, seq, ticks, msg_type):
    694 		pos = stream.tell()
    695 		meta_type = ReadByte(stream)
    696 		length = ReadVarLenQty(stream)
    697 		msg = ReadBytes(stream, length)
    698 		if msg_type != META_EVENT:
    699 			stream.seek(pos,0)
    700 			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
    701 		obj = MetaEvent(ticks, seq, meta_type, msg)
    702 		return obj
    703 	def WriteToStream (self, stream, track, filters=None):
    704 		if not self.CheckFilters(filters):
    705 			return
    706 		self.WriteTicks(stream, track)
    707 		WriteByte(stream, META_EVENT)
    708 		WriteByte(stream, self.meta_type)
    709 		WriteVarLenQty(stream, self.length)
    710 		WriteBytes(stream, self.msg)
    711 		track.running_status = None
    712 	def __str__ (self):
    713 		fmt_str = '%s: %02x' + ' %02x'*self.length
    714 		return fmt_str % ((self.name, self.meta_type) + tuple(self.msg))
    715 
    716 #---------------------------------------------------------------

    717 # MIDIControllers

    718 #---------------------------------------------------------------

    719 class MIDIControllers (object):
    720 	def __init__ (self):
    721 		self.controllers = []
    722 		self.rpns = []
    723 		for channel in range(16):
    724 			self.controllers.append({})
    725 			self.controllers[channel] = copy.deepcopy(DEFAULT_CONTROLLER_VALUES)
    726 			self.rpns.append({})
    727 			self.rpns[channel] = copy.deepcopy(DEFAULT_RPN_VALUES)
    728 		self.pitchbend = [0] * 16
    729 		self.program = [-1] * 16
    730 		self.pressure = [0] * 16
    731 
    732 	def __str__ (self):
    733 		output = []
    734 		for channel in range(16):
    735 			output.append('channel=%d' % channel)
    736 			output.append('  program=%d' % self.program[channel])
    737 			output.append('  pressure=%d' % self.pressure[channel])
    738 
    739 			output.append('  controllers')
    740 			for controller in self.controllers[channel].keys():
    741 				output.append('    %03d: %03d' % (controller, self.controllers[channel][controller]))
    742 
    743 			output.append('  rpns')
    744 			for rpn in self.rpns[channel].keys():
    745 				output.append('    %05d: %05d>' % (controller, self.rpns[channel][rpn]))
    746 		return '\n'.join(output)
    747 
    748 
    749 	def Event (self, event):
    750 		"""Process an event and save any changes in controller values"""
    751 		# process control changes

    752 		if event.msg_type == CONTROL_CHANGE:
    753 			self.ControlChange(event)
    754 		elif event.msg_type == CHANNEL_PRESSURE:
    755 			self.PressureChange(event)
    756 		elif event.msg_type == PROGRAM_CHANGE:
    757 			self.ProgramChange(event)
    758 		elif event.msg_type == PITCH_BEND:
    759 			self.PitchBendChange(event)
    760 
    761 	def PitchBendChange (self, event):
    762 		"""Monitor pitch bend change."""
    763 		self.pitchbend[event.channel] = event.value
    764 
    765 	def ProgramChange (self, event):
    766 		"""Monitor program change."""
    767 		self.program[event.channel] = event.program
    768 
    769 	def ControlChange (self, event):
    770 		"""Monitor control change."""
    771 		controller = event.controller
    772 		if controller in MONITOR_CONTROLLERS:
    773 			channel = event.channel
    774 			self.controllers[channel][controller] = event.value
    775 			if (controller == CTRL_RPN_DATA_MSB) or (controller == CTRL_RPN_DATA_LSB):
    776 				rpn = (self.controllers[channel][CTRL_RPN_MSB] << 7) + self.controllers[channel][CTRL_RPN_LSB]
    777 				if rpn in MONITOR_RPNS:
    778 					value = (self.controllers[channel][CTRL_RPN_DATA_MSB] << 7) + self.controllers[channel][CTRL_RPN_DATA_LSB]
    779 					self.rpns[channel][rpn] = value
    780 
    781 		# reset controllers

    782 		elif event.controller == CTRL_RESET_CONTROLLERS:
    783 			self.ResetControllers[event.channel]
    784 
    785 	def PressureChange (self, event):
    786 		"""Monitor pressure change."""
    787 		self.pressure[event.channel] = event.value
    788 
    789 	def ResetControllers (self, channel):
    790 		"""Reset controllers to default."""
    791 		self.controllers[channel] = DEFAULT_CONTROLLER_VALUES
    792 		self.rpns[channel] = DEFAULT_RPN_VALUES
    793 		self.pressure[channel] = 0
    794 
    795 	def GenerateEventList (self, ticks, ref_values=None):
    796 		"""Generate an event list based on controller differences."""
    797 		events = EventList()
    798 
    799 		# if no reference values, based on default values

    800 		if ref_values is None:
    801 			ref_values = MIDIControllers()
    802 
    803 		# iterate through 16 MIDI channels

    804 		for channel in range(16):
    805 
    806 			# generate RPN changes

    807 			for rpn in self.rpns[channel].keys():
    808 				value = self.rpns[channel][rpn]
    809 				if value != ref_values.rpns[channel][rpn]:
    810 					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_MSB, rpn >> 7))
    811 					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_LSB, rpn & 0x7f))
    812 					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_DATA_MSB, value >> 7))
    813 					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_DATA_LSB, value & 0x7f))
    814 
    815 			# generate controller changes

    816 			for controller in self.controllers[channel].keys():
    817 				if self.controllers[channel][controller] != ref_values.controllers[channel][controller]:
    818 					events.append(ControlChangeEvent(ticks, -1, channel, controller, self.controllers[channel][controller]))
    819 
    820 			# generate pressure changes

    821 			if self.pressure[channel] != ref_values.pressure[channel]:
    822 				events.append(ChannelPressureEvent(ticks, -1, channel, self.pressure[channel]))
    823 
    824 			# generate program changes

    825 			if self.program[channel] != ref_values.program[channel]:
    826 				if self.program[channel] in range(128):
    827 					events.append(ProgramChangeEvent(ticks, -1, channel, self.program[channel]))
    828 
    829 			# generate pitch bend changes

    830 			if self.pitchbend[channel] != ref_values.pitchbend[channel]:
    831 				if self.pitchbend[channel] in range(-8192,8191):
    832 					events.append(PitchBendEvent(ticks, -1, channel, self.pitchbend[channel]))
    833 
    834 		return events
    835 
    836 #---------------------------------------------------------------

    837 # EventList

    838 #---------------------------------------------------------------

    839 class EventList (list):
    840 	def __init__ (self):
    841 		list.__init__(self)
    842 
    843 	def FixNoteLengths (self):
    844 		midi_file_logger.debug('Fix note lengths')
    845 
    846 		# search for note-on's in event list

    847 		for index in range(len(self)):
    848 			event = self[index]
    849 			if event.msg_type == NOTE_ON:
    850 				note_off_ticks = event.ticks + event.note_length
    851 
    852 				# check for note-on occuring before end of current note

    853 				for i in range(index + 1, len(self)):
    854 					event_to_check = self[i]
    855 					if event_to_check.ticks >= note_off_ticks:
    856 						break
    857 
    858 					# adjust note length

    859 					if (event_to_check.msg_type == NOTE_ON) and (event_to_check.note == event.note):
    860 						midi_file_logger.debug('Adjusting note length @ %d' % event.ticks)
    861 						event.note_length = event_to_check.ticks - event.ticks
    862 						break
    863 
    864 	def ChaseControllers (self, end_seq, start_seq = 0, values = None):
    865 		midi_file_logger.debug('ChaseControllers from %d to %d' % (start_seq, end_seq))
    866 
    867 		# initialize controller values

    868 		if values is None:
    869 			values = MIDIControllers()
    870 
    871 		# chase controllers in track

    872 		for i in range(start_seq, min(end_seq, len(self))):
    873 			values.Event(self[i])
    874 
    875 		# return new values

    876 		return values
    877 		
    878 	def SelectEvents (self, start, end):
    879 		midi_file_logger.debug('SelectEvents: %d to %d' % (start, end))
    880 		selected = EventList()
    881 		for event in self:
    882 			if event.ticks >= start:
    883 				if event.ticks >= end:
    884 					break
    885 				midi_file_logger.debug('SelectEvent: %s' % event.__str__())
    886 				selected.append(event)
    887 		return selected
    888 
    889 	def MergeEvents (self, events):
    890 		# copy events and sort them by ticks/sequence#

    891 		self.extend(events)
    892 		self.SortEvents()
    893 		
    894 	def InsertEvents (self, events, seq):
    895 		self[seq:seq] = events
    896 		self.RenumberSeq()
    897 
    898 	def DeleteEvents (self, start_index, end_index, move_meta_events=None):
    899 		# default parameters

    900 		if start_index is None:
    901 			start_index = 0
    902 		if end_index is None:
    903 			end_index = len(self)
    904 
    905 		#print("\n")

    906 		#for evt in self[start_index:end_index]:

    907 		#	print("%d %s" % (evt.ticks, evt))

    908 
    909 		# delete events

    910 		delete_count = 0
    911 		move_count = 0
    912 		for event in self[start_index:end_index]:
    913 			#Bth; Added this so we always get clip end events; clips that ended on last measure wouldn't end on repeat

    914 			if (event.msg_type == CONTROL_CHANGE) and \
    915 			        (event.controller == JET_EVENT_TRIGGER_CLIP) and \
    916 			        ((event.value & 0x40) != 0x40):
    917 				pass
    918 			else:
    919 				if (move_meta_events is None) or (event.msg_type != META_EVENT):
    920 					self.remove(event)
    921 					delete_count += 1
    922 					
    923 				# move meta-events

    924 				else:
    925 					event.ticks = move_meta_events
    926 					move_count += 1
    927 				
    928 		midi_file_logger.debug('DeleteEvents: deleted %d events in range(%s:%s)' % (delete_count, start_index, end_index))
    929 		midi_file_logger.debug('DeleteEvents: moved %d events in range(%s:%s)' % (move_count, start_index, end_index))
    930 
    931 			
    932 	def SeekEvent (self, pos):
    933 		for i in range(len(self)):
    934 			if self[i].ticks >= pos:
    935 				return i
    936 		return None
    937 
    938 	def RenumberSeq (self):
    939 		seq = 0
    940 		for event in self:
    941 			event.seq = seq
    942 			seq += 1
    943 
    944 	def SortEvents (self):
    945 		self.sort(self.EventSorter)
    946 		self.RenumberSeq()
    947 
    948 	@staticmethod
    949 	def EventSorter (x, y):
    950 		if x.ticks == y.ticks:
    951 			return cmp(x.seq, y.seq)
    952 		else:
    953 			return cmp(x.ticks, y.ticks)
    954 
    955 	def DumpEvents (self, output, timebase):
    956 		if output is not None:
    957 			for event in self:
    958 				output.write('%s\n' % event.TimeEventStr(timebase))
    959 		else:
    960 			for event in self:
    961 				midi_file_logger.debug(event.TimeEventStr(timebase))
    962 
    963 #---------------------------------------------------------------

    964 # MIDITrack

    965 #---------------------------------------------------------------

    966 class MIDITrack (object):
    967 	"""The MIDITrack class implements methods for reading, parsing,
    968 	modifying, and writing tracks in Standard MIDI Files (SMF).
    969 
    970 	"""
    971 	def __init__ (self):
    972 		self.length = 0
    973 		self.events = EventList()
    974 		self.end_of_track = None
    975 		self.channel = None
    976 		self.name = None
    977 	
    978 	def ReadFromStream (self, stream, offset, file_size):
    979 		self.stream = stream
    980 		ticks = 0
    981 		seq = 0
    982 		running_status = None
    983 		tick_warning_level = stream.timebase.ppqn * LARGE_TICK_WARNING
    984 		
    985 		# read the track header - verify it's an SMF track

    986 		stream.seek(offset)
    987 		bytes = stream.read(struct.calcsize(SMF_TRACK_HEADER_FMT))
    988 		riff_tag, track_len = struct.unpack(SMF_TRACK_HEADER_FMT, bytes)
    989 		midi_file_logger.debug('SMF track header\n  Tag:      %s\n  TrackLen: %d' % (riff_tag, track_len))
    990 		if (riff_tag != SMF_TRACK_RIFF_TAG):
    991 			raise MIDIFileException(stream, MSG_INVALID_TRACK_HEADER)
    992 		self.start = stream.tell()
    993 		
    994 		# check for valid track length

    995 		if (self.start + track_len) > file_size:
    996 			stream.Warning('Ignoring illegal track length - %d exceeds length of file' % track_len)
    997 			track_len = None
    998 			
    999 		# read the entire track

   1000 		note_on_list = []
   1001 		while 1:
   1002 
   1003 			# save current position

   1004 			pos = stream.tell()
   1005 
   1006 			# check for end of track

   1007 			if track_len is not None:
   1008 				if (pos - self.start) >= track_len:
   1009 					break
   1010 
   1011 			# are we past end of track?

   1012 			if self.end_of_track:
   1013 				stream.Warning('Ignoring data encountered beyond end-of-track meta-event')
   1014 				break;
   1015 		
   1016 			# read delta timestamp

   1017 			delta = ReadVarLenQty(stream)
   1018 			if ticks > tick_warning_level:
   1019 				stream.Warning('Tick value is excessive - possibly corrupt data?')
   1020 			ticks += delta
   1021 				
   1022 			# get the event type and process it

   1023 			msg_type = ReadByte(stream)
   1024 
   1025 			# if data byte, check for running status

   1026 			if msg_type & 0x80 == 0:
   1027 
   1028 				# use running status

   1029 				msg_type = running_status
   1030 
   1031 				# back up so event can process data

   1032 				stream.seek(-1,1)
   1033 
   1034 				# if no running status, we have a problem

   1035 				if not running_status:
   1036 					stream.Warning('Ignoring data byte received with no running status')
   1037 
   1038 			# create event type from stream

   1039 			event = MIDIEvent.ReadFromStream(stream, seq, ticks, msg_type)
   1040 			
   1041 			if self.channel == None:
   1042 				try:
   1043 					self.channel = event.channel
   1044 				except AttributeError:
   1045 					pass
   1046 					
   1047 			# track note-ons

   1048 			if event.msg_type == NOTE_ON:
   1049 
   1050 				"""
   1051 				Experimental code to clean up overlapping notes
   1052 				Clean up now occurs during write process
   1053 				
   1054 				for note_on in note_on_list:
   1055 					if (event.channel == note_on.channel) and (event.note == note_on.note):
   1056 						stream.Warning('Duplicate note-on\'s encountered without intervening note-off')
   1057 						stream.Warning('  [%s]: %s' % (stream.timebase.ConvertTicksToStr(event.ticks), event.__str__()))
   1058 						note_on.note_length = event.ticks - note_on.ticks - 1
   1059 						if note_on.note_length <= 0:
   1060 							stream.Warning('Eliminating duplicate note-on')
   1061 							event.ticks = note_on.ticks
   1062 							self.events.remove(note_on)
   1063 				"""
   1064 				
   1065 				note_on_list.append(event)
   1066 
   1067 			# process note-offs

   1068 			if event.msg_type == NOTE_OFF:
   1069 				for note_on in note_on_list[:]:
   1070 					if (event.channel == note_on.channel) and (event.note == note_on.note):
   1071 						note_on.note_length = event.ticks - note_on.ticks
   1072 						note_on.note_off_velocity = event.velocity
   1073 						note_on_list.remove(note_on)
   1074 						break
   1075 				#else:

   1076 				#	stream.Warning('Note-off encountered without corresponding note-on')

   1077 				#	stream.Warning('  [%s]: %s' % (stream.timebase.ConvertTicksToStr(event.ticks), event.__str__()))

   1078 
   1079 			# check for end of track

   1080 			elif event.msg_type == META_EVENT and event.meta_type == META_EVENT_END_OF_TRACK:
   1081 				self.end_of_track = event.ticks
   1082 
   1083 			# BTH; get track name

   1084 			elif event.msg_type == META_EVENT and event.meta_type == META_EVENT_SEQUENCE_TRACK_NAME:
   1085 				self.name = array.array('B', event.msg).tostring()
   1086 				
   1087 			# append event to event list

   1088 			else:
   1089 				self.events.append(event)
   1090 				seq += 1
   1091 
   1092 			# save position for port-mortem

   1093 			stream.last_good_event = pos
   1094 
   1095 			# update running statusc_str(

   1096 			if msg_type < 0xf0:
   1097 				running_status = msg_type
   1098 			elif (msg_type < 0xf8) or (msg_type == 0xff):
   1099 				running_status = None
   1100 
   1101 		# check for stuck notes

   1102 		#if len(note_on_list):

   1103 		#	stream.Warning('Note-ons encountered without corresponding note-offs')

   1104 
   1105 		# check for missing end-of-track meta-event

   1106 		if self.end_of_track is None:
   1107 			self.last_tick = self.events[-1].ticks
   1108 			stream.Warning('End of track encountered with no end-of-track meta-event')
   1109 
   1110 		# if track length was bad, correct it

   1111 		if track_len is None:
   1112 			track_len = stream.tell() - offset - 8
   1113 
   1114 		return track_len
   1115 
   1116 	def Write (self, stream, filters=None):
   1117 		# save current file position so we can write header

   1118 		header_loc = stream.tell()
   1119 		stream.seek(header_loc + struct.calcsize(SMF_TRACK_HEADER_FMT))
   1120 
   1121 		# save a copy of the event list so we can restore it

   1122 		save_events = copy.copy(self.events)
   1123 
   1124 		# create note-off events

   1125 		index = 0
   1126 		while 1:
   1127 			if index >= len(self.events):
   1128 				break
   1129 
   1130 			# if note-on event, create a note-off event

   1131 			event = self.events[index]
   1132 			index += 1
   1133 			if event.msg_type == NOTE_ON:
   1134 				note_off = NoteOffEvent(event.ticks + event.note_length, index, event.channel, event.note, event.note_off_velocity)
   1135 
   1136 				# insert note-off in list

   1137 				for i in range(index, len(self.events)):
   1138 					if self.events[i].ticks >= note_off.ticks:
   1139 						self.events.insert(i, note_off)
   1140 						break
   1141 				else:
   1142 					self.events.append(note_off)
   1143 
   1144 		# renumber list

   1145 		self.events.RenumberSeq()
   1146 
   1147 		# write the events

   1148 		self.running_status = None
   1149 		self.ticks = 0
   1150 		for event in self.events:
   1151 
   1152 			# write event

   1153 			event.WriteToStream(stream, self, filters)
   1154 
   1155 		# restore original list (without note-off events)

   1156 		self.events = save_events
   1157 
   1158 		# write the end-of-track meta-event

   1159 		MetaEvent(self.end_of_track, 0, META_EVENT_END_OF_TRACK,[]).WriteToStream(stream, self, None)
   1160 
   1161 		# write track header

   1162 		end_of_track = stream.tell()
   1163 		track_len = end_of_track - header_loc - struct.calcsize(SMF_TRACK_HEADER_FMT)
   1164 		stream.seek(header_loc)
   1165 		bytes = struct.pack(SMF_TRACK_HEADER_FMT, SMF_TRACK_RIFF_TAG, track_len)
   1166 		stream.write(bytes)
   1167 		stream.seek(end_of_track)
   1168 
   1169 	def Trim (self, start, end, slide=True, chase_controllers=True, delete_meta_events=False, quantize=0):
   1170 		controllers = None
   1171 
   1172 		if quantize:
   1173 			# quantize events just before start

   1174 			for event in self.events.SelectEvents(start - quantize, start):
   1175 				midi_file_logger.debug('Trim: Moving event %s to %d' % (event.__str__(), start))
   1176 				event.ticks = start
   1177 
   1178 			# quantize events just before end

   1179 			for event in self.events.SelectEvents(end - quantize, end):
   1180 				midi_file_logger.debug('Trim: Moving event %s to %d' % (event.__str__(), end))
   1181 				event.ticks = end
   1182 
   1183 		# trim start

   1184 		if start:
   1185 
   1186 			# find first event inside trim

   1187 			start_event = self.events.SeekEvent(start)
   1188 			if start_event is not None:
   1189 
   1190 				# chase controllers to cut point

   1191 				if chase_controllers:
   1192 					controllers = self.events.ChaseControllers(self.events[start_event].seq)
   1193 					controller_events = controllers.GenerateEventList(0)
   1194 					midi_file_logger.debug('Trim: insert new controller events at %d:' % start)
   1195 					controller_events.DumpEvents(None, self.stream.timebase)
   1196 					self.events.InsertEvents(controller_events, start_event)
   1197 
   1198 				# delete events					

   1199 				midi_file_logger.debug('Trim: deleting events up to event %d' % start_event)
   1200 				if delete_meta_events:
   1201 					self.events.DeleteEvents(None, start_event, None)
   1202 				else:
   1203 					self.events.DeleteEvents(None, start_event, start)
   1204 
   1205 			# delete everything except metadata

   1206 			else:
   1207 				self.events.DeleteEvents(None, None, start)
   1208 
   1209 		# trim end

   1210 		end_event = self.events.SeekEvent(end)
   1211 		if end_event is not None:
   1212 			midi_file_logger.debug('Trim: trimming section starting at event %d' % end_event)
   1213 			self.events.DeleteEvents(end_event, None)
   1214 
   1215 		# trim any notes that extend past the end

   1216 		for event in self.events:
   1217 			if event.msg_type == NOTE_ON:
   1218 				if (event.ticks + event.note_length) > end:
   1219 					midi_file_logger.debug('Trim: trimming note that extends past end %s' % event.TimeEventStr(self.stream.timebase))
   1220 					event.note_length = end - event.ticks
   1221 					if event.note_length <= 0:
   1222 						raise 'Error in note length - note should have been deleted'
   1223 
   1224 		midi_file_logger.debug('Trim: initial end-of-track: %d' % self.end_of_track)
   1225 		self.end_of_track = min(self.end_of_track, end)
   1226 
   1227 		# slide events to start of track to fill hole

   1228 		if slide and start:
   1229 			midi_file_logger.debug('Trim: sliding events: %d' % start)
   1230 			for event in self.events:
   1231 				if event.ticks > start:
   1232 					event.ticks -= start
   1233 				else:
   1234 					event.ticks = 0
   1235 			self.end_of_track = max(0, self.end_of_track - start)
   1236 		midi_file_logger.debug('Trim: new end-of-track: %d' % self.end_of_track)
   1237 
   1238 		self.events.RenumberSeq()
   1239 		self.events.FixNoteLengths()
   1240 
   1241 	def DumpEvents (self, output):
   1242 		self.events.DumpEvents(output, self.stream.timebase)
   1243 		if output is not None:
   1244 			output.write('[%s]: end-of-track\n' % self.stream.timebase.ConvertTicksToStr(self.end_of_track))
   1245 		else:
   1246 			midi_file_logger.debug('[%s]: end-of-track' % self.stream.timebase.ConvertTicksToStr(self.end_of_track))
   1247 
   1248 
   1249 #---------------------------------------------------------------

   1250 # MIDIFile

   1251 #---------------------------------------------------------------

   1252 class MIDIFile (file):
   1253 	"""The MIDIFile class implements methods for reading, parsing,
   1254 	modifying, and writing Standard MIDI Files (SMF).
   1255 
   1256 	"""
   1257 	def __init__ (self, name, mode):
   1258 		file.__init__(self, name, mode)
   1259 		self.timebase = TimeBase()
   1260 
   1261 	def ReadFromStream (self, start_offset=0, file_size=None):
   1262 		"""Parse the MIDI file creating a list of properties, tracks,
   1263 		and events based on the contents of the file.
   1264 
   1265 		"""
   1266 
   1267 		# determine file size - without using os.stat

   1268 		if file_size == None:
   1269 			self.start_offset = start_offset
   1270 			self.seek(0,2)
   1271 			file_size = self.tell() - self.start_offset
   1272 			self.seek(start_offset,0)
   1273 		else:
   1274 			file_size = file_size
   1275 
   1276 		# for error recovery

   1277 		self.last_good_event = None
   1278 		self.error_loc = None
   1279 
   1280 		# read the file header - verify it's an SMF file

   1281 		bytes = self.read(struct.calcsize(SMF_HEADER_FMT))
   1282 		riff_tag, self.hdr_len, self.format, self.num_tracks, self.timebase.ppqn = struct.unpack(SMF_HEADER_FMT, bytes)
   1283 		midi_file_logger.debug('SMF header\n  Tag:       %s\n  HeaderLen: %d\n  Format:    %d\n  NumTracks: %d\n  PPQN:      %d\n' % \
   1284 			(riff_tag, self.hdr_len, self.format, self.num_tracks, self.timebase.ppqn))
   1285 
   1286 		# sanity check on header

   1287 		if (riff_tag != SMF_RIFF_TAG) or (self.format not in range(2)):
   1288 			raise MIDIFileException(self, MSG_NOT_SMF_FILE)
   1289 
   1290 		# check for odd header size

   1291 		if self.hdr_len + 8 != struct.calcsize(SMF_HEADER_FMT):
   1292 			self.Warning('SMF file has unusual header size: %d bytes' % self.hdr_len)
   1293 
   1294 		# read each of the tracks

   1295 		offset = start_offset + self.hdr_len + 8
   1296 		self.tracks = []
   1297 		self.end_of_file = 0
   1298 		for i in range(self.num_tracks):
   1299 			#print("Track: %d" % i)

   1300 
   1301 			# parse the track

   1302 			track = MIDITrack()
   1303 			length = track.ReadFromStream(self, offset, file_size)
   1304 			track.trackNum = i
   1305 			
   1306 			self.tracks.append(track)
   1307 
   1308 			# calculate offset to next track

   1309 			offset += length + 8
   1310 
   1311 			# determine time of last event

   1312 			self.end_of_file = max(self.end_of_file, track.end_of_track)
   1313 
   1314 		# if start_offset is zero, the final offset should match the file length

   1315 		if (offset - start_offset) != file_size:
   1316 			self.Warning('SMF file size is incorrect - should be %d, was %d' % (file_size, offset))
   1317 		
   1318 	def Save (self, offset=0, filters=None):
   1319 		"""Save this file back to disk with modifications."""
   1320 		if (not 'w' in self.mode) and (not '+' in self.mode):
   1321 			raise MIDIFileException(self, 'Cannot write to file in read-only mode')
   1322 		self.Write(self, offset, filters)
   1323 
   1324 	def SaveAs (self, filename, offset=0, filters=None):
   1325 		"""Save MIDI data to new file."""
   1326 		output_file = MIDIFile(filename, 'wb')
   1327 		self.Write(output_file, offset, filters)
   1328 		output_file.close()
   1329 
   1330 	def Write (self, output_file, offset=0, filters=None):
   1331 		"""This function does the actual work of writing the file."""
   1332 		# write the file header

   1333 		output_file.seek(offset)
   1334 		bytes = struct.pack(SMF_HEADER_FMT, SMF_RIFF_TAG, struct.calcsize(SMF_HEADER_FMT) - 8, self.format, self.num_tracks, self.timebase.ppqn)
   1335 		output_file.write(bytes)
   1336 
   1337 		# write out the tracks

   1338 		for track in self.tracks:
   1339 			track.Write(output_file, filters)
   1340 
   1341 		# flush the data to disk

   1342 		output_file.flush()
   1343 
   1344 	def ConvertToType0 (self):
   1345 		"""Convert a file to type 0."""
   1346 		if self.format == 0:
   1347 			midi_file_logger.warning('File is already type 0 - ignoring request to convert')
   1348 			return
   1349 
   1350 		# convert to type 0

   1351 		for track in self.tracks[1:]:
   1352 			self.tracks[0].MergeEvents(track.events)
   1353 		self.tracks = self.tracks[:1]
   1354 		self.num_tracks = 1
   1355 		self.format = 0
   1356 
   1357 	def DeleteEmptyTracks (self):
   1358 		"""Delete any tracks that do not contain MIDI messages"""
   1359 		track_num = 0
   1360 		for track in self.tracks[:]:
   1361 			for event in self.tracks.events:
   1362 				if event.msg_type in MIDI_MESSAGES:
   1363 					break;
   1364 				else:
   1365 					midi_file_logger.debug('Deleting track %d' % track_num)
   1366 					self.tracks.remove(track)
   1367 			track_num += 1
   1368 
   1369 	def ConvertToTicks (self, measures, beats, ticks):
   1370 		return self.timebase.ConvertToTicks(measures, beats, ticks)
   1371 
   1372 	def Trim (self, start, end, quantize=0, chase_controllers=True):
   1373 		track_num = 0
   1374 		for track in self.tracks:
   1375 			midi_file_logger.debug('Trimming track %d' % track_num)
   1376 			track.Trim(start, end, quantize=quantize, chase_controllers=chase_controllers)
   1377 			track_num += 1
   1378 
   1379 	def DumpTracks (self, output=None):
   1380 		track_num = 0
   1381 		for track in self.tracks:
   1382 			if output is None:
   1383 				midi_file_logger.debug('*** Track %d ***' % track_num)
   1384 			else:
   1385 				output.write('*** Track %d ***' % track_num)
   1386 			track.DumpEvents(output)
   1387 			track_num += 1
   1388 
   1389 	def Warning (self, msg):
   1390 		midi_file_logger.warning('[%d]: %s' % (self.tell(), msg))
   1391 
   1392 	def Error (self, msg):
   1393 		midi_file_logger.error('[%d]: %s' % (self.tell(), msg))
   1394 
   1395 	def DumpError (self):
   1396 		if self.last_good_event:
   1397 			midi_file_logger.error('Dumping from last good event:')
   1398 			pos = self.last_good_event - 16
   1399 			length = self.error_loc - pos + 16
   1400 		elif self.error_loc:
   1401 			midi_file_logger.error('Dumping from 16 bytes prior to error:')
   1402 			pos = self.error_loc
   1403 			length = 32
   1404 		else:
   1405 			midi_file_logger.error('No dump information available')
   1406 			return
   1407 
   1408 		self.seek(pos, 0)
   1409 		for i in range(length):
   1410 			if i % 16 == 0:
   1411 				if i:
   1412 					midi_file_logger.error(' '.join(debug_out))
   1413 				debug_out = ['%08x:' % (pos + i)]
   1414 			byte = self.read(1)
   1415 			if len(byte) == 0:
   1416 				break;
   1417 			debug_out.append('%02x' % ord(byte))
   1418 		if i % 16 > 0:
   1419 			midi_file_logger.error(' '.join(debug_out))
   1420 
   1421 def GetMidiInfo(midiFile):
   1422 	"""Bth; Get MIDI info"""
   1423 	
   1424 	class midiData(object):
   1425 		def __init__ (self):
   1426 			self.err = 1
   1427 			self.endMbt = "0:0:0"
   1428 			self.totalTicks = 0
   1429 			self.maxTracks = 0
   1430 			self.maxMeasures = 0
   1431 			self.maxBeats = 0
   1432 			self.maxTicks = 0
   1433 			self.totalTicks = 0
   1434 			self.timebase = None
   1435 			self.ppqn = 0
   1436 			self.beats_per_measure = 0
   1437 			self.trackList = []
   1438 			
   1439 	md = midiData()
   1440 	
   1441 	try:
   1442 		m = MIDIFile(midiFile, 'rb')
   1443 		m.ReadFromStream()
   1444 		
   1445 		for track in m.tracks:
   1446 			if track.channel is not None:
   1447 				empty = False 
   1448 				trk = track.channel + 1
   1449 			else:
   1450 				empty = True	
   1451 				trk = ''		
   1452 			md.trackList.append(trackGrid(track.trackNum, trk, track.name, empty))
   1453 				
   1454 		md.endMbt = m.timebase.ConvertTicksToMBT(m.end_of_file)
   1455 		md.endMbtStr = "%d:%d:%d" % (md.endMbt[0], md.endMbt[1], md.endMbt[2])
   1456 		md.maxMeasures = md.endMbt[0]
   1457 		md.maxBeats = 4
   1458 		md.maxTicks = m.timebase.ppqn
   1459 		md.maxTracks = m.num_tracks
   1460 		md.totalTicks = m.end_of_file
   1461 		md.timebase = m.timebase
   1462 		md.ppqn = m.timebase.ppqn
   1463 		md.beats_per_measure = m.timebase.beats_per_measure
   1464 
   1465 		#add above if more added

   1466 		md.err = 0
   1467 		
   1468 		m.close()
   1469 	except:
   1470 		raise
   1471 		pass
   1472 	
   1473 	return md
   1474 	
   1475 		
   1476 
   1477 
   1478 #---------------------------------------------------------------

   1479 # main

   1480 #---------------------------------------------------------------

   1481 if __name__ == '__main__':
   1482 	sys = __import__('sys')
   1483 	os = __import__('os')
   1484 
   1485 	# initialize root logger

   1486 	root_logger = logging.getLogger('')
   1487 	root_logger.setLevel(logging.NOTSET)
   1488 
   1489 	# initialize console handler

   1490 	console_handler = logging.StreamHandler()
   1491 	console_handler.setFormatter(logging.Formatter('%(message)s'))
   1492 	console_handler.setLevel(logging.DEBUG)
   1493 	root_logger.addHandler(console_handler)
   1494 
   1495 	files = []
   1496 	dirs = []
   1497 	last_arg = None
   1498 	sysex_filter = False
   1499 	drum_filter = False
   1500 	convert = False
   1501 
   1502 	# process args

   1503 	for arg in sys.argv[1:]:
   1504 
   1505 		# previous argument implies this argument

   1506 		if last_arg is not None:
   1507 			if last_arg == '-DIR':
   1508 				dirs.append(arg)
   1509 				last_arg = None
   1510 
   1511 		# check for switch

   1512 		elif arg[0] == '-':
   1513 			if arg == '-DIR':
   1514 				last_arg = arg
   1515 			elif arg == '-SYSEX':
   1516 				sysex_filter = True
   1517 			elif arg == '-DRUMS':
   1518 				drum_filter = True
   1519 			elif arg == '-CONVERT':
   1520 				convert = True
   1521 			else:
   1522 				midi_file_logger.error('Bad option %s' % arg)
   1523 
   1524 		# must be a filename

   1525 		else:
   1526 			files.append(arg)
   1527 
   1528 	# setup filters

   1529 	filters = []
   1530 	if sysex_filter:
   1531 		filters.append(EventTypeFilter((SYSEX,)))
   1532 	if drum_filter:
   1533 		filters.append(ChannelFilter((9,),False))
   1534 		
   1535 
   1536 	# process dirs

   1537 	for d in dirs:
   1538 		for root, dir_list, file_list in os.walk(d):
   1539 			for f in file_list:
   1540 				if f.endswith('.mid'):
   1541 					files.append(os.path.join(root, f))
   1542 
   1543 	# process files

   1544 	bad_files = []
   1545 	for f in files:
   1546 		midi_file_logger.info('Processing file %s' % f)
   1547 		midiFile = MIDIFile(f, 'rb')
   1548 		try:
   1549 			midiFile.ReadFromStream()
   1550 			
   1551 			#midiFile.DumpTracks()

   1552 			#print('[%s]: end-of-track\n' % midiFile.timebase.ConvertTicksToStr(midiFile.end_of_file))

   1553 
   1554 			# convert to type 0

   1555 			if convert and (midiFile.format == 1):
   1556 				midiFile.Convert(0)
   1557 				converted = True
   1558 			else:
   1559 				converted = False
   1560 
   1561 			# write processed file

   1562 			if converted or len(filters):
   1563 				midiFile.SaveAs(f[:-4] + '-mod.mid', filters)
   1564 				
   1565 		except MIDIFileException, X:
   1566 			bad_files.append(f)
   1567 			midi_file_logger.error('Error in file %s' % f)
   1568 			midi_file_logger.error(X)
   1569 			midiFile.DumpError()
   1570 		midiFile.close()
   1571 
   1572 	# dump problem files

   1573 	if len(bad_files):
   1574 		midi_file_logger.info('The following file(s) had errors:')
   1575 		for f in bad_files:
   1576 			midi_file_logger.info(f)
   1577 	else:
   1578 		midi_file_logger.info('All files read successfully')
   1579 
   1580