def process_track_chunk(data): log.info("Parsing Track Chunk...") events = [] running_status = None while len(data) > 0: data, delta = variable_length_field(data) prefix = data[:8] if prefix == F0_SYSEX_EVENT_PREFIX or prefix == F7_SYSEX_EVENT_PREFIX: data, event = process_sysex_event(prefix, data[8:]) events.append((delta, event)) elif prefix == META_EVENT_PREFIX: data, event = process_meta_event(data[8:]) events.append((delta, event)) else: data, event, running_status = process_midi_event(data, running_status) events.append((delta, event)) if events[-1][1]["sub_type"] != "End of Track": raise Exception("End of Track event missing") return { "type": TRACK, "events": events }
def test_variable_length_decoding_of_0_returns_correct_result(self): input = BitArray("0x00") remainder, extracted = variable_length_field(input) self.assertEqual(extracted, 0) self.assertEqual(remainder, BitArray())
def test_variable_length_decoding_of_268435455_with_excess_data_returns_correct_result_and_correct_remainder( self): input = BitArray("0xFFFFFF7F12304FABC") remainder, extracted = variable_length_field(input) self.assertEqual(extracted, 268435455) self.assertEqual(remainder, BitArray("0x12304FABC"))
def process_sysex_event(prefix, data): data, length = variable_length_field(data) if prefix == F0_SYSEX_EVENT_PREFIX: subtype = "F0" elif prefix == F7_SYSEX_EVENT_PREFIX: subtype = "F7" else: raise Exception( "Tried to process Sysex event but invalid prefix {} found.\nExiting..." .format(prefix)) event = {"type": "SYSEX", "sub_type": subtype, "data": data[:8 * length]} return data[:8 * length], event
def process_meta_event(data): type = data[:8].hex data, length = variable_length_field(data[8:]) event_data = data[:length * 8] remainder = data[length * 8:] event = {"type": META, "sub_type": "Unknown", "data": event_data} # TODO make sure that these comparisons are case correct/case-insensitive if type == "00": # This is an optional event, which must occur only at the start of a track, before any non-zero delta-time. # # For Format 2 MIDI files, this is used to identify each track.If omitted, the sequences are numbered # sequentially in the order the tracks appear. # # For Format 1 files, this event should occur on the first track only. event["sub_type"] = "Sequence Number" event["sequence_number"] = event_data[:length * 8].bytes elif type == "01": event["sub_type"] = "Text Event" event["text"] = event_data[:length * 8].bytes elif type == "02": event["sub_type"] = "Copyright Notice" event["text"] = event_data[:length * 8].bytes elif type == "03": event["sub_type"] = "Sequence/Track Name" event["text"] = event_data[:length * 8].bytes elif type == "04": event["sub_type"] = "Instrument Name" event["text"] = event_data[:length * 8].bytes elif type == "05": event["sub_type"] = "Lyric" event["text"] = event_data[:length * 8].bytes elif type == "06": event["sub_type"] = "Marker" event["text"] = event_data[:length * 8].bytes elif type == "07": event["sub_type"] = "Cue Point" event["text"] = event_data[:length * 8].bytes elif type == "20": # MIDI Channel Prefix # Associate all following meta-events and sysex-events with the specified MIDI channel, until the next # <midi_event> (which must contain MIDI channel information). if length != 1: log.error("Channel Prefix Event has the wrong length!\nExiting...") exit(1) event["sub_type"] = "MIDI Channel Prefix" event["channel"] = event_data[:8].hex elif type == "21": # MIDI Prefix Port if length != 1: print("MIDI Prefix Port event has the wrong length!\nExiting...") exit(1) event["sub_type"] = "MIDI Prefix Port" event["device"] = event_data[:8] elif type == "2f": print("End of Track") # This event is not optional. # It is used to give the track a clearly defined length, which is essential information if the track is looped # or concatenated with another track if length: print("End of Track event should not have any length!\nExiting...") exit(1) event["sub_type"] = "End of Track" elif type == "51": # Set Tempo # This sets the tempo in microseconds per quarter note. This means a change in the unit-length of a delta-time # tick. (note 1) # If not specified, the default tempo is 120 beats/minute, which is equivalent to tttttt=500000 if length != 3: print("Set Tempo event has the wrong length!\nExiting...") exit(1) event["sub_type"] = "Set Tempo" event["new_tempo"] = event_data[:8 * 3] elif type == "54": # SMTPE Offset # This (optional) event specifies the SMTPE time at which the track is to start. # This event must occur before any non-zero delta-times, and before any MIDI events. # In a format 1 MIDI file, this event must be on the first track (the tempo map). if length != 5: print("SMTPE Offset event has the wrong length!\nExiting...") exit(1) event["sub_type"] = "SMTPE Offset" event["hours"] = event_data[:8] event["minutes"] = event_data[8:16] event["seconds"] = event_data[16:24] event["frames"] = event_data[24:32] event["fractional_frames"] = event_data[32:40] elif type == "58": # Time Signature if length != 4: print("Time Signature event has the wrong length!\nExiting...") exit(1) event["sub_type"] = "Time Signature" event["numerator"] = event_data[:8].int event["denominator"] = event_data[8:16].int # MIDI clocks per metronome click event["clocks_per_tick"] = event_data[16:24].int # number of 1/32 notes per 24 MIDI clocks (8 is standard) event["32nd_notes_per_24_clocks"] = event_data[24:32].int elif type == "59": # Key Signature # Key Signature, expressed as the number of sharps or flats, and a major/minor flag. # 0 represents a key of C, negative numbers represent 'flats', while positive numbers represent 'sharps'. if length != 2: print("Key Signature event has the wrong length!\nExiting...") exit(1) event["sub_type"] = "Key Signature" event["sharps_flats"] = event_data[:8].int event["major_minor"] = event_data[8:16].int elif type == "7F": # This is the MIDI-file equivalent of the System Exclusive Message. # A manufacturer may incorporate sequencer-specific directives into a MIDI file using this event. # consists of <id> + <data>, length is length of both of these fields combined # <id> is either one or three bytes, and is the Manufacturer ID # This value is the same as is used for MIDI System Exclusive messages # <data> 8-bit binary data event["sub_type"] = "Sequencer-Specific Meta-event" event["data"] = event_data[:length * 8] else: log.warning("Unrecognised Meta event: {}".format(type)) return remainder, event