Package midi
[hide private]
[frames] | no frames]

Source Code for Package midi

   1  """Midi file I/O and manipulation library. 
   2   
   3  Python MIDI 
   4   
   5   - Original author: Giles Hall 
   6   - Modified by Mark Granroth-Wilding, 2010-11 
   7   
   8  This package contains data structures and utilities for reading,  
   9  manipulating and writing MIDI data. 
  10   
  11  """ 
  12  import copy 
  13  from cStringIO import StringIO 
  14  from struct import unpack, pack 
  15  from math import log 
  16   
  17  from .constants import NOTE_VALUE_MAP_SHARP, BEATVALUES, \ 
  18                          DEFAULT_MIDI_HEADER_SIZE 
  19  from .encoding import read_varlen, write_varlen 
20 21 22 -class Event(object):
23 """ 24 Base class for all MIDI events. 25 26 """ 27 length = 0 28 name = "Generic MIDI Event" 29 statusmsg = 0x0 30 # By default, events may be transmitted using running status, 31 # but this is disabled for some event types 32 allow_running = True 33
34 - class __metaclass__(type):
35 - def __init__(cls, name, bases, dict):
36 if name not in ['Event', 'MetaEvent', 'NoteEvent']: 37 EventFactory.register_event(cls, bases)
38
39 - def __init__(self):
40 self.type = self.__class__.__name__ 41 """ Event type derived from class name """ 42 self.channel = 0 43 """ Midi channel """ 44 self.tick = 0 45 """ Time of the event in midi ticks """ 46 self.msdelay = 0 47 """ Delay in ms """ 48 self.data = '' 49 """ Data after status message """ 50 self.track = 0 51 """ Track number (gets set when event is added to a stream) """ 52 self.order = None 53 """ Sort order """
54
55 - def copy(self):
56 return copy.deepcopy(self)
57
58 - def is_event(cls, statusmsg):
59 """ 60 Checks whether this is of an event identified by the given 61 status message. 62 63 """ 64 return (cls.statusmsg == (statusmsg & 0xF0))
65 is_event = classmethod(is_event) 66
67 - def __str__(self):
68 return "%s @%d %dms C%d T%d" % (self.name, 69 self.tick, 70 self.msdelay, 71 self.channel, 72 self.track)
73
74 - def __cmp__(self, other):
75 if self.tick < other.tick: return -1 76 elif self.tick > other.tick: return 1 77 return 0
78
79 - def adjust_msdelay(self, tempo):
80 rtick = self.tick - tempo.tick 81 self.msdelay = int((rtick * tempo.mpt) + tempo.msdelay)
82
83 - def encode(self, last_tick=0, running=False):
84 """ 85 Produces an encoding of this event for writing to a MIDI stream. 86 Includes the delta and status message. 87 88 @type last_tick: int/long 89 @param last_tick: tick value of the previous event that was 90 encoded in the stream. The timing of this event will be 91 stored as a delta, so we need to know when the last thing 92 happened. 93 @type running: bool 94 @param running: omits the status message if true, since it is 95 assumed that the status is carried over from the previous 96 event. 97 98 """ 99 encstr = '' 100 if not running: 101 encstr += chr((self.statusmsg & 0xF0) | (0x0F & self.channel)) 102 return self.encode_delta_tick(last_tick=last_tick) + encstr + self.encode_data()
103
104 - def decode(self, tick, statusmsg, track, runningstatus=''):
105 """ 106 Reads MIDI data from the track stream, from which the tick 107 and status message have already been read. Removes as many 108 bytes from the track as this event type needs. 109 Sets instance variables according to data read from the stream. 110 111 @param tick: tick time of the event (already read from the stream) 112 @param statusmsg: the status message that was read from the 113 stream for this event. This is expected to be of the correct 114 type for this event type. 115 @param track: data stream from which to continue reading as 116 much data as is needed for this event. 117 @param runningstatus: if this event had a running status in the 118 input stream, the byte that was read off the stream to 119 try to get a status for this event (which turned out not 120 to be a status message) goes in here and gets treated as 121 the first byte of the data. 122 123 """ 124 assert(self.is_event(statusmsg)) 125 self.tick = tick 126 self.channel = statusmsg & 0x0F 127 self.data = '' 128 if runningstatus: 129 self.data += runningstatus 130 remainder = self.length - len(self.data) 131 if remainder: 132 self.data += str.join('',[track.next() for x in range(remainder)]) 133 self.decode_data()
134
135 - def encode_delta_tick(self, last_tick=0):
136 """ 137 this function should be renamed "encode_delta_tick"; it doesn't 138 encode the tick value of the event. 139 Returns the varlen data to use to represent the timing of this 140 event relative to the last event. 141 142 @param last_tick: time of the previous event in the stream. 143 144 """ 145 delta_tick = self.tick - last_tick 146 return write_varlen(delta_tick)
147
148 - def decode_data(self):
149 """ 150 Takes the data that's been read into C{data} instance variable 151 and sets instance attributes from the values in it. What this 152 does is specific to the event type. 153 154 At the simplest, it could do nothing, just leaving the raw data 155 in C{data}, but more likely it will decode values from the 156 data. 157 158 """ 159 pass
160
161 - def encode_data(self):
162 """ 163 Produces byte data to represent this event on the basis of 164 instance variables. This is the data that will be written to 165 a MIDI stream (not including the tick and status message). 166 167 @return: byte data as a string 168 169 """ 170 return self.data
171
172 173 174 -class MetaEvent(Event):
175 """ 176 MetaEvent is a special subclass of Event that is not meant to 177 be used as a concrete class. It defines a subset of Events known 178 as the Meta events. 179 180 """ 181 statusmsg = 0xFF 182 metacommand = 0x0 183 name = 'Meta Event' 184 allow_running = False 185 186 data = '' 187 """ Raw data to store in the event """ 188
189 - def is_event(cls, statusmsg):
190 return (cls.statusmsg == statusmsg)
191 is_event = classmethod(is_event) 192
193 - def is_meta_event(cls, metacmd):
194 return (cls.metacommand == metacmd)
195 is_meta_event = classmethod(is_meta_event) 196
197 - def encode(self, last_tick=0, running=False):
198 tick = self.encode_delta_tick(last_tick=last_tick) 199 data = self.encode_data() 200 datalen = write_varlen(len(data)) 201 smsg = chr(self.statusmsg) 202 mcmd = chr(self.metacommand) 203 return str.join("", (tick, smsg, mcmd, datalen, data))
204
205 - def decode(self, tick, command, track):
206 assert(self.is_meta_event(command)) 207 self.tick = tick 208 self.channel = 0 209 if not hasattr(self, 'order'): 210 self.order = None 211 len = read_varlen(track) 212 self.data = str.join('', [track.next() for x in range(len)]) 213 self.decode_data()
214
215 216 -class EventFactory(object):
217 """ 218 EventFactory is a factory for getting MIDI events out of a data 219 stream. 220 221 """ 222 EventRegistry = [] 223 MetaEventRegistry = [] 224
225 - def __init__(self):
226 self.RunningStatus = None 227 self.RunningTick = 0
228
229 - def register_event(cls, event, bases):
230 if MetaEvent in bases: 231 cls.MetaEventRegistry.append(event) 232 elif (Event in bases) or (NoteEvent in bases): 233 cls.EventRegistry.append(event) 234 else: 235 raise ValueError, "Unknown bases class in event type: "+event.name
236 register_event = classmethod(register_event) 237
238 - def parse_midi_event(self, track):
239 """ 240 Reads bytes out of a data stream and returns a representation 241 of the next MIDI event. All events in a stream should be read 242 in turn using the same EventFactory, which keeps track 243 of things like running status. 244 245 @param track: data stream to read bytes from 246 @return: L{Event} subclass instance for the next MIDI event in 247 the track. 248 249 """ 250 # first datum is varlen representing delta-time 251 tick = read_varlen(track) 252 self.RunningTick += tick 253 # next byte is status message 254 stsmsg = ord(track.next()) 255 # is the event a MetaEvent? 256 if MetaEvent.is_event(stsmsg): 257 # yes, figure out which one 258 cmd = ord(track.next()) 259 for etype in self.MetaEventRegistry: 260 if etype.is_meta_event(cmd): 261 evi = etype() 262 evi.decode(self.RunningTick, cmd, track) 263 return evi 264 else: 265 raise Warning, "Unknown Meta MIDI Event: " + `cmd` 266 # not a Meta MIDI event, must be a general message 267 else: 268 for etype in self.EventRegistry: 269 if etype.is_event(stsmsg): 270 self.RunningStatus = (stsmsg, etype) 271 evi = etype() 272 evi.decode(self.RunningTick, stsmsg, track) 273 return evi 274 else: 275 if self.RunningStatus: 276 cached_stsmsg, etype = self.RunningStatus 277 evi = etype() 278 evi.decode(self.RunningTick, 279 cached_stsmsg, track, chr(stsmsg)) 280 return evi 281 else: 282 raise Warning, "Unknown MIDI Event: " + `stsmsg`
283
284 -class NoteEvent(Event):
285 """ 286 Abstract base class for L{NoteOnEvent} and L{NoteOffEvent}. 287 288 """ 289 length = 2 290 291 # Event attributes 292 pitch = 60 293 """ Note number (0-127) """ 294 velocity = 64 295 """ How hard the note is played or how quickly it's released (0-127) """ 296
297 - def __str__(self):
298 return "%s [ %s(%s) %d ]" % \ 299 (super(NoteEvent, self).__str__(), 300 NOTE_VALUE_MAP_SHARP[self.pitch], 301 self.pitch, 302 self.velocity)
303
304 - def decode_data(self):
305 self.pitch = ord(self.data[0]) 306 self.velocity = ord(self.data[1])
307
308 - def encode_data(self):
309 return chr(self.pitch) + chr(self.velocity)
310
311 -class NoteOnEvent(NoteEvent):
312 """ 313 Note-on event. Starts a note playing. 314 315 """ 316 statusmsg = 0x90 317 name = 'Note On'
318
319 -class NoteOffEvent(NoteEvent):
320 """ 321 Note-off event. Stops a note playing. 322 323 """ 324 statusmsg = 0x80 325 name = 'Note Off'
326
327 -class AfterTouchEvent(Event):
328 """ 329 Changes the pressure of aftertouch on a note. 330 331 """ 332 statusmsg = 0xA0 333 length = 2 334 name = 'After Touch' 335 336 # Event attributes 337 pitch = 60 338 """ Note number (0-127) """ 339 pressure = 0 340 """ How hard to apply aftertouch (0-127) """ 341
342 - def __str__(self):
343 return "%s [ %s %s ]" % \ 344 (super(AfterTouchEvent, self).__str__(), 345 hex(self.pitch), 346 hex(self.pressure))
347
348 - def decode_data(self):
349 self.pitch = ord(self.data[0]) 350 self.pressure = ord(self.data[1])
351
352 - def encode_data(self):
353 return chr(self.pitch) + chr(self.pressure)
354
355 -class ControlChangeEvent(Event):
356 """ 357 Sets the value of one of the 128 controllers available to a device. 358 359 """ 360 # To do: properly print out bank changes, with msb and lsb calculated correctly. 361 362 statusmsg = 0xB0 363 length = 2 364 name = 'Control Change' 365 366 # Event attributes 367 control = 0 368 """ Which controller's value to set """ 369 value = 0 370 """ Value to assign to the controller """ 371
372 - def __str__(self):
373 return "%s [ %s %s ]" % \ 374 (super(ControlChangeEvent, self).__str__(), 375 constants.CONTROL_MESSAGE_DICTIONARY[self.control], 376 (self.value))
377
378 - def decode_data(self):
379 self.control = ord(self.data[0]) 380 self.value = ord(self.data[1])
381
382 - def encode_data(self):
383 return chr(self.control) + chr(self.value)
384
385 -def all_notes_off_event(channel):
386 """ 387 Creates an All Notes Off event. This is really a specialized control 388 change event and uses one of the reserved controller numbers. 389 390 """ 391 ev = ControlChangeEvent() 392 ev.channel = channel 393 # This controller is reserved specially for this purpose 394 ev.control = 123 395 # I believe this is the only value that's valid 396 ev.value = 0 397 return ev
398
399 -class ProgramChangeEvent(Event):
400 """ 401 Changes the patch (voice) number of the instrument. 402 403 """ 404 statusmsg = 0xC0 405 length = 1 406 name = 'Program Change' 407 408 # Event attributes 409 value = 0 410 """ New patch number to select """ 411
412 - def __str__(self):
413 return "%s [ %s ]" % \ 414 (super(ProgramChangeEvent, self).__str__(), 415 self.value)
416
417 - def decode_data(self):
418 self.value = ord(self.data[0])
419
420 - def encode_data(self):
421 return chr(self.value)
422
423 -class ChannelAfterTouchEvent(Event):
424 """ 425 Channel pressure (after-touch). Most often sent by pressing down 426 on the key after it "bottoms out". Different from polyphonic 427 after-touch. Use this message to send the single greatest pressure 428 value (of all the current depressed keys). 429 430 """ 431 statusmsg = 0xD0 432 length = 1 433 name = 'Channel After Touch' 434 435 # Event attributes 436 pressure = 0 437 """ Pressure value to set the channel's after touch to """ 438
439 - def __str__(self):
440 return "%s [ %s ]" % \ 441 (super(ChannelAfterTouchEvent,self).__str__(), 442 hex(self.pressure))
443
444 - def decode_data(self):
445 self.pressure = ord(self.data[0])
446
447 - def encode_data(self):
448 return chr(self.pressure)
449
450 -class PitchWheelEvent(Event):
451 """ 452 Change the pitch-bend wheel value. Given as a 14-bit value 453 (0-16,383), 8,192 being the neutral value. 454 """ 455 statusmsg = 0xE0 456 length = 2 457 name = 'Pitch Wheel' 458 459 # Event attributes 460 value = 0x2222 461 """ Pitch wheel position (0-16,383, middle 8,192). """ 462 463 #to do: multiply the pitch bend amount by the sensitivity to properly 464 #display the amount of pitch bend
465 - def __str__(self):
466 return "%s [ %s ]" % \ 467 (super(PitchWheelEvent, self).__str__(), 468 self.value/8192.)
469
470 - def decode_data(self):
471 first = ord(self.data[0]) 472 second = ord(self.data[1]) 473 if (first & 0x80) or (second & 0x80): 474 raise Warning ("Pitch Wheel data out of range") 475 self.value = ((second << 7) | first) - 0x2000
476
477 - def encode_data(self):
478 value = self.value + 0x2000 479 first = chr(value & 0x7F) 480 second = chr((value >> 7) & 0xFF) 481 return first + second
482
483 -class SysExEvent(Event):
484 """ 485 System exclusive MIDI event. Generic event type for sending data 486 to a system in a format that it recognizes, but which may not be 487 recognized by any other device. 488 489 Some standards are defined for sub-event types of sysex. Tuning 490 messages are one example. This class implements encoding/decoding 491 for tuning messages. 492 493 Other sysex events are treated just as raw data, between the initial 494 F0 and the final F7. 495 496 @todo: we should possibly terminate the sysex data on finding any 497 1-initial byte. Sysex messages are supposed to end with a 0xF7, but 498 the data can only contain 0-initial bytes (7-bit values). 499 500 """ 501 statusmsg = 0xF0 502 name = 'SysEx' 503 subtype = 0 504 allow_running = False 505 506 SUBTYPE_NONE = 0 507 SUBTYPE_SINGLE_NOTE_TUNING_CHANGE = 1 508 509 # Event attributes 510 data = '' 511 """ 512 Data stored in the SysEx. This can be any data in a format that 513 the device will recognise. 514 """ 515
516 - def is_event(cls, statusmsg):
517 return (cls.statusmsg == statusmsg)
518 is_event = classmethod(is_event) 519
520 - def decode(self, tick, statusmsg, track, runningstatus=''):
521 assert(self.is_event(statusmsg)) 522 self.tick = tick 523 if runningstatus != '': 524 # SysEx events should not be used with running status 525 raise MidiReadError, "sysex event was encountered with running status" 526 length = read_varlen(track) 527 self.data = '' 528 # Keep getting data until we have the number of bytes specified 529 self.data = ''.join([track.next() for i in range(length)]) 530 # Check we got the end marker, or at least a 1-initial byte 531 # at the end 532 ender = ord(self.data[-1]) 533 self.data = self.data[:-1] 534 if ender & 0x80 == 0: 535 raise MidiReadError, "sysex didn't end with a status byte "\ 536 "(got %s, data %s)" % (hex(ender), " ".join([hex(b) for b in self.data])) 537 self.decode_data()
538
539 - def encode(self, last_tick=0, running=False):
540 if running: 541 raise MidiWriteError, "sysex event cannot be encoded with running status" 542 data = self.encode_data() 543 return self.encode_delta_tick(last_tick=last_tick) + chr(self.statusmsg) + write_varlen(len(data)+1) + data + chr(0xF7)
544
545 - def decode_data(self):
546 if len(self.data) >= 6 and \ 547 ord(self.data[0]) == 0x7F and \ 548 ord(self.data[2]) == 0x08 and \ 549 ord(self.data[3]) == 0x02: 550 # This is a single note tuning change sysex event, as 551 # defined by the MIDI standard on tuning messages 552 self.init_subtype(SysExEvent.SUBTYPE_SINGLE_NOTE_TUNING_CHANGE) 553 # First byte is the device id 554 self.device_id = ord(self.data[1]) 555 self.tuning_program = ord(self.data[4]) 556 num_changes = ord(self.data[5]) 557 change_data = self.data[6:] 558 if len(change_data) % 4 != 0: 559 raise MidiReadError, "incomplete tuning change data in single note tuning change" 560 # Get the data for each change 561 self.changes = [(ord(change_data[i*4]), 562 ord(change_data[i*4+1]), 563 (ord(change_data[i*4+2])<<7) + ord(change_data[i*4+3]) 564 ) for i in range(len(change_data)/4)] 565 if len(self.changes) != num_changes: 566 raise Warning, "tuning change event reported wrong number of changes"
567
568 - def encode_data(self):
569 if self.subtype == SysExEvent.SUBTYPE_SINGLE_NOTE_TUNING_CHANGE: 570 data = chr(0x7F) + chr(self.device_number) + chr(0x08) + chr(0x02) 571 data += chr(self.tuning_program) 572 data += chr(len(self.changes)) 573 for key,semitone,freq in self.changes: 574 data += chr(key) 575 data += chr(semitone & 0x7F) + chr((freq >>7) & 0x7F) + chr(freq & 0x7F) 576 return data 577 else: 578 return self.data
579
580 - def init_subtype(self, subtype):
581 if subtype == SysExEvent.SUBTYPE_NONE: 582 pass 583 elif subtype == SysExEvent.SUBTYPE_SINGLE_NOTE_TUNING_CHANGE: 584 self.name = 'Single Note Tuning Change' 585 self.subtype = SysExEvent.SUBTYPE_SINGLE_NOTE_TUNING_CHANGE 586 self.device_number = 0x7F 587 self.tuning_program = 0 588 self.changes = [] 589 else: 590 raise MidiReadError, "unknown SysEx subtype identifier: %d" % subtype
591
592 - def __str__(self):
593 out = super(SysExEvent, self).__str__() 594 if self.subtype == SysExEvent.SUBTYPE_SINGLE_NOTE_TUNING_CHANGE: 595 for key,semitone,freq in self.changes: 596 out += " [ %d -> st %d + %f cents ]" % (key, semitone, float(freq)*100/(2**14)) 597 return out
598
599 -def single_note_tuning_event(note_changes, device_number=None, tuning_program=None):
600 """ 601 Returns a SysExEvent which will encode single note tuning changes 602 according to the MIDI tuning messages standard. 603 604 note_changes should be a list of note changes, specified as triples: 605 - (<note number>, <semitone>, <cents>) 606 where: 607 - <key number> is a note number (0-127) to retune, 608 - <semitone> is the nearest equal-temperament semitone below the 609 desired frequency (also a note number, 0-127), and 610 - <cents> is a float >= 0 and < 100 representing the number of 611 cents above the semitone's pitch to retune it to. 612 613 """ 614 ev = SysExEvent() 615 ev.init_subtype(SysExEvent.SUBTYPE_SINGLE_NOTE_TUNING_CHANGE) 616 # Leave these as the defaults if no values are given 617 if device_number is not None: 618 ev.device_number = device_number 619 if tuning_program is not None: 620 ev.tuning_program = tuning_program 621 ev.changes = [(key,semitone,int(float(cents)/100*(2**14))) for (key,semitone,cents) in note_changes] 622 return ev
623
624 625 -class SequenceNumberEvent(MetaEvent):
626 """ 627 Defines the pattern number of a Type 2 MIDI file or the number of 628 a sequence in a Type 0 or Type 1 MIDI file. Should always have a 629 delta time of 0. 630 631 """ 632 name = 'Sequence Number' 633 metacommand = 0x00
634
635 -class TextEvent(MetaEvent):
636 """ 637 Defines some text which can be used for any reason including track 638 notes, comments. The text string is usually ASCII text, but may be 639 any character. 640 641 """ 642 name = 'Text' 643 metacommand = 0x01 644
645 - def __str__(self):
646 return "%s [ %s ]" % \ 647 (super(TextEvent, self).__str__(), 648 self.data)
649 - def encode_data(self):
650 return self.data
651
652 -class CopyrightEvent(MetaEvent):
653 """ 654 Defines copyright information including the copyright symbol 655 (0xA9), year and author. Should always be in the first track chunk, 656 have a delta time of 0. 657 658 """ 659 name = 'Copyright Notice' 660 metacommand = 0x02
661
662 -class TrackNameEvent(MetaEvent):
663 """ 664 Defines the name of a sequence when in a Type 0 or Type 2 MIDI file 665 or in the first track of a Type 1 MIDI file. Defines a track name 666 when it appears in any track after the first in a Type 1 MIDI file. 667 668 """ 669 name = 'Track Name' 670 metacommand = 0x03 671 order = 3 672
673 - def __str__(self):
674 return "%s [ %s ]" % \ 675 (super(TrackNameEvent, self).__str__(), 676 self.data)
677
678 -class InstrumentNameEvent(MetaEvent):
679 """ 680 Defines the name of an instrument being used in the current track 681 chunk. This event can be used with the MIDI Channel Prefix meta 682 event to define which instrument is being used on a specific channel. 683 684 """ 685 name = 'Instrument Name' 686 metacommand = 0x04 687 order = 4 688
689 - def __str__(self):
690 return "%s [ %s ]" % \ 691 (super(InstrumentNameEvent, self).__str__(), 692 self.data)
693
694 695 -class LyricsEvent(MetaEvent):
696 """ 697 Defines the lyrics in a song and usually used to define a syllable 698 or group of works per quarter note. This event can be used as an 699 equivalent of sheet music lyrics or for implementing a karaoke-style 700 system. 701 702 """ 703 name = 'Lyrics' 704 metacommand = 0x05 705
706 - def __str__(self):
707 return "%s [ %s ]" % \ 708 (super(LyricsEvent, self).__str__(), 709 self.data)
710
711 712 -class MarkerEvent(MetaEvent):
713 """ 714 Marks a significant point in time for the sequence. Usually found 715 in the first track, but may appear in any. Can be useful for 716 marking the beginning/end of a new verse or chorus. 717 718 """ 719 name = 'Marker' 720 metacommand = 0x06
721
722 -class CuePointEvent(MetaEvent):
723 """ 724 Marks the start of some type of new sound or action. Usually found 725 in the first track, but may appear in any. Sometimes used by 726 sequencers to mark when playback of a sample or video should begin. 727 728 """ 729 name = 'Cue Point' 730 metacommand = 0x07
731
732 -class UnknownEvent(MetaEvent):
733 name = 'whoknows?' 734 metacommand = 0x09
735
736 -class ChannelPrefixEvent(MetaEvent):
737 """ 738 Associates a MIDI channel with following meta events. Its effect 739 is terminated by another MIDI Channel Prefix event or any non-meta 740 event. Often used before an Instrument Name Event to specify which 741 channel an instrument name represents. 742 743 """ 744 name = 'Channel Prefix' 745 metacommand = 0x20 746
747 - def __str__(self):
748 return "%s [ %s ]" % \ 749 (super(ChannelPrefixEvent, self).__str__(), 750 ord(self.data))
751
752 -class PortEvent(MetaEvent):
753 """ 754 Specifies which MIDI port should be used to output all subsequent 755 events. 756 757 """ 758 name = 'MIDI Port/Cable' 759 metacommand = 0x21 760 order = 5 761 762 # Event attributes 763 port = 0 764 """ MIDI port to switch to. """ 765
766 - def __str__(self):
767 return "%s [ port: %d ]" % \ 768 (super(PortEvent, self).__str__(), 769 self.port)
770
771 - def decode_data(self):
772 if len(self.data) != 1: 773 raise MidiReadError, "port event data should be 1 byte long" 774 self.port = ord(self.data[0])
775
776 - def encode_data(self):
777 return chr(self.port)
778
779 -class TrackLoopEvent(MetaEvent):
780 name = 'Track Loop' 781 metacommand = 0x2E
782
783 -class EndOfTrackEvent(MetaEvent):
784 """ 785 Appears and the end of each track to mark the end. 786 787 Note that you don't need to add this to tracks in an L{EventStream} - 788 it gets added automatically. 789 790 """ 791 name = 'End of Track' 792 metacommand = 0x2F 793 order = 2
794
795 -class SetTempoEvent(MetaEvent):
796 """ 797 Change the tempo of events played after this point. In the MIDI 798 data, the tempo is stored in microseconds per quarter note (MPQN), 799 but you may also specify it in beats per minute via the C{tempo} 800 attribute. 801 802 """ 803 name = 'Set Tempo' 804 metacommand = 0x51 805 order = 1 806 807 # Event attributes 808 tempo = 120 809 """ Tempo value in beats per minute (MM) """ 810 mpqn = int(float(6e7) / 120) 811 """ Tempo value in microseconds per quarter note """ 812
813 - def __str__(self):
814 return "%s [ mpqn: %d tempo: %d ]" % \ 815 (super(SetTempoEvent, self).__str__(), 816 self.mpqn, self.tempo)
817
818 - def __setattr__(self, item, value):
819 if item == 'mpqn': 820 self.__dict__['mpqn'] = value 821 self.__dict__['tempo'] = float(6e7) / value 822 elif item == 'tempo': 823 self.__dict__['tempo'] = value 824 self.__dict__['mpqn'] = int(float(6e7) / value) 825 else: 826 self.__dict__[item] = value
827
828 - def decode_data(self):
829 if len(self.data) != 3: 830 raise MidiReadError, "tempo event data should be 3 bytes long" 831 self.mpqn = (ord(self.data[0]) << 16) + (ord(self.data[1]) << 8) \ 832 + ord(self.data[2])
833
834 - def encode_data(self):
835 self.mpqn = int(float(6e7) / self.tempo) 836 return chr((self.mpqn & 0xFF0000) >> 16) + \ 837 chr((self.mpqn & 0xFF00) >> 8) + \ 838 chr((self.mpqn & 0xFF))
839
840 -class SmpteOffsetEvent(MetaEvent):
841 """ 842 Designates the SMPTE start time (hours, minutes, secs, frames, 843 subframes) of the track. Should be at the start of the track. 844 The hour should not be encoded with the SMPTE format as it is in 845 MIDI Time Code. In a format 1 file, the SMPTE OFFSET must be stored 846 with the tempo map (ie, the first track). The final byte contains 847 fractional frames in 100ths of a frame. 848 849 Data should be encoded as 5 bytes: 850 <hour> <min> <sec> <frame> <fractional-frames>. 851 852 @note: encoding is not currently implemented, so you need to do 853 it manually and set the C{data} attribute. 854 855 """ 856 name = 'SMPTE Offset' 857 metacommand = 0x54
858
859 -class TimeSignatureEvent(MetaEvent):
860 """ 861 Expressed as 4 numbers: 862 <numerator> <denominator> <metronome> <thirtyseconds>. 863 864 <numerator> and <denominator> represent the parts of the 865 signature as notated on sheet music. The denominator must be a 866 power of 2: 2 = quarter note, 3 = eighth, etc. 867 868 <metronome> expresses the number of MIDI clocks in a metronome click. 869 870 <thirtyseconds> expresses the number of notated 32nd notes in a MIDI 871 quarter note (24 MIDI clocks). 872 873 This event allows a program to relate what MIDI thinks of as a 874 quarter, to something entirely different. 875 876 """ 877 name = 'Time Signature' 878 metacommand = 0x58 879 order = 0 880 881 # Event attributes 882 numerator = 4 883 """ Top part of the time signature. """ 884 denominator = 4 885 """ Bottom part of the time signature (must be a power of 2). """ 886 metronome = 24 887 """ Number of MIDI clocks in a metronome click. """ 888 thirtyseconds = 8 889 """ Number of 32nd notes in 24 MIDI clocks. """ 890
891 - def __str__(self):
892 return "%s [ %d/%d metro: %d 32nds: %d ]" % \ 893 (super(TimeSignatureEvent, self).__str__(), 894 self.numerator, self.denominator, 895 self.metronome, self.thirtyseconds)
896
897 - def decode_data(self):
898 if len(self.data) != 4: 899 raise MidiReadError, "time signature event data should be 4 bytes long" 900 self.numerator = ord(self.data[0]) 901 # Weird: the denominator is two to the power of the data variable 902 self.denominator = 2 ** ord(self.data[1]) 903 self.metronome = ord(self.data[2]) 904 self.thirtyseconds = ord(self.data[3])
905
906 - def encode_data(self):
907 return chr(self.numerator) + \ 908 chr(int(log(self.denominator,2))) + \ 909 chr(self.metronome) + \ 910 chr(self.thirtyseconds)
911
912 913 -class KeySignatureEvent(MetaEvent):
914 """ 915 Sets the key signature, made up of a number of sharps/flats and 916 either major or minor. 917 918 """ 919 name = 'Key Signature' 920 metacommand = 0x59 921 922 # Event attributes 923 accidentals = 0 924 """ Number of sharps/flats. Positive sharps, negative flats. """ 925 major = True 926 """ Major or minor: True for major, False for minor. """ 927
928 - def __str__(self):
929 return "%s [ %d%s %s ]" % \ 930 (super(KeySignatureEvent, self).__str__(), 931 abs(self.accidentals), 932 self.accidentals >= 0 and "#" or "b", 933 self.major and "major" or "minor")
934
935 - def decode_data(self):
936 self.accidentals = ord(self.data[0]) 937 self.major = ord(self.data[1]) == 0
938 939 ##should encode 0 for major, 1 for minor.
940 - def encode_data(self):
941 return chr(self.accidentals) + chr(not self.major)
942
943 -class BeatMarkerEvent(MetaEvent):
944 name = 'Beat Marker' 945 metacommand = 0x7F
946
947 -class SequencerSpecificEvent(MetaEvent):
948 """ 949 Specifies information specific to a hardware or software sequencer. 950 The first data byte (or three bytes if the first byte is 0) 951 specifies the manufacturer's ID and the following bytes contain 952 information specified by the manufacturer. 953 954 Individual manufacturers may document this in their manuals. 955 956 """ 957 name = 'Sequencer Specific' 958 metacommand = 0x7F
959
960 961 -class TempoMap(list):
962 - def __init__(self, stream):
963 self.stream = stream
964
965 - def add_and_update(self, event):
966 self.add(event) 967 self.update()
968
969 - def add(self, event):
970 # get tempo in microseconds per beat 971 tempo = event.mpqn 972 # convert into milliseconds per beat 973 tempo = tempo / 1000.0 974 # generate ms per tick 975 event.mpt = tempo / self.stream.resolution 976 self.append(event)
977
978 - def update(self):
979 self.sort() 980 # adjust running time 981 last = None 982 for event in self: 983 if last: 984 event.msdelay = last.msdelay + \ 985 int(last.mpt * (event.tick - last.tick)) 986 last = event
987
988 - def get_tempo(self, offset=0):
989 if len(self) == 0: 990 # Default tempo is 120 bpm 991 # No tempo exists in the tempo map, so use a default 992 def_tempo = SetTempoEvent() 993 def_tempo.tempo = 120 994 # Set the mpt, as if this has been added to the map 995 def_tempo.mpt = def_tempo.mpqn / 1000.0 / self.stream.resolution 996 return def_tempo 997 else: 998 last = self[0] 999 for tm in self[1:]: 1000 if tm.tick > offset: 1001 return last 1002 last = tm 1003 return last
1004
1005 -class EventStreamIterator(object):
1006 - def __init__(self, stream, window):
1007 self.stream = stream 1008 self.trackpool = stream.trackpool 1009 self.window_length = window 1010 self.window_edge = 0 1011 self.leftover = None 1012 self.events = self.stream.iterevents() 1013 # First, need to look ahead to see when the 1014 # tempo markers end 1015 self.ttpts = [] 1016 for tempo in stream.tempomap[1:]: 1017 self.ttpts.append(tempo.tick) 1018 # Finally, add the end of track tick. 1019 self.ttpts.append(stream.endoftrack.tick) 1020 self.ttpts = iter(self.ttpts) 1021 # Setup next tempo timepoint 1022 self.ttp = self.ttpts.next() 1023 self.tempomap = iter(self.stream.tempomap) 1024 self.tempo = self.tempomap.next() 1025 self.endoftrack = False
1026
1027 - def __iter__(self):
1028 return self
1029
1030 - def __next_edge(self):
1031 if self.endoftrack: 1032 raise StopIteration 1033 lastedge = self.window_edge 1034 self.window_edge += int(self.window_length / self.tempo.mpt) 1035 if self.window_edge > self.ttp: 1036 # We're past the tempo-marker. 1037 oldttp = self.ttp 1038 try: 1039 self.ttp = self.ttpts.next() 1040 except StopIteration: 1041 # End of Track! 1042 self.window_edge = self.ttp 1043 self.endoftrack = True 1044 return 1045 # Calculate the next window edge, taking into 1046 # account the tempo change. 1047 msused = (oldttp - lastedge) * self.tempo.mpt 1048 msleft = self.window_length - msused 1049 self.tempo = self.tempomap.next() 1050 ticksleft = msleft / self.tempo.mpt 1051 self.window_edge = ticksleft + self.tempo.tick
1052
1053 - def next(self):
1054 ret = [] 1055 self.__next_edge() 1056 if self.leftover: 1057 if self.leftover.tick > self.window_edge: 1058 return ret 1059 ret.append(self.leftover) 1060 self.leftover = None 1061 for event in self.events: 1062 if event.tick > self.window_edge: 1063 self.leftover = event 1064 return ret 1065 ret.append(event) 1066 return ret
1067
1068 1069 -class EventStream(object):
1070 """ 1071 Class used to describe a collection of MIDI events, organized into 1072 tracks. 1073 1074 """
1075 - def __init__(self):
1076 self.format = 1 1077 self.tempomap = TempoMap(self) 1078 self._curtrack = None 1079 self.trackpool = [] 1080 self.tracklist = {} 1081 self.timemap = [] 1082 self.endoftracks = {} 1083 self.beatmap = [] 1084 self.resolution = 220 1085 self.tracknames = {}
1086
1087 - def __set_resolution(self, resolution):
1088 # XXX: Add code to rescale notes 1089 assert(not self.trackpool) 1090 self.__resolution = resolution 1091 self.beatmap = [] 1092 for value in BEATVALUES: 1093 self.beatmap.append(int(value * resolution))
1094
1095 - def __get_resolution(self):
1096 return self.__resolution
1097 resolution = property(__get_resolution, __set_resolution, None, 1098 "Ticks per quarter note") 1099
1100 - def add_track(self):
1101 # Move on to the next, new track (track 0 if none exists yet) 1102 self._curtrack = len(self) 1103 self.endoftrack = None 1104 self.tracklist[self._curtrack] = [] 1105 # Type 0 midi files only support one track 1106 # If adding more than one track, make this a type 1 file 1107 if len(self) > 1: 1108 self.format = 1
1109
1110 - def get_current_track_number(self):
1111 return self._curtrack
1112
1113 - def get_track_by_number(self, tracknum):
1114 return self.tracklist[tracknum]
1115
1116 - def get_current_track(self):
1117 return self.tracklist[self._curtrack]
1118
1119 - def get_track_by_name(self, trackname):
1120 tracknum = self.tracknames[trackname] 1121 return self.get_track_by_number(tracknum)
1122
1123 - def replace_current_track(self, track):
1124 self.tracklist[self._curtrack] = track 1125 self.__refresh()
1126
1127 - def replace_track_by_number(self, tracknum, track):
1128 self.tracklist[tracknumber] = track 1129 self.__refresh()
1130
1131 - def replace_track_by_name(self, trackname, track):
1132 tracknum = self.tracklist[tracknum] 1133 self.repdeletelace_track_by_number(tracknum, track)
1134
1135 - def delete_current_track(self):
1136 del self.tracklist[self._curtrack] 1137 self.__refresh()
1138
1139 - def delete_track_by_number(self, tracknum):
1140 del self.tracklist[tracknum] 1141 self.__refresh()
1142
1143 - def delete_track_by_name(self, trackname, track):
1144 tracknum = self.tracklist[trackname] 1145 self.delete_track_by_number(tracknum, track)
1146
1147 - def add_event(self, event):
1148 if not isinstance(event, EndOfTrackEvent): 1149 event.track = self.curtrack 1150 self.trackpool.append(event) 1151 self.track.append(event) 1152 self.__adjust_endoftrack(event) 1153 1154 if isinstance(event, TrackNameEvent): 1155 self.__refresh_tracknames() 1156 if isinstance(event, SetTempoEvent): 1157 self.tempomap.add_and_update(event) 1158 self.__refresh_timemap() 1159 else: 1160 tempo = self.tempomap.get_tempo(event.tick) 1161 event.adjust_msdelay(tempo)
1162
1163 - def _add_event_without_timemap(self, event):
1164 """ 1165 Like add_event, but doesn't update the timemap after adding the event. 1166 You shouldn't usually use this, since it leaves the timemap in an 1167 inconsistent state, but it's used internally when adding a lot of 1168 events in a row. If you use it, you should ensure that _refresh_timemap 1169 gets called afterwards. 1170 1171 """ 1172 if not isinstance(event, EndOfTrackEvent): 1173 event.track = self.curtrack 1174 self.trackpool.append(event) 1175 self.track.append(event) 1176 self.__adjust_endoftrack(event) 1177 1178 if isinstance(event, TrackNameEvent): 1179 self.__refresh_tracknames() 1180 if isinstance(event, SetTempoEvent): 1181 self.tempomap.add_and_update(event)
1182
1183 - def get_tempo(self, offset=0):
1184 return self.tempomap.get_tempo(offset)
1185
1186 - def timesort(self):
1187 self.trackpool.sort() 1188 for track in self.tracklist.values(): 1189 track.sort()
1190
1191 - def textdump(self):
1192 for event in self.trackpool: 1193 print event
1194
1195 - def __iter__(self):
1196 return iter(self.tracklist.values())
1197
1198 - def iterevents(self, mswindow=0):
1199 self.timesort() 1200 if mswindow: 1201 return EventStreamIterator(self, mswindow) 1202 return iter(self.trackpool)
1203 1204 @property
1205 - def duration(self):
1206 """ 1207 The length of the stream in midi ticks. 1208 Note that this is not the same as len(stream), which gives the 1209 number of tracks. 1210 1211 """ 1212 if len(self.trackpool): 1213 return max(self.trackpool).tick 1214 else: 1215 return 0
1216
1217 - def __get_trackcount(self):
1218 return len(self.tracklist)
1219 trackcount = property(__get_trackcount) 1220
1221 - def __len__(self):
1222 return self.trackcount
1223
1224 - def __getitem__(self, intkey):
1225 return self.tracklist[intkey]
1226
1227 - def __refresh(self):
1228 self.__refresh_trackpool() 1229 self.__refresh_tempomap() 1230 self.__refresh_timemap() 1231 self.__refresh_tracknames()
1232
1233 - def __refresh_tracknames(self):
1234 self.tracknames = {} 1235 for tracknum in self.tracklist: 1236 track = self.tracklist[tracknum] 1237 for event in track: 1238 if isinstance(event, TrackNameEvent): 1239 self.tracknames[event.data] = tracknum 1240 break
1241
1242 - def __refresh_trackpool(self):
1243 self.trackpool = [] 1244 for track in self.tracklist.values(): 1245 for event in track: 1246 self.trackpool.append(event) 1247 self.trackpool.sort()
1248
1249 - def __refresh_tempomap(self):
1250 self.tempomap = TempoMap(self) 1251 1252 for tracknum,track in self.tracklist.items(): 1253 self.endoftracks[tracknum] = None 1254 1255 eots = [] 1256 for event in track: 1257 if isinstance(event, SetTempoEvent): 1258 self.tempomap.add(event) 1259 elif isinstance(event, EndOfTrackEvent): 1260 eots.append(event) 1261 # Only allow one EOT in each track 1262 eots.sort() 1263 if len(eots) > 1: 1264 for eot in eots[:-1]: 1265 track.remove(eot) 1266 self.trackpool.remove(eot) 1267 if len(eots): 1268 self.__adjust_endoftrack(eots[-1], track=tracknum) 1269 self.tempomap.update()
1270
1271 - def __refresh_timemap(self):
1272 for event in self.trackpool: 1273 if not isinstance(event, SetTempoEvent): 1274 tempo = self.tempomap.get_tempo(event.tick) 1275 event.adjust_msdelay(tempo)
1276 _refresh_timemap = __refresh_timemap 1277
1278 - def __adjust_endoftrack(self, event, track=None):
1279 """ 1280 Track defaults to the current track. 1281 The event itself is assumed to be in the track already. 1282 1283 """ 1284 if track is None: 1285 track = self.curtrack 1286 1287 if self.endoftracks[track] is None: 1288 if isinstance(event, EndOfTrackEvent): 1289 eot_event = event 1290 else: 1291 # We don't have an EOT event for this track and the 1292 # given event isn't one itself, so make a new one 1293 eot_event = EndOfTrackEvent() 1294 eot_event.tick = event.tick 1295 eot_event.track = self.curtrack 1296 self.trackpool.append(eot_event) 1297 self.tracklist[track].append(eot_event) 1298 self.endoftracks[track] = eot_event 1299 else: 1300 # Update the time of the EOT event already in use 1301 self.endoftracks[track].tick = max(event.tick+1, self.endoftracks[track].tick) 1302 if self.tempomap: 1303 tempo = self.tempomap.get_tempo(self.endoftracks[track].tick) 1304 self.endoftracks[track].adjust_msdelay(tempo)
1305
1306 - def __get_endoftrack(self):
1307 return self.endoftracks[self.curtrack]
1308 - def __set_endoftrack(self, eot):
1309 self.endoftracks[self.curtrack] = eot
1310 endoftrack = property(__get_endoftrack, __set_endoftrack) 1311
1312 - def __get_track_num(self):
1313 return self._curtrack
1314 - def __set_track_num(self, num):
1315 if num not in self.tracklist: 1316 raise ValueError, "track number %d does not exist" % num 1317 self._curtrack = num
1318 curtrack = property(__get_track_num, __set_track_num, 1319 delete_current_track, 1320 """The track number of the current track""") 1321 1322 track = property(get_current_track, replace_current_track, 1323 delete_current_track, 1324 """The current track""") 1325
1326 - def remove_event(self, ev, track=None):
1327 """ 1328 Remove the first occurence of an event matching the 1329 given event from the event stream. Raises a 1330 ValueError a match is not found in the stream. 1331 1332 @type track: int 1333 @param track: track number to look for the event in. If not 1334 given, all tracks are searched. 1335 1336 """ 1337 if track is None: 1338 tracks = self.tracklist.values() 1339 else: 1340 tracks = [self.tracklist[track]] 1341 1342 for trck in tracks: 1343 if ev in trck: 1344 trck.remove(ev) 1345 self.__refresh() 1346 return 1347 1348 if track is None: 1349 track_mess = "" 1350 else: 1351 track_mess = ", track %d" % track 1352 raise ValueError, "remove_event: event %s not found in stream%s" % \ 1353 (ev, track_mess)
1354
1355 - def remove_event_instance(self, ev, track=None):
1356 """ 1357 Removes the event instance from a stream, if it exists. Raises a 1358 ValueError a match is not found in the stream. 1359 1360 Note that this is different from L{remove_event}, which looks 1361 for a match to the argument, whereas this requires identity. 1362 1363 @type track: int 1364 @param track: track number to look for the event in. If not 1365 given, all tracks are searched. 1366 1367 """ 1368 if track is None: 1369 tracks = self.tracklist.values() 1370 else: 1371 tracks = [self.tracklist[track]] 1372 1373 for trck in tracks: 1374 track_ids = [id(e) for e in trck] 1375 if id(ev) in track_ids: 1376 trck.pop(track_ids.index(id(ev))) 1377 self.__refresh() 1378 return 1379 1380 if track is None: 1381 track_mess = "" 1382 else: 1383 track_mess = ", track %d" % track 1384 raise ValueError, "remove_event: event %s not found in stream%s" % \ 1385 (ev, track_mess)
1386
1387 - def remove_event_instances(self, evs):
1388 """ 1389 Has the same effect as calling L{remove_event_instance} on each members 1390 of C{evs}, but is a lot faster if removing many events in one go. 1391 1392 Note that this won't raise an exception if any of the events aren't 1393 found. 1394 1395 """ 1396 remove_ids = [id(e) for e in evs] 1397 1398 for tracknum in self.tracklist.keys(): 1399 self.tracklist[tracknum] = \ 1400 [ev for ev in self.tracklist[tracknum] if id(ev) not in remove_ids] 1401 self.__refresh()
1402
1403 - def slice(self, start, end=None):
1404 from slice import EventStreamSlice 1405 return EventStreamSlice(self, start, end)
1406
1407 - def clean_midi(self):
1408 """ 1409 Unimplemented. Will check for redundant messages and delete them, 1410 such as control changes that are followed by a "Reset Controllers" 1411 message before a note on. 1412 1413 """ 1414 pass
1415
1416 -class EventStreamWriter(object):
1417 """ 1418 Takes care of writing MIDI data from an L{EventStream} to an output 1419 stream, such as a file. 1420 1421 """
1422 - def __init__(self, midistream, output):
1423 if isinstance(output, str): 1424 output = open(output, 'w') 1425 self.output = output 1426 self.midistream = midistream 1427 self.write_file_header() 1428 for i,track in enumerate(self.midistream): 1429 self.write_track(track)
1430
1431 - def write(cls, midistream, output):
1432 cls(midistream, output)
1433 write = classmethod(write) 1434
1435 - def write_file_header(self):
1436 # First four bytes are MIDI header 1437 packdata = pack(">LHHH", 6, 1438 self.midistream.format, 1439 self.midistream.trackcount, 1440 self.midistream.resolution) 1441 self.output.write('MThd%s' % packdata)
1442
1443 - def write_track_header(self, trklen):
1444 self.output.write('MTrk%s' % pack(">L", trklen))
1445
1446 - def write_track(self, track):
1447 buf = '' 1448 track = copy.copy(track) 1449 track.sort() 1450 last_tick = delta = 0 1451 smsg = 0 1452 chn = 0 1453 for event in track: 1454 running = (event.allow_running and (smsg == event.statusmsg) and (chn == event.channel)) 1455 this_encoding=event.encode(last_tick=last_tick, running=running) 1456 buf += this_encoding 1457 last_tick = event.tick 1458 # Only allow status to run if the event is of an appropriate type 1459 if event.allow_running: 1460 smsg = event.statusmsg 1461 chn = event.channel 1462 else: 1463 # Some events cannot have running status 1464 # They cancel any previous running status 1465 smsg = 0 1466 chn = 0 1467 self.write_track_header(len(buf)) 1468 self.output.write(buf)
1469
1470 -class EventStreamReader(object):
1471 """ 1472 Reads MIDI data in from an input stream, probably a file, and 1473 produces an L{EventStream} to represent it internally. 1474 1475 """
1476 - def __init__(self, instream, outstream, force_resolution=None):
1477 self.eventfactory = None 1478 self.force_resolution = force_resolution 1479 self.parse(instream, outstream)
1480
1481 - def read(cls, instream, *args, **kwargs):
1482 if len(args) > 0: 1483 outstream = args[0] 1484 else: 1485 outstream = kwargs.pop('outstream', None) 1486 if not outstream: 1487 outstream = EventStream() 1488 cls(instream, outstream, *args, **kwargs) 1489 return outstream
1490 read = classmethod(read) 1491
1492 - def parse(self, instream, outstream):
1493 self.midistream = outstream 1494 if isinstance(instream, str): 1495 instream = open(instream) 1496 self.instream = instream 1497 if isinstance(instream, file): 1498 self.instream = StringIO(instream.read()) 1499 self.cursor = 0 1500 # XXX: unicode? 1501 else: 1502 # Assume this is a file-like object (like a StringIO) 1503 # without checking the type 1504 self.instream = instream 1505 self.parse_file_header() 1506 for track in range(self.tracks_to_read): 1507 trksz = self.parse_track_header() 1508 self.eventfactory = EventFactory() 1509 self.midistream.add_track() 1510 self.parse_track(trksz) 1511 # We didn't do this while adding each event, so do it once now instead 1512 self.midistream._refresh_timemap()
1513
1514 - def parse_file_header(self):
1515 # First four bytes are MIDI header 1516 magic = self.instream.read(4) 1517 if magic != 'MThd': 1518 raise MidiReadError, "Bad header in MIDI file." 1519 # next four bytes are header size 1520 # next two bytes specify the format version 1521 # next two bytes specify the number of tracks 1522 # next two bytes specify the resolution/PPQ/Parts Per Quarter 1523 # (in other words, how many ticks per quater note) 1524 data = unpack(">LHHH", self.instream.read(10)) 1525 hdrsz = data[0] 1526 self.midistream.format = data[1] 1527 # Let the trackcount be calculated from the number of tracks we find 1528 self.tracks_to_read = data[2] 1529 self.midistream.resolution = self.force_resolution or data[3] 1530 # XXX: the assumption is that any remaining bytes 1531 # in the header are padding 1532 if hdrsz > DEFAULT_MIDI_HEADER_SIZE: 1533 self.instream.read(hdrsz - DEFAULT_MIDI_HEADER_SIZE)
1534
1535 - def parse_track_header(self):
1536 # First four bytes are Track header 1537 magic = self.instream.read(4) 1538 if magic != 'MTrk': 1539 raise MidiReadError, "Bad track header in MIDI file: " + magic 1540 # next four bytes are header size 1541 trksz = unpack(">L", self.instream.read(4))[0] 1542 return trksz
1543
1544 - def parse_track(self, trksz):
1545 track = iter(self.instream.read(trksz)) 1546 while True: 1547 try: 1548 event = self.eventfactory.parse_midi_event(track) 1549 # Don't refresh the timemap for every event 1550 # It's very important that _refresh_timemap gets called after 1551 # this (in parse()), since we don't do it here 1552 self.midistream._add_event_without_timemap(event) 1553 except Warning, err: 1554 print "Warning: %s" % err 1555 except StopIteration: 1556 break
1557
1558 -def check_midi(mid):
1559 """ 1560 Midi debugger. Checks through a midi event stream for things 1561 that might be wrong with it. 1562 1563 @todo: add more possible problems to the checks. 1564 1565 @type mid: L{EventStream} 1566 @param mid: midi stream to check 1567 @return: list of tuples, consisting of a string problems identifier, 1568 a descriptive message and a tuple of midi events that generated 1569 the problem. 1570 1571 """ 1572 problems = [] 1573 for event in mid.trackpool: 1574 if event.tick < 0: 1575 # Negative timing 1576 problems.append( 1577 ("Negative time", "negative time value: %d" % event.tick, (event,)) ) 1578 if event.msdelay < 0: 1579 # Negative timing in the MS time 1580 problems.append( 1581 ("Negative MS time", "negative millisecond time value: %s" % event.msdelay, (event,)) ) 1582 # Check for more potential problems 1583 return problems
1584
1585 -def new_stream(tempo=120, resolution=480, format=1):
1586 stream = EventStream() 1587 stream.format = format 1588 stream.resolution = resolution 1589 stream.add_track() 1590 tempoev = SetTempoEvent() 1591 tempoev.tempo = tempo 1592 tempoev.tick = 0 1593 stream.add_event(tempoev) 1594 return stream
1595 1596 read_midifile = EventStreamReader.read 1597 """ 1598 Reads in a MIDI file given by a file object or filename and returns 1599 an L{EventStream}. 1600 """ 1601 write_midifile = EventStreamWriter.write 1602 """ 1603 Writes an L{EventStream} out to a MIDI file. 1604 """
1605 1606 -class MidiReadError(Exception):
1607 """ 1608 Usually raised on encountering invalid MIDI data while parsing. 1609 Some mildly bad things may be overlooked, but critical errors in 1610 the data will cause the reader to raise a MidiReadError. 1611 1612 """ 1613 pass
1614
1615 -class MidiWriteError(Exception):
1616 pass
1617