def end_track(track): track.events.append(DeltaTime(track=track, time=0)) end_of_track = MidiEvent(track=track, type='END_OF_TRACK') end_of_track.data = b'' track.events.append(end_of_track) return track
def write_starting_messages(track): no_time_spacing = DeltaTime(track, time=0) track.events.append(no_time_spacing) channel_prefix = MidiEvent(track=track, type=midi.MetaEvents.MIDI_CHANNEL_PREFIX) channel_prefix.data = b'\x00' track.events.append(channel_prefix) track.events.append(no_time_spacing) track_name = MidiEvent(track=track, type=MetaEvents.SEQUENCE_TRACK_NAME) track_name.data = bytes("Quantized Midi", 'ascii') track.events.append(track_name) # track.events.append(no_time_spacing) # smtpe_offset = MidiEvent(track=track, type='SMTPE_OFFSET') # smtpe_offset.data = b'\x00\x00\x00\x00' # track.events.append(smtpe_offset) # # track.events.append(no_time_spacing) # SET_TEMPO should be MetaEvents.SET_TEMPO # tempo = MidiEvent(track=track, type='SET_TEMPO') # tempo.data = b'\x05\xe8\x19'#b'\x07\xA1\x20' # track.events.append(tempo) # track.events.append(no_time_spacing) # # time_signature = MidiEvent(track=track, type='TIME_SIGNATURE') # time_signature.data = b'\x04\x02\x18\x08' # track.events.append(time_signature) track.events.append(no_time_spacing) program_change = MidiEvent(track=track, type=ChannelVoiceMessages.PROGRAM_CHANGE) program_change.channel = 1 program_change.data = 1 track.events.append(program_change) # track.events.append(no_time_spacing) # # Controller change should be ChannelVoiceMessages.CONTROLLER_CHANGE now # controller_change = MidiEvent(track=track, type='CONTROLLER_CHANGE') # controller_change.channel = 1 # controller_change.parameter1 = 64 # controller_change.parameter2 = 127 # track.events.append(controller_change) # track.events.append(no_time_spacing) return track
def quantized_to_midi(quantized_data): resulting_midi = MidiFile() resulting_midi.format = 0 resulting_midi.ticksPerQuarterNote = 400 track = MidiTrack(0) track.events.append(MidiEvent()) track.setChannel(1)
def end_notes(notes_to_end, track, first_delta_time_to_use): first = True time_written = 0 for note in notes_to_end: if first: track.events.append(DeltaTime(track=track, time=first_delta_time_to_use)) time_written = first_delta_time_to_use first = False else: track.events.append(DeltaTime(track=track, time=0)) end_note = MidiEvent(track, type=ChannelVoiceMessages.NOTE_OFF) end_note.pitch = note[0] end_note.channel = 1 end_note.parameter2 = 0 track.events.append(end_note) return time_written
def make_midi(quantized_data, ticks_per_sixteenth_note): result = MidiFile() result.format = 0 result.ticksPerQuarterNote = ticks_per_sixteenth_note * 4 track = write_starting_messages() beats_passed = 0 notes_to_end = [] leading_delta_time_written = False last_event_written_at_beat = 0 for beat in quantized_data: to_write_end = find_notes_to_remove(notes_to_end) last_event_written_at_beat += int( end_notes(to_write_end, track, (beats_passed - last_event_written_at_beat) * ticks_per_sixteenth_note) / ticks_per_sixteenth_note) for note in beat: if note[1] > 0: time = (beats_passed - last_event_written_at_beat) * ticks_per_sixteenth_note spacing = DeltaTime(track=track, time=time) track.events.append(spacing) last_event_written_at_beat = beats_passed note_on = MidiEvent(track=track, type='NOTE_ON') note_on.velocity = 50 note_on.pitch = note[0] note_on.channel = 1 track.events.append(note_on) notes_to_end.append(note) beats_passed += 1 # toRemove = [] # # for note in notes_to_end: # if note[1] == 0: # time = (beats_passed - last_event_written_at_beat) * ticks_per_sixteenth_note # spacing = DeltaTime(track=track, time=time) # track.events.append(spacing) # # last_event_written_at_beat = beats_passed # # note_off = MidiEvent(track=track, type='NOTE_OFF') # note_off.pitch = note[0] # note_off.channel = 1 # note_off.parameter2 = 0 # toRemove.append(note) # # note[1] -= 1 # beats_passed += 1 # # for note in toRemove: # if note[1] <= 0: # notes_to_end.remove(note) end_notes(notes_to_end, track, ticks_per_sixteenth_note * 2) end_track(track) result.tracks.append(track) return result
def write_midi(pitch_block, duration_block, outfile="out.mid", qpm_multiplier=1024, tempo_multiplier=1.0): # Assumes any element with from music21.midi import MidiTrack, MidiFile, MidiEvent, DeltaTime # duration, pitch, velocity qpm_mult = qpm_multiplier all_mt = [] for i in range(pitch_block.shape[0]): mt = MidiTrack(1) t = 0 t_last = 0 pitch_slice = pitch_block[i, :] duration_slice = duration_block[i, :] beat_slice = list((qpm_mult * duration_slice).astype("int32")) pitch_slice = list(pitch_slice.astype("int32")) for d, p in zip(beat_slice, pitch_slice): if (p == -1) or (d == -1): # bypass continue dt = DeltaTime(mt) dt.time = t - t_last mt.events.append(dt) me = MidiEvent(mt) me.type = "NOTE_ON" me.channel = 1 me.time = None me.pitch = p me.velocity = 90 mt.events.append(me) # add note off / velocity zero message dt = DeltaTime(mt) dt.time = d # add to track events mt.events.append(dt) me = MidiEvent(mt) me.type = "NOTE_ON" me.channel = 1 me.time = None me.pitch = p me.velocity = 0 mt.events.append(me) t_last = t + d t += d # add end of track dt = DeltaTime(mt) dt.time = 0 mt.events.append(dt) me = MidiEvent(mt) me.type = "END_OF_TRACK" me.channel = 1 me.data = '' mt.events.append(me) all_mt.append(mt) mf = MidiFile() mf.ticksPerQuarterNote = int(tempo_multiplier * qpm_mult) for mt in all_mt: mf.tracks.append(mt) mf.open(outfile, 'wb') mf.write() mf.close()
def process_msg(msg): """ Adds a new `music21.midi.Note` to `score` (`music21.stream.Score`) if it complete a note, otherwise just updates `curr_time_s`. Argument `msg` should be a `mido.Message` """ global first_message_time_s global last_ticks global curr_time_s if first_message_time_s is None: first_message_time_s = msg.time # This is assuming that the time is tracked by adding all the delta times # that I ultimately get from rtmidi. # TODO need to check if the msg has this attribute? curr_time_s += msg.time # TODO also extend to those midi pads? requires that ~polytouch type, right? if msg.type not in ('note_on', 'note_off'): return # TODO TODO deal w/ pitch bends and stuff like that. music21 docs seem to # indicate some of its fns already deal with that, so i feel like i'm not # taking full advantage of the functionality already in the library... # TODO TODO TODO how to use deltatime w/o drifting away from true bpm due to # rounding????? (actually, how i'm doing it might be fine. check!!!) # TODO TODO TODO check assumption that delta time increments between each # msg (how do the ableton clock messages help then? how would i use any info # they have to improve current midi time est? is it just a matter of how # frequently they are received, or do they have other info that maybe i'm # just not getting?) # TODO TODO TODO if there is a quarter note in every position from 1.1.1, # this should equal ticks_per_quarter note at the note_off for the first # quarter note. why is it 4571 rather than 1024=ticks_per_quarter_note??? ticks = secs2ticks(curr_time_s, offset_s=first_message_time_s) print('ticks:', ticks) d_ticks = ticks - last_ticks print('d_ticks:', d_ticks) # The conditional above should guarantee this `note` attribute exists. note_num = msg.note # Generating approprate input for `midiEventsToNote` as specified in # `music21` docs. # TODO TODO check, but it seems like rtmidi 0 indexes these, while ableton # and mido 1-index them? channel = msg.channel + 1 if msg.type == 'note_on': assert note_num not in note_num2start_data # TODO any reason not to specify the channel? #delta1 = DeltaTime(midi_track, time=d_ticks, channel=msg.channel) delta1 = DeltaTime(midi_track, time=d_ticks) # TODO TODO leaving type=None of default OK? does that select some # appropriate version of the "ChannelVoiceMessages" the docs talk about? # TODO TODO del tim=0 here and below once i figure out whawt was causing # "type NoneType doesn't define __round__ method" error... on = MidiEvent(midi_track, type=CVM.NOTE_ON, channel=channel, time=0) # TODO refactor? print('msg.note:', msg.note) on.pitch = msg.note # TODO TODO TODO why is this None despite being set to an int? print('on.pitch:', on.pitch) print() on.velocity = msg.velocity note_num2start_data[note_num] = (ticks, delta1, on) elif msg.type == 'note_off': abs_on_ticks, delta1, on = note_num2start_data[note_num] # TODO TODO TODO or should this be from the last midi event??? # that feels more consistent... (though it also seems harder for music21 # to handle that...) # TODO TODO maybe test w/ single notes first # TODO delete ticks from start_data if not used #d_ticks = ticks - abs_on_ticks # TODO how are tracks used? will i only ever want one? # TODO see CVM.PITCH_BEND re: how to handle pitch bends (though prob. # not a complete solution right there...) #delta2 = DeltaTime(midi_track, time=d_ticks, channel=msg.channel) delta2 = DeltaTime(midi_track, time=d_ticks) # TODO why are there `time` fields for MidiEvent, if DeltaTime # is used for midiEventsToNote? (docs say it's non-essential as # DeltaTime is more important, so prob shouldn't worry) off = MidiEvent(midi_track, type=CVM.NOTE_OFF, channel=channel, time=0) # These variables aren't in the constructor (at least accoring to the # docs) for some reason. # TODO need to remap the range here, or good as-is? off.pitch = msg.note print('off.pitch:', off.pitch) off.velocity = msg.velocity # TODO need to set the ticksPerQuarter=None kwarg to this? and how # should i derive the appropriate input from the midi data i'm getting # from ableton? (i think i should set it, yes, but what happens if i # don't?) try: note = music21.midi.translate.midiEventsToNote( [delta1, on, delta2, off], ticksPerQuarter=ticks_per_quarter_note #[(delta1, on), (delta2, off)], ticksPerQuarter=ticks_per_quarter_note ) print('WORKED!') except: print(delta1) print(on) print(delta2) print(off) print(ticks_per_quarter_note) raise import ipdb; ipdb.set_trace() del note_num2start_data[note_num] last_ticks = ticks