class MidiOutFile(MidiOutStream): """ MidiOutFile is an eventhandler that subclasses MidiOutStream. """ def __init__(self, raw_out=''): self.raw_out = RawOutstreamFile(raw_out) super(MidiOutFile, self).__init__(self) def write(self): self.raw_out.write() def event_slice(self, slc): """ Writes the slice of an event to the current track. Correctly inserting a varlen timestamp too. """ trk = self._current_track_buffer trk.writeVarLen(self.rel_time()) trk.writeSlice(slc) ##################### ## Midi events def note_on(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([NOTE_ON + channel, note, velocity]) self.event_slice(slc) def note_off(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([NOTE_OFF + channel, note, velocity]) self.event_slice(slc) def aftertouch(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([AFTERTOUCH + channel, note, velocity]) self.event_slice(slc) def continuous_controller(self, channel, controller, value): """ channel: 0-15 controller, value: 0-127 """ slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value]) self.event_slice(slc) # These should probably be implemented # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums def patch_change(self, channel, patch): """ channel: 0-15 patch: 0-127 """ slc = fromBytes([PATCH_CHANGE + channel, patch]) self.event_slice(slc) def channel_pressure(self, channel, pressure): """ channel: 0-15 pressure: 0-127 """ slc = fromBytes([CHANNEL_PRESSURE + channel, pressure]) self.event_slice(slc) def pitch_bend(self, channel, value): """ channel: 0-15 value: 0-16383 """ msb = (value>>7) & 0xFF lsb = value & 0xFF slc = fromBytes([PITCH_BEND + channel, msb, lsb]) self.event_slice(slc) ##################### ## System Exclusive # def sysex_slice(sysex_type, data): # "" # sysex_len = writeVar(len(data)+1) # self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE) # def system_exclusive(self, data): """ data: list of values in range(128) """ sysex_len = writeVar(len(data)+1) self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE)) ##################### ## Common events def midi_time_code(self, msg_type, values): """ msg_type: 0-7 values: 0-15 """ value = (msg_type<<4) + values self.event_slice(fromBytes([MTC, value])) def song_position_pointer(self, value): """ value: 0-16383 """ lsb = (value & 0x7F) msb = (value >> 7) & 0x7F self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb])) def song_select(self, songNumber): """ songNumber: 0-127 """ self.event_slice(fromBytes([SONG_SELECT, songNumber])) def tuning_request(self): """ No values passed """ self.event_slice(chr(TUNING_REQUEST)) ######################### # header does not really belong here. But anyhoo!!! def header(self, format=0, nTracks=1, division=96): """ format: type of midi file in [0,1,2] nTracks: number of tracks. 1 track for type 0 file division: timing division ie. 96 ppq. """ raw = self.raw_out raw.writeSlice('MThd') bew = raw.writeBew bew(6, 4) # header size bew(format, 2) bew(nTracks, 2) bew(division, 2) def eof(self): """ End of file. No more events to be processed. """ # just write the file then. self.write() ##################### ## meta events def meta_slice(self, meta_type, data_slice): "Writes a meta event" slc = fromBytes([META_EVENT, meta_type]) + \ writeVar(len(data_slice)) + data_slice self.event_slice(slc) def meta_event(self, meta_type, data): """ Handles any undefined meta events """ self.meta_slice(meta_type, fromBytes(data)) def start_of_track(self, n_track=0): """ n_track: number of track """ self._current_track_buffer = RawOutstreamFile() self.reset_time() self._current_track += 1 def end_of_track(self): """ Writes the track to the buffer. """ raw = self.raw_out raw.writeSlice(TRACK_HEADER) track_data = self._current_track_buffer.getvalue() # wee need to know size of track data. eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0]) raw.writeBew(len(track_data)+len(eot_slice), 4) # then write raw.writeSlice(track_data) raw.writeSlice(eot_slice) def sequence_number(self, value): """ value: 0-65535 """ self.meta_slice(SEQUENCE_NUMBER, writeBew(value, 2)) def text(self, text): """ Text event text: string """ self.meta_slice(TEXT, text) def copyright(self, text): """ Copyright notice text: string """ self.meta_slice(COPYRIGHT, text) def sequence_name(self, text): """ Sequence/track name text: string """ self.meta_slice(SEQUENCE_NAME, text) def instrument_name(self, text): """ text: string """ self.meta_slice(INSTRUMENT_NAME, text) def lyric(self, text): """ text: string """ self.meta_slice(LYRIC, text) def marker(self, text): """ text: string """ self.meta_slice(MARKER, text) def cuepoint(self, text): """ text: string """ self.meta_slice(CUEPOINT, text) def midi_ch_prefix(self, channel): """ channel: midi channel for subsequent data (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(channel)) def midi_port(self, value): """ value: Midi port (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(value)) def tempo(self, value): """ value: 0-2097151 tempo in us/quarternote (to calculate value from bpm: int(60,000,000.00 / BPM)) """ hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff) self.meta_slice(TEMPO, fromBytes([hb, mb, lb])) def smtp_offset(self, hour, minute, second, frame, framePart): """ hour, minute, second: 3 bytes specifying the hour (0-23), minutes (0-59) and seconds (0-59), respectively. The hour should be encoded with the SMPTE format, just as it is in MIDI Time Code. frame: A byte specifying the number of frames per second (one of : 24, 25, 29, 30). framePart: A byte specifying the number of fractional frames, in 100ths of a frame (even in SMPTE-based tracks using a different frame subdivision, defined in the MThd chunk). """ self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart])) def time_signature(self, nn, dd, cc, bb): """ nn: Numerator of the signature as notated on sheet music dd: Denominator of the signature as notated on sheet music The denominator is a negative power of 2: 2 = quarter note, 3 = eighth, etc. cc: The number of MIDI clocks in a metronome click bb: The number of notated 32nd notes in a MIDI quarter note (24 MIDI clocks) """ self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb])) def key_signature(self, sf, mi): """ sf: is a byte specifying the number of flats (-ve) or sharps (+ve) that identifies the key signature (-7 = 7 flats, -1 = 1 flat, 0 = key of C, 1 = 1 sharp, etc). mi: is a byte specifying a major (0) or minor (1) key. """ self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi])) def sequencer_specific(self, data): """ data: The data as byte values """ self.meta_slice(SPECIFIC, data)
class MidiOutFile(MidiOutStream): """ MidiOutFile is an eventhandler that subclasses MidiOutStream. Note that some of the methods here overwrite (or even implement) the methods inherited from MidiOutStrem (some of them are not implemented there but only defined). Parameters ---------- raw_out : str, optional. Default: '' the file name of the file to be written. May contain a path to a folder / directory as well. Attributes ---------- raw_out : RawOutstreamFile object this is used to write a MIDI file to the disk. """ def __init__(self, raw_out=''): self.raw_out = RawOutstreamFile(raw_out) MidiOutStream.__init__(self) def write(self): """ calls the write() function of the RawOutstreamFiel object that was created on init of this MidiOutFile instance. """ self.raw_out.write() def event_slice(self, slc): """ Writes the slice of an event to the current track. Correctly inserting a varlen timestamp too. Parameters ---------- slc : slice of an event. """ trk = self._current_track_buffer trk.writeVarLen(self.rel_time()) trk.writeSlice(slc) ##################### ## Midi events def note_on(self, channel=0, note=0x40, velocity=0x40): """ (0x40 = 64) Parameters ---------- channel : number, optional. Default: 0 the MIDI channel number. Possible range is 0-15 note : hex number, optional. Default: 0x40 the MIDI note number. velocity : hex number, optional. Default: 0x40 the note velocity, possible range is 0-127 (0x00 - 0x7F) # channel: 0-15 # note, velocity: 0-127 """ slc = fromBytes([NOTE_ON + channel, note, velocity]) self.event_slice(slc) def note_off(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([NOTE_OFF + channel, note, velocity]) self.event_slice(slc) def aftertouch(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([AFTERTOUCH + channel, note, velocity]) self.event_slice(slc) def continuous_controller(self, channel, controller, value): """ channel: 0-15 controller, value: 0-127 """ slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value]) self.event_slice(slc) # These should probably be implemented # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums def patch_change(self, channel, patch): """ Parameters ---------- channel : number the MIDI channel number, possible values: 0-15 patch : number the patch number (instrument/sound), possible values: 0-127 # channel: 0-15 # patch: 0-127 """ slc = fromBytes([PATCH_CHANGE + channel, patch]) self.event_slice(slc) def channel_pressure(self, channel, pressure): """ channel: 0-15 pressure: 0-127 """ slc = fromBytes([CHANNEL_PRESSURE + channel, pressure]) self.event_slice(slc) def pitch_bend(self, channel, value): """ Parameters ---------- channel : number value : number the pitch bend controller value. Note that this doesn't tell straight away by what amount the pitch is bent (e.g. in cents = 100th part of a semi-tone). CHECK this! # channel: 0-15 # value: 0-16383 """ msb = (value >> 7) & 0xFF lsb = value & 0xFF slc = fromBytes([PITCH_BEND + channel, msb, lsb]) self.event_slice(slc) ##################### ## System Exclusive # def sysex_slice(sysex_type, data): # "" # sysex_len = writeVar(len(data)+1) # self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE) # def system_exclusive(self, data): """ data: list of values in range(128) """ sysex_len = writeVar(len(data) + 1) self.event_slice( chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE)) ##################### ## Common events def midi_time_code(self, msg_type, values): """ Quarter-frame messages. Parameters ---------- msg_type : number range is 0-7 values : number range is 0-15 # msg_type: 0-7 # values: 0-15 """ value = (msg_type << 4) + values self.event_slice(fromBytes([MIDI_TIME_CODE, value])) def song_position_pointer(self, value): """ value: 0-16383 """ lsb = (value & 0x7F) msb = (value >> 7) & 0x7F self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb])) def song_select(self, songNumber): """ songNumber: 0-127 """ self.event_slice(fromBytes([SONG_SELECT, songNumber])) def tuning_request(self): """ No values passed """ self.event_slice(chr(TUNING_REQUEST)) ######################### # header does not really belong here. But anyhoo!!! def header(self, format=0, nTracks=1, division=96): """ Parameters ---------- format : number, optional. Default: 0 the type of MIDI file, possible is: 0, 1, 2 nTracks : number, optional. Default: 1 number of tracks in the MIDI file. division : number, optional. Default: 96 the timing division in PPQ. # format: type of midi file in [0,1,2] # nTracks: number of tracks. 1 track for type 0 file # division: timing division ie. 96 ppq. """ raw = self.raw_out raw.writeSlice('MThd') bew = raw.writeBew bew(6, 4) # header size (in WHAT?) bew(format, 2) bew(nTracks, 2) bew(division, 2) def eof(self): """ End of file. No more events to be processed. Calls `write()`. """ # just write the file then. self.write() ##################### ## meta events def meta_slice(self, meta_type, data_slice): """ Writes a meta event. Whatever that is. Parameters ---------- meta_type : data_slice : """ # EDIT: added try / except for debugging purposes! TG # try: # slc = fromBytes([META_EVENT, meta_type]) + \ # writeVar(len(data_slice)) + data_slice # # self.event_slice(slc) # except: # pass # Original code: slc = fromBytes([META_EVENT, meta_type]) + \ writeVar(len(data_slice)) + data_slice self.event_slice(slc) def meta_event(self, meta_type, data): """ Handles any undefined meta events """ self.meta_slice(meta_type, fromBytes(data)) def start_of_track(self, n_track=0): """ n_track: number of track """ self._current_track_buffer = RawOutstreamFile() self.reset_time() self._current_track += 1 def end_of_track(self): """ Writes the track to the buffer. """ raw = self.raw_out raw.writeSlice(TRACK_HEADER) track_data = self._current_track_buffer.getvalue() # wee need to know size of track data. eot_slice = writeVar(self.rel_time()) + fromBytes( [META_EVENT, END_OF_TRACK, 0]) raw.writeBew(len(track_data) + len(eot_slice), 4) # then write raw.writeSlice(track_data) raw.writeSlice(eot_slice) def sequence_number(self, value): """ value: 0-65535 """ self.meta_slice(meta_type, writeBew(value, 2)) def text(self, text): """ Text event text : str """ self.meta_slice(TEXT, text) def copyright(self, text): """ Copyright notice text : str """ self.meta_slice(COPYRIGHT, text) def sequence_name(self, text): """ Sequence/track name text : str """ self.meta_slice(SEQUENCE_NAME, text) def instrument_name(self, text): """ text : str """ self.meta_slice(INSTRUMENT_NAME, text) def lyric(self, text): """ text : str """ self.meta_slice(LYRIC, text) def marker(self, text): """ text : str """ self.meta_slice(MARKER, text) def cuepoint(self, text): """ text : str """ self.meta_slice(CUEPOINT, text) def midi_ch_prefix(self, channel): """ channel: midi channel for subsequent data (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(channel)) def midi_port(self, value): """ value: Midi port (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(value)) def tempo(self, value): """ value: 0-2097151 tempo in us/quarternote (to calculate value from bpm: int(60,000,000.00 / BPM)) """ hb, mb, lb = (value >> 16 & 0xff), (value >> 8 & 0xff), (value & 0xff) self.meta_slice(TEMPO, fromBytes([hb, mb, lb])) def smtp_offset(self, hour, minute, second, frame, framePart): """ hour, minute, second: 3 bytes specifying the hour (0-23), minutes (0-59) and seconds (0-59), respectively. The hour should be encoded with the SMPTE format, just as it is in MIDI Time Code. frame: A byte specifying the number of frames per second (one of : 24, 25, 29, 30). framePart: A byte specifying the number of fractional frames, in 100ths of a frame (even in SMPTE-based tracks using a different frame subdivision, defined in the MThd chunk). """ self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart])) def time_signature(self, nn, dd, cc, bb): """ Parameters ---------- nn : number the numerator of the notated time signature. The number of beats that make up a full measure. dd : number represents the denominator of the notated time signature but NOT as the written number itself. It is cc : number number of MIDI clocks in a metronome click bb : number the number of notated 32nd notes in a MIDI quarter note (24 MIDI clocks) # nn: Numerator of the signature as notated on sheet music # dd: Denominator of the signature as notated on sheet music # The denominator is a negative power of 2: 2 = quarter # note, 3 = eighth, etc. # cc: The number of MIDI clocks in a metronome click # bb: The number of notated 32nd notes in a MIDI quarter note # (24 MIDI clocks) """ self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb])) def key_signature(self, sf, mi): """ sf: is a byte specifying the number of flats (-ve) or sharps (+ve) that identifies the key signature (-7 = 7 flats, -1 = 1 flat, 0 = key of C, 1 = 1 sharp, etc). mi: is a byte specifying a major (0) or minor (1) key. """ self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi])) def sequencer_specific(self, data): """ data: The data as byte values """ self.meta_slice(SEQUENCER_SPECIFIC, data)
class MidiOutFile(MidiOutStream): """ MidiOutFile is an eventhandler that subclasses MidiOutStream. """ def __init__(self, raw_out=''): self.raw_out = RawOutstreamFile(raw_out) MidiOutStream.__init__(self) def write(self): self.raw_out.write() def event_slice(self, slc): """ Writes the slice of an event to the current track. Correctly inserting a varlen timestamp too. """ trk = self._current_track_buffer trk.writeVarLen(self.rel_time()) trk.writeSlice(slc) ##################### ## Midi events def note_on(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([NOTE_ON + channel, note, velocity]) self.event_slice(slc) def note_off(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([NOTE_OFF + channel, note, velocity]) self.event_slice(slc) def aftertouch(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([AFTERTOUCH + channel, note, velocity]) self.event_slice(slc) def continuous_controller(self, channel, controller, value): """ channel: 0-15 controller, value: 0-127 """ slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value]) self.event_slice(slc) # These should probably be implemented # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums def patch_change(self, channel, patch): """ channel: 0-15 patch: 0-127 """ slc = fromBytes([PATCH_CHANGE + channel, patch]) self.event_slice(slc) def channel_pressure(self, channel, pressure): """ channel: 0-15 pressure: 0-127 """ slc = fromBytes([CHANNEL_PRESSURE + channel, pressure]) self.event_slice(slc) def pitch_bend(self, channel, value): """ channel: 0-15 value: 0-16383 """ msb = (value >> 7) & 0xFF lsb = value & 0xFF slc = fromBytes([PITCH_BEND + channel, msb, lsb]) self.event_slice(slc) ##################### ## System Exclusive # def sysex_slice(sysex_type, data): # "" # sysex_len = writeVar(len(data)+1) # self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE) # def system_exclusive(self, data): """ data: list of values in range(128) """ sysex_len = writeVar(len(data) + 1) self.event_slice( chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE)) ##################### ## Common events def midi_time_code(self, msg_type, values): """ msg_type: 0-7 values: 0-15 """ value = (msg_type << 4) + values self.event_slice(fromBytes([MIDI_TIME_CODE, value])) def song_position_pointer(self, value): """ value: 0-16383 """ lsb = (value & 0x7F) msb = (value >> 7) & 0x7F self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb])) def song_select(self, songNumber): """ songNumber: 0-127 """ self.event_slice(fromBytes([SONG_SELECT, songNumber])) def tuning_request(self): """ No values passed """ self.event_slice(chr(TUNING_REQUEST)) ######################### # header does not really belong here. But anyhoo!!! def header(self, format=0, nTracks=1, division=96): """ format: type of midi file in [0,1,2] nTracks: number of tracks. 1 track for type 0 file division: timing division ie. 96 ppq. """ raw = self.raw_out raw.writeSlice('MThd') bew = raw.writeBew bew(6, 4) # header size bew(format, 2) bew(nTracks, 2) bew(division, 2) def eof(self): """ End of file. No more events to be processed. """ # just write the file then. self.write() ##################### ## meta events def meta_slice(self, meta_type, data_slice): "Writes a meta event" slc = fromBytes([META_EVENT, meta_type]) + \ writeVar(len(data_slice)) + data_slice self.event_slice(slc) def meta_event(self, meta_type, data): """ Handles any undefined meta events """ self.meta_slice(meta_type, fromBytes(data)) def start_of_track(self, n_track=0): """ n_track: number of track """ self._current_track_buffer = RawOutstreamFile() self.reset_time() self._current_track += 1 def end_of_track(self): """ Writes the track to the buffer. """ raw = self.raw_out raw.writeSlice(TRACK_HEADER) track_data = self._current_track_buffer.getvalue() # wee need to know size of track data. eot_slice = writeVar(self.rel_time()) + fromBytes( [META_EVENT, END_OF_TRACK, 0]) raw.writeBew(len(track_data) + len(eot_slice), 4) # then write raw.writeSlice(track_data) raw.writeSlice(eot_slice) def sequence_number(self, value): """ value: 0-65535 """ self.meta_slice(meta_type, writeBew(value, 2)) def text(self, text): """ Text event text: string """ self.meta_slice(TEXT, text) def copyright(self, text): """ Copyright notice text: string """ self.meta_slice(COPYRIGHT, text) def sequence_name(self, text): """ Sequence/track name text: string """ self.meta_slice(SEQUENCE_NAME, text) def instrument_name(self, text): """ text: string """ self.meta_slice(INSTRUMENT_NAME, text) def lyric(self, text): """ text: string """ self.meta_slice(LYRIC, text) def marker(self, text): """ text: string """ self.meta_slice(MARKER, text) def cuepoint(self, text): """ text: string """ self.meta_slice(CUEPOINT, text) def midi_ch_prefix(self, channel): """ channel: midi channel for subsequent data (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(channel)) def midi_port(self, value): """ value: Midi port (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(value)) def tempo(self, value): """ value: 0-2097151 tempo in us/quarternote (to calculate value from bpm: int(60,000,000.00 / BPM)) """ hb, mb, lb = (value >> 16 & 0xff), (value >> 8 & 0xff), (value & 0xff) self.meta_slice(TEMPO, fromBytes([hb, mb, lb])) def smtp_offset(self, hour, minute, second, frame, framePart): """ hour, minute, second: 3 bytes specifying the hour (0-23), minutes (0-59) and seconds (0-59), respectively. The hour should be encoded with the SMPTE format, just as it is in MIDI Time Code. frame: A byte specifying the number of frames per second (one of : 24, 25, 29, 30). framePart: A byte specifying the number of fractional frames, in 100ths of a frame (even in SMPTE-based tracks using a different frame subdivision, defined in the MThd chunk). """ self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart])) def time_signature(self, nn, dd, cc, bb): """ nn: Numerator of the signature as notated on sheet music dd: Denominator of the signature as notated on sheet music The denominator is a negative power of 2: 2 = quarter note, 3 = eighth, etc. cc: The number of MIDI clocks in a metronome click bb: The number of notated 32nd notes in a MIDI quarter note (24 MIDI clocks) """ self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb])) def key_signature(self, sf, mi): """ sf: is a byte specifying the number of flats (-ve) or sharps (+ve) that identifies the key signature (-7 = 7 flats, -1 = 1 flat, 0 = key of C, 1 = 1 sharp, etc). mi: is a byte specifying a major (0) or minor (1) key. """ self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi])) def sequencer_specific(self, data): """ data: The data as byte values """ self.meta_slice(SEQUENCER_SPECIFIC, data)
class MidiOutFile(MidiOutStream): """ MidiOutFile is an eventhandler that subclasses MidiOutStream. Note that some of the methods here overwrite (or even implement) the methods inherited from MidiOutStrem (some of them are not implemented there but only defined). Parameters ---------- raw_out : str, optional. Default: '' the file name of the file to be written. May contain a path to a folder / directory as well. Attributes ---------- raw_out : RawOutstreamFile object this is used to write a MIDI file to the disk. """ def __init__(self, raw_out=''): self.raw_out = RawOutstreamFile(raw_out) MidiOutStream.__init__(self) def write(self): """ calls the write() function of the RawOutstreamFiel object that was created on init of this MidiOutFile instance. """ self.raw_out.write() def event_slice(self, slc): """ Writes the slice of an event to the current track. Correctly inserting a varlen timestamp too. Parameters ---------- slc : slice of an event. """ trk = self._current_track_buffer trk.writeVarLen(self.rel_time()) trk.writeSlice(slc) ##################### ## Midi events def note_on(self, channel=0, note=0x40, velocity=0x40): """ (0x40 = 64) Parameters ---------- channel : number, optional. Default: 0 the MIDI channel number. Possible range is 0-15 note : hex number, optional. Default: 0x40 the MIDI note number. velocity : hex number, optional. Default: 0x40 the note velocity, possible range is 0-127 (0x00 - 0x7F) # channel: 0-15 # note, velocity: 0-127 """ slc = fromBytes([NOTE_ON + channel, note, velocity]) self.event_slice(slc) def note_off(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([NOTE_OFF + channel, note, velocity]) self.event_slice(slc) def aftertouch(self, channel=0, note=0x40, velocity=0x40): """ channel: 0-15 note, velocity: 0-127 """ slc = fromBytes([AFTERTOUCH + channel, note, velocity]) self.event_slice(slc) def continuous_controller(self, channel, controller, value): """ channel: 0-15 controller, value: 0-127 """ slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value]) self.event_slice(slc) # These should probably be implemented # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums def patch_change(self, channel, patch): """ Parameters ---------- channel : number the MIDI channel number, possible values: 0-15 patch : number the patch number (instrument/sound), possible values: 0-127 # channel: 0-15 # patch: 0-127 """ slc = fromBytes([PATCH_CHANGE + channel, patch]) self.event_slice(slc) def channel_pressure(self, channel, pressure): """ channel: 0-15 pressure: 0-127 """ slc = fromBytes([CHANNEL_PRESSURE + channel, pressure]) self.event_slice(slc) def pitch_bend(self, channel, value): """ Parameters ---------- channel : number value : number the pitch bend controller value. Note that this doesn't tell straight away by what amount the pitch is bent (e.g. in cents = 100th part of a semi-tone). CHECK this! # channel: 0-15 # value: 0-16383 """ msb = (value>>7) & 0xFF lsb = value & 0xFF slc = fromBytes([PITCH_BEND + channel, msb, lsb]) self.event_slice(slc) ##################### ## System Exclusive # def sysex_slice(sysex_type, data): # "" # sysex_len = writeVar(len(data)+1) # self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE) # def system_exclusive(self, data): """ data: list of values in range(128) """ sysex_len = writeVar(len(data)+1) self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE)) ##################### ## Common events def midi_time_code(self, msg_type, values): """ Quarter-frame messages. Parameters ---------- msg_type : number range is 0-7 values : number range is 0-15 # msg_type: 0-7 # values: 0-15 """ value = (msg_type << 4) + values self.event_slice(fromBytes([MTC, value])) def song_position_pointer(self, value): """ value: 0-16383 """ lsb = (value & 0x7F) msb = (value >> 7) & 0x7F self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb])) def song_select(self, songNumber): """ songNumber: 0-127 """ self.event_slice(fromBytes([SONG_SELECT, songNumber])) def tuning_request(self): """ No values passed """ self.event_slice(chr(TUNING_REQUEST)) ######################### # header does not really belong here. But anyhoo!!! def header(self, format=0, nTracks=1, division=96): """ Parameters ---------- format : number, optional. Default: 0 the type of MIDI file, possible is: 0, 1, 2 nTracks : number, optional. Default: 1 number of tracks in the MIDI file. division : number, optional. Default: 96 the timing division in PPQ. # format: type of midi file in [0,1,2] # nTracks: number of tracks. 1 track for type 0 file # division: timing division ie. 96 ppq. """ raw = self.raw_out raw.writeSlice('MThd') bew = raw.writeBew bew(6, 4) # header size (in WHAT?) bew(format, 2) bew(nTracks, 2) bew(division, 2) def eof(self): """ End of file. No more events to be processed. Calls `write()`. """ # just write the file then. self.write() ##################### ## meta events def meta_slice(self, meta_type, data_slice): """ Writes a meta event. Whatever that is. Parameters ---------- meta_type : data_slice : """ # EDIT: added try / except for debugging purposes! TG # try: # slc = fromBytes([META_EVENT, meta_type]) + \ # writeVar(len(data_slice)) + data_slice # # self.event_slice(slc) # except: # pass # Original code: slc = fromBytes([META_EVENT, meta_type]) + \ writeVar(len(data_slice)) + data_slice self.event_slice(slc) def meta_event(self, meta_type, data): """ Handles any undefined meta events """ self.meta_slice(meta_type, fromBytes(data)) def start_of_track(self, n_track=0): """ n_track: number of track """ self._current_track_buffer = RawOutstreamFile() self.reset_time() self._current_track += 1 def end_of_track(self): """ Writes the track to the buffer. """ raw = self.raw_out raw.writeSlice(TRACK_HEADER) track_data = self._current_track_buffer.getvalue() # wee need to know size of track data. eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0]) raw.writeBew(len(track_data)+len(eot_slice), 4) # then write raw.writeSlice(track_data) raw.writeSlice(eot_slice) def sequence_number(self, value): """ value: 0-65535 """ self.meta_slice(meta_type, writeBew(value, 2)) def text(self, text): """ Text event text : str """ self.meta_slice(TEXT, text) def copyright(self, text): """ Copyright notice text : str """ self.meta_slice(COPYRIGHT, text) def sequence_name(self, text): """ Sequence/track name text : str """ self.meta_slice(SEQUENCE_NAME, text) def instrument_name(self, text): """ text : str """ self.meta_slice(INSTRUMENT_NAME, text) def lyric(self, text): """ text : str """ self.meta_slice(LYRIC, text) def marker(self, text): """ text : str """ self.meta_slice(MARKER, text) def cuepoint(self, text): """ text : str """ self.meta_slice(CUEPOINT, text) def midi_ch_prefix(self, channel): """ channel: midi channel for subsequent data (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(channel)) def midi_port(self, value): """ value: Midi port (deprecated in the spec) """ self.meta_slice(MIDI_CH_PREFIX, chr(value)) def tempo(self, value): """ value: 0-2097151 tempo in us/quarternote (to calculate value from bpm: int(60,000,000.00 / BPM)) """ hb, mb, lb = (value >> 16 & 0xff), (value >> 8 & 0xff), (value & 0xff) self.meta_slice(TEMPO, fromBytes([hb, mb, lb])) def smtp_offset(self, hour, minute, second, frame, framePart): """ hour, minute, second: 3 bytes specifying the hour (0-23), minutes (0-59) and seconds (0-59), respectively. The hour should be encoded with the SMPTE format, just as it is in MIDI Time Code. frame: A byte specifying the number of frames per second (one of : 24, 25, 29, 30). framePart: A byte specifying the number of fractional frames, in 100ths of a frame (even in SMPTE-based tracks using a different frame subdivision, defined in the MThd chunk). """ self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart])) def time_signature(self, nn, dd, cc, bb): """ Parameters ---------- nn : number the numerator of the notated time signature. The number of beats that make up a full measure. dd : number represents the denominator of the notated time signature but NOT as the written number itself. It is cc : number number of MIDI clocks in a metronome click bb : number the number of notated 32nd notes in a MIDI quarter note (24 MIDI clocks) # nn: Numerator of the signature as notated on sheet music # dd: Denominator of the signature as notated on sheet music # The denominator is a negative power of 2: 2 = quarter # note, 3 = eighth, etc. # cc: The number of MIDI clocks in a metronome click # bb: The number of notated 32nd notes in a MIDI quarter note # (24 MIDI clocks) """ self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb])) def key_signature(self, sf, mi): """ sf: is a byte specifying the number of flats (-ve) or sharps (+ve) that identifies the key signature (-7 = 7 flats, -1 = 1 flat, 0 = key of C, 1 = 1 sharp, etc). mi: is a byte specifying a major (0) or minor (1) key. """ self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi])) def sequencer_specific(self, data): """ data: The data as byte values """ self.meta_slice(SEQUENCER_SPECIFIC, data)