def merge_midi(midis, input_dir, output, default_tempo=500000): '''Merge midi files into one''' pairs = [(int(x[:-4].split('_')[-1]), x) for x in midis] pairs = sorted(pairs, key=lambda x: x[0]) midis = [join(input_dir, x[1]) for x in pairs] mid = MidiFile(midis[0]) # identify the meta messages metas = [] # tempo = default_tempo tempo = default_tempo // 2 for msg in mid: if msg.type is 'set_tempo': tempo = msg.tempo if msg.is_meta: metas.append(msg) for meta in metas: meta.time = int(mido.second2tick(meta.time, mid.ticks_per_beat, tempo)) target = MidiFile() track = MidiTrack() track.extend(metas) target.tracks.append(track) for midi in midis: mid = MidiFile(midi) for msg in mid: if msg.is_meta: continue if msg.type is not 'end_of_track': msg.time = int( mido.second2tick(msg.time, mid.ticks_per_beat, tempo)) track.append(msg) track.append(MetaMessage('end_of_track')) target.save(output)
def to_mido_track( track: Track, channel: Optional[int] = None, use_note_off_message: bool = False, ) -> MidiTrack: """Return a Track object as a mido MidiTrack object. Parameters ---------- track : :class:`muspy.Track` object Track object to convert. use_note_off_message : bool, optional Whether to use note-off messages. If False, note-on messages with zero velocity are used instead. The advantage to using note-on messages at zero velocity is that it can avoid sending additional status bytes when Running Status is employed. Defaults to False. channel : int, optional Channel number. Defaults to 10 for drums and 0 for other instruments. Returns ------- :class:`mido.MidiTrack` object Converted mido MidiTrack object. """ if channel is None: channel = 9 if track.is_drum else 0 # Create a new MIDI track midi_track = MidiTrack() # Track name messages if track.name is not None: midi_track.append(MetaMessage("track_name", name=track.name)) # Program change messages midi_track.append( Message("program_change", program=track.program, channel=channel)) # Note on and note off messages for note in track.notes: midi_track.extend( to_mido_note_on_note_off( note, channel=channel, use_note_off_message=use_note_off_message, )) # End of track message midi_track.append(MetaMessage("end_of_track")) # Convert to delta time to_delta_time(midi_track) return midi_track
def main(): #open spear file with open('Untitled.txt') as f: spear = f.readlines() root_frequency = 440 pb_range = 2 #init midi file mid = MidiFile() tracks = MidiTrack() #init out port midiout = rtmidi.MidiOut() available_ports = midiout.get_ports() if available_ports: midiout.open_port(0) else: midiout.open_virtual_port("Virtual port") #read data from spear file partials = [] for a,b in pairwise(spear[4:]): a_split = a.split(' ') b_split = b.split(' ') partials.append( Partial(a_split[0].replace(',','.'), float(a_split[1].replace(',','.')), float(a_split[2].replace(',','.')), float(a_split[3].replace(',','.')), b_split[0::3], b_split[1::3], b_split[2::3], root_frequency, pb_range, midiout ) ) tracks.extend(partials[-1].note_list) mid.tracks.append(tracks) mid.save('output.mid')
def to_mido_track(track: Track, use_note_on_as_note_off: bool = True) -> MidiTrack: """Return a Track object as a mido MidiTrack object. Parameters ---------- track : :class:`muspy.Track` object Track object to convert. use_note_on_as_note_off : bool Whether to use a note on message with zero velocity instead of a note off message. Returns ------- :class:`mido.MidiTrack` object Converted mido MidiTrack object. """ # Create a new MIDI track midi_track = MidiTrack() # Track name messages if track.name is not None: midi_track.append(MetaMessage("track_name", name=track.name)) # Program change messages channel = 9 if track.is_drum else 0 midi_track.append( Message( "program_change", program=track.program, channel=channel, )) # Note on and note off messages for note in track.notes: midi_track.extend( to_mido_note_on_note_off(note, channel, use_note_on_as_note_off)) # End of track message midi_track.append(MetaMessage("end_of_track")) # Convert to delta time to_delta_time(midi_track) return midi_track
def split_midi(mid_file, target_dir, default_tempo=500000, target_segment_len=1): '''Split midi file into many chunks''' song_name = split(mid_file)[-1][:-4] mid = MidiFile(mid_file) # identify the meta messages metas = [] tempo = default_tempo for msg in mid: if msg.type is 'set_tempo': tempo = msg.tempo if msg.is_meta: metas.append(msg) for meta in metas: meta.time = int(mido.second2tick(meta.time, mid.ticks_per_beat, tempo)) target = MidiFile() track = MidiTrack() track.extend(metas) target.tracks.append(track) prefix = 0 time_elapsed = 0 for msg in mid: # Skip non-note related messages if msg.is_meta: continue time_elapsed += msg.time if msg.type is not 'end_of_track': msg.time = int( mido.second2tick(msg.time, mid.ticks_per_beat, tempo)) track.append(msg) if msg.type is 'end_of_track' or time_elapsed >= target_segment_len: track.append(MetaMessage('end_of_track')) target.save(join(target_dir, song_name + '_{}.mid'.format(prefix))) target = MidiFile() track = MidiTrack() track.extend(metas) target.tracks.append(track) time_elapsed = 0 prefix += 1
def merge_midi(self, input_dir, output, tempo_override=None): '''Merge midi files into one''' midis = [] for midi in listdir(input_dir): midis.append(join(input_dir, midi)) pairs = [(int(x[:-4].split('_')[-1]), x) for x in midis] pairs = sorted(pairs, key=lambda x: x[0]) midis = [join(input_dir, x[1]) for x in pairs] mid = MidiFile(midis[0]) metas = [] tempo = self.__default_tempo if tempo_override: tempo = tempo_override for msg in mid: if msg.type is 'set_tempo': tempo = msg.tempo if msg.is_meta: metas.append(msg) for meta in metas: meta.time = int( mido.second2tick(meta.time, mid.ticks_per_beat, tempo)) target = MidiFile() track = MidiTrack() track.extend(metas) target.tracks.append(track) for midi in midis: mid = MidiFile(midi) for msg in mid: if msg.is_meta: continue if msg.type is not 'end_of_track': msg.time = int( mido.second2tick(msg.time, mid.ticks_per_beat, tempo)) track.append(msg) track.append(MetaMessage('end_of_track')) target.ticks_per_beat = mid.ticks_per_beat target.save(output) for msg in target: print(msg)
def quantize_track(track, ticks_per_quarter, quantization): '''Return the differential time stamps of the note_on, note_off, and end_of_track events, in order of appearance, with the note_on events quantized to the grid given by the quantization. Arguments: track -- MIDI track containing note event and other messages ticks_per_quarter -- The number of ticks per quarter note quantization -- The note duration, represented as 1/2**quantization.''' pp = pprint.PrettyPrinter() # Message timestamps are represented as differences between # consecutive events. Annotate messages with cumulative timestamps. # Assume the following structure: # [header meta messages] [note messages] [end_of_track message] first_note_msg_idx = None for i, msg in enumerate(track): if msg.type == 'note_on': first_note_msg_idx = i break cum_msgs = list( zip(np.cumsum([msg.time for msg in track[first_note_msg_idx:]]), [msg for msg in track[first_note_msg_idx:]])) end_of_track_cum_time = cum_msgs[-1][0] quantized_track = MidiTrack() quantized_track.extend(track[:first_note_msg_idx]) # Keep track of note_on events that have not had an off event yet. # note number -> message open_msgs = defaultdict(list) quantized_msgs = [] for cum_time, msg in cum_msgs: if DEBUG: print('Message:', msg) print('Open messages:') pp.pprint(open_msgs) if msg.type == 'note_on' and msg.velocity > 0: # Store until note off event. Note that there can be # several note events for the same note. Subsequent # note_off events will be associated with these note_on # events in FIFO fashion. open_msgs[msg.note].append((cum_time, msg)) elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0): # assert msg.note in open_msgs, \ # 'Bad MIDI. Cannot have note off event before note on event' if msg.note not in open_msgs: print( 'Bad MIDI. Cannot have note off event before note on event' ) return note_on_open_msgs = open_msgs[msg.note] if len(note_on_open_msgs) == 0: print('Bad MIDI, Note has no end time.') return # assert len(note_on_open_msgs) > 0, 'Bad MIDI, Note has no end time.' note_on_cum_time, note_on_msg = note_on_open_msgs[0] open_msgs[msg.note] = note_on_open_msgs[1:] # Quantized note_on time quantized_note_on_cum_time = quantize_tick(note_on_cum_time, ticks_per_quarter, quantization) # The cumulative time of note_off is the quantized # cumulative time of note_on plus the orginal difference # of the unquantized cumulative times. quantized_note_off_cum_time = quantized_note_on_cum_time + ( cum_time - note_on_cum_time) quantized_msgs.append( (min(end_of_track_cum_time, quantized_note_on_cum_time), note_on_msg)) quantized_msgs.append((min(end_of_track_cum_time, quantized_note_off_cum_time), msg)) if DEBUG: print('Appended', quantized_msgs[-2:]) elif msg.type == 'end_of_track': quantized_msgs.append((cum_time, msg)) if DEBUG: print('\n') # Now, sort the quantized messages by (cumulative time, # note_type), making sure that note_on events come before note_off # events when two event have the same cumulative time. Compute # differential times and construct the quantized track messages. quantized_msgs.sort(key=lambda cum_time_msg2: cum_time_msg2[0] if (cum_time_msg2[1].type == 'note_on' and cum_time_msg2[ 1].velocity > 0) else cum_time_msg2[0] + 0.5) diff_times = [quantized_msgs[0][0]] + list( np.diff([msg[0] for msg in quantized_msgs])) for diff_time, (cum_time, msg) in zip(diff_times, quantized_msgs): quantized_track.append(msg.copy(time=diff_time)) if DEBUG: print('Quantized messages:') pp.pprint(quantized_msgs) pp.pprint(diff_times) return quantized_track
def quantize_track(track, ticks_per_quarter, quantization): '''Return the differential time stamps of the note_on, note_off, and end_of_track events, in order of appearance, with the note_on events quantized to the grid given by the quantization. Arguments: track -- MIDI track containing note event and other messages ticks_per_quarter -- The number of ticks per quarter note quantization -- The note duration, represented as 1/2**quantization.''' pp = pprint.PrettyPrinter() # Message timestamps are represented as differences between # consecutive events. Annotate messages with cumulative timestamps. # Assume the following structure: # [header meta messages] [note messages] [end_of_track message] first_note_msg_idx = None for i, msg in enumerate(track): if msg.type == 'note_on': first_note_msg_idx = i break cum_msgs = zip( np.cumsum([msg.time for msg in track[first_note_msg_idx:]]), [msg for msg in track[first_note_msg_idx:]]) end_of_track_cum_time = cum_msgs[-1][0] quantized_track = MidiTrack() quantized_track.extend(track[:first_note_msg_idx]) # Keep track of note_on events that have not had an off event yet. # note number -> message open_msgs = defaultdict(list) quantized_msgs = [] for cum_time, msg in cum_msgs: if DEBUG: print msg pp.pprint(open_msgs) if msg.type == 'note_on' and msg.velocity > 0: # Store until note off event. Note that there can be # several note events for the same note. Subsequent # note_off events will be associated with these note_on # events in FIFO fashion. open_msgs[msg.note].append((cum_time, msg)) elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0): assert msg.note in open_msgs, \ 'Bad MIDI. Cannot have note off event before note on event' note_on_open_msgs = open_msgs[msg.note] note_on_cum_time, note_on_msg = note_on_open_msgs[0] open_msgs[msg.note] = note_on_open_msgs[1:] # Quantized note_on time quantized_note_on_cum_time = quantize_tick( note_on_cum_time, ticks_per_quarter, quantization) # The cumulative time of note_off is the quantized # cumulative time of note_on plus the orginal difference # of the unquantized cumulative times. quantized_note_off_cum_time = quantized_note_on_cum_time + (cum_time - note_on_cum_time) quantized_msgs.append((min(end_of_track_cum_time, quantized_note_on_cum_time), note_on_msg)) quantized_msgs.append((min(end_of_track_cum_time, quantized_note_off_cum_time), msg)) elif msg.type == 'end_of_track': quantized_msgs.append((cum_time, msg)) # Now, sort the quantized messages by (cumulative time, # note_type), making sure that note_on events come before note_off # events when two event have the same cumulative time. Compute # differential times and construct the quantized track messages. quantized_msgs.sort( key=lambda (cum_time, msg): cum_time if (msg.type=='note_on' and msg.velocity > 0) else cum_time + 0.5) diff_times = [0] + list( np.diff([ msg[0] for msg in quantized_msgs ])) for diff_time, (cum_time, msg) in zip(diff_times, quantized_msgs): quantized_track.append(msg.copy(time=diff_time)) if DEBUG: pp.pprint(quantized_msgs) pp.pprint(diff_times) return quantized_track
#!/usr/bin/env python """ Create a new MIDI file with some random notes. The file is saved to example.mid. """ import random from mido import Message, MidiFile, MidiTrack, MetaMessage notes = [64, 64+7, 64+12] outfile = MidiFile() track = MidiTrack() outfile.tracks.append(track) track.extend([ MetaMessage('track_name', name='Melody'), Message('program_change', program=12), ]) delta = 16 for i in range(4): note = random.choice(notes) track.append(Message('note_on', note=note, velocity=100, time=delta)) track.append(Message('note_off', note=note, velocity=100, time=delta)) outfile.save('example.mid')
def quantize_track(track, ticks_per_quarter, quantization): '''Return the differential time stamps of the note_on, note_off, and end_of_track events, in order of appearance, with the note_on events quantized to the grid given by the quantization. Arguments: track -- MIDI track containing note event and other messages ticks_per_quarter -- The number of ticks per quarter note quantization -- The note duration, represented as 1/2**quantization.''' # Message timestamps are represented as differences between # consecutive events. Annotate messages with cumulative timestamps. # Assume the following structure: # [header meta messages] [note messages] [end_of_track message] first_note_msg_idx = None for i, msg in enumerate(track): if msg.type == 'note_on': first_note_msg_idx = i break cum_msgs = list( zip(np.cumsum([msg.time for msg in track[first_note_msg_idx:]]), [msg for msg in track[first_note_msg_idx:]])) end_of_track_cum_time = cum_msgs[-1][0] quantized_track = MidiTrack() quantized_track.extend(track[:first_note_msg_idx]) quantized_msgs = [] index = 0 # Iterate through all the MIDI messages searching for 'note on' events. for cum_time, msg in cum_msgs: if msg.type == 'note_on' and msg.velocity > 0: # For each 'note on' event, find the next corresponding 'note off' # event for the same note value. for other_cum_time, other_msg in cum_msgs[index:]: if ((other_msg.type == 'note_off' or (other_msg.type == 'note_on' and other_msg.velocity == 0)) and msg.note == other_msg.note): # Quantized 'note on' time. quantized_note_on_cum_time = quantize_tick( cum_time, ticks_per_quarter, quantization) # The cumulative time of a 'note off' event is the quantized # cumulative time of the associated 'note on' plus the # original difference of the unquantized cumulative times. quantized_note_off_cum_time = quantized_note_on_cum_time + \ (other_cum_time - cum_time) quantized_msgs.append( (min(end_of_track_cum_time, quantized_note_on_cum_time), msg)) quantized_msgs.append( (min(end_of_track_cum_time, quantized_note_off_cum_time), other_msg)) # print('Appended', quantized_msgs[-2:]) break elif msg.type == 'end_of_track': quantized_msgs.append((cum_time, msg)) index += 1 # Now, sort the quantized messages by (cumulative time, note_type), making # sure that note_on events come before note_off events when two event have # the same cumulative time. Compute differential times and construct the # quantized track messages. def sort_msg(args): cum_time, msg = args return cum_time if (msg.type == 'note_on' and msg.velocity > 0) else (cum_time + 0.5) quantized_msgs.sort(key=sort_msg) diff_times = [quantized_msgs[0][0]] + list( np.diff([msg[0] for msg in quantized_msgs])) for diff_time, (cum_time, msg) in zip(diff_times, quantized_msgs): quantized_track.append(msg.copy(time=diff_time)) return quantized_track
def split_midi(self, mid_file, destinationDir=""): '''Split midi file into many chunks''' if destinationDir is "": destinationDir = self.__destinationDir print(destinationDir) song_name = split(mid_file)[-1][:-4] print(song_name) mid = MidiFile(mid_file) self.__chunks[mid_file] = [] # identify the meta messages metas = [] tempo = self.__default_tempo for msg in mid: if msg.type == 'set_tempo': tempo = msg.tempo if msg.is_meta: metas.append(msg) target = MidiFile() track = MidiTrack() for i in range(len(metas)): metas[i].time = int(mido.second2tick(metas[i].time, 960, tempo)) track.extend(metas) target.tracks.append(track) for msg in mid: # Skip non-note related messages if msg.is_meta: continue msg.time = int(mido.second2tick(msg.time, 960, tempo)) track.append(msg) target.save("test.mid") mid = MidiFile("test.mid") self.__chunks["test.mid"] = [] # identify the meta messages metas = [] tempo = self.__default_tempo tpb = mid.ticks_per_beat for msg in mid: if msg.type == 'set_tempo': tempo = msg.tempo if msg.is_meta: metas.append(msg) for i in range(len(metas)): metas[i].time = 0 target = MidiFile() track = MidiTrack() track.extend(metas) target.tracks.append(track) prefix = 0 time_elapsed = 0 ct = 0 for msg in mid: # Skip non-note related messages if msg.is_meta: continue time_elapsed += msg.time t = msg.time if msg.type is not 'end_of_track': msg.time = int( mido.second2tick(msg.time, mid.ticks_per_beat, tempo)) track.append(msg) if msg.type in ['note_on', 'note_off']: ct += 1 if msg.type is 'end_of_track' or time_elapsed >= self.__target_segment_len: track.append(MetaMessage('end_of_track')) target.ticks_per_beat = mid.ticks_per_beat if ct > 0: for i in range(int(t / self.__target_segment_len)): dest = join(destinationDir, song_name + '_{}.mid'.format(prefix)) target.save(dest) prefix += 1 self.__chunks[mid_file].append(dest) target = MidiFile() track = MidiTrack() track.extend(metas) target.tracks.append(track) time_elapsed = 0 ct = 0 return tempo, tpb