Spaces:
Build error
Build error
| import chord_recognition | |
| import numpy as np | |
| import miditoolkit | |
| import copy | |
| # parameters for input | |
| DEFAULT_VELOCITY_BINS = np.linspace(0, 128, 32+1, dtype=np.int) | |
| DEFAULT_FRACTION = 16 | |
| DEFAULT_DURATION_BINS = np.arange(60, 3841, 60, dtype=int) | |
| DEFAULT_TEMPO_INTERVALS = [range(30, 90), range(90, 150), range(150, 210)] | |
| # parameters for output | |
| DEFAULT_RESOLUTION = 480 | |
| # define "Item" for general storage | |
| class Item(object): | |
| def __init__(self, name, start, end, velocity, pitch): | |
| self.name = name | |
| self.start = start | |
| self.end = end | |
| self.velocity = velocity | |
| self.pitch = pitch | |
| def __repr__(self): | |
| return 'Item(name={}, start={}, end={}, velocity={}, pitch={})'.format( | |
| self.name, self.start, self.end, self.velocity, self.pitch) | |
| # read notes and tempo changes from midi (assume there is only one track) | |
| def read_items(file_path): | |
| midi_obj = miditoolkit.midi.parser.MidiFile(file_path) | |
| # note | |
| note_items = [] | |
| notes = midi_obj.instruments[0].notes | |
| notes.sort(key=lambda x: (x.start, x.pitch)) | |
| for note in notes: | |
| note_items.append(Item( | |
| name='Note', | |
| start=note.start, | |
| end=note.end, | |
| velocity=note.velocity, | |
| pitch=note.pitch)) | |
| note_items.sort(key=lambda x: x.start) | |
| # tempo | |
| tempo_items = [] | |
| for tempo in midi_obj.tempo_changes: | |
| tempo_items.append(Item( | |
| name='Tempo', | |
| start=tempo.time, | |
| end=None, | |
| velocity=None, | |
| pitch=int(tempo.tempo))) | |
| tempo_items.sort(key=lambda x: x.start) | |
| # expand to all beat | |
| max_tick = tempo_items[-1].start | |
| existing_ticks = {item.start: item.pitch for item in tempo_items} | |
| wanted_ticks = np.arange(0, max_tick+1, DEFAULT_RESOLUTION) | |
| output = [] | |
| for tick in wanted_ticks: | |
| if tick in existing_ticks: | |
| output.append(Item( | |
| name='Tempo', | |
| start=tick, | |
| end=None, | |
| velocity=None, | |
| pitch=existing_ticks[tick])) | |
| else: | |
| output.append(Item( | |
| name='Tempo', | |
| start=tick, | |
| end=None, | |
| velocity=None, | |
| pitch=output[-1].pitch)) | |
| tempo_items = output | |
| return note_items, tempo_items | |
| # quantize items | |
| def quantize_items(items, ticks=120): | |
| # grid | |
| grids = np.arange(0, items[-1].start, ticks, dtype=int) | |
| # process | |
| for item in items: | |
| index = np.argmin(abs(grids - item.start)) | |
| shift = grids[index] - item.start | |
| item.start += shift | |
| item.end += shift | |
| return items | |
| # extract chord | |
| def extract_chords(items): | |
| method = chord_recognition.MIDIChord() | |
| chords = method.extract(notes=items) | |
| output = [] | |
| for chord in chords: | |
| output.append(Item( | |
| name='Chord', | |
| start=chord[0], | |
| end=chord[1], | |
| velocity=None, | |
| pitch=chord[2].split('/')[0])) | |
| return output | |
| # group items | |
| def group_items(items, max_time, ticks_per_bar=DEFAULT_RESOLUTION*4): | |
| items.sort(key=lambda x: x.start) | |
| downbeats = np.arange(0, max_time+ticks_per_bar, ticks_per_bar) | |
| groups = [] | |
| for db1, db2 in zip(downbeats[:-1], downbeats[1:]): | |
| insiders = [] | |
| for item in items: | |
| if (item.start >= db1) and (item.start < db2): | |
| insiders.append(item) | |
| overall = [db1] + insiders + [db2] | |
| groups.append(overall) | |
| return groups | |
| # define "Event" for event storage | |
| class Event(object): | |
| def __init__(self, name, time, value, text): | |
| self.name = name | |
| self.time = time | |
| self.value = value | |
| self.text = text | |
| def __repr__(self): | |
| return 'Event(name={}, time={}, value={}, text={})'.format( | |
| self.name, self.time, self.value, self.text) | |
| # item to event | |
| def item2event(groups): | |
| events = [] | |
| n_downbeat = 0 | |
| for i in range(len(groups)): | |
| if 'Note' not in [item.name for item in groups[i][1:-1]]: | |
| continue | |
| bar_st, bar_et = groups[i][0], groups[i][-1] | |
| n_downbeat += 1 | |
| events.append(Event( | |
| name='Bar', | |
| time=None, | |
| value=None, | |
| text='{}'.format(n_downbeat))) | |
| for item in groups[i][1:-1]: | |
| # position | |
| flags = np.linspace(bar_st, bar_et, DEFAULT_FRACTION, endpoint=False) | |
| index = np.argmin(abs(flags-item.start)) | |
| events.append(Event( | |
| name='Position', | |
| time=item.start, | |
| value='{}/{}'.format(index+1, DEFAULT_FRACTION), | |
| text='{}'.format(item.start))) | |
| if item.name == 'Note': | |
| # velocity | |
| velocity_index = np.searchsorted( | |
| DEFAULT_VELOCITY_BINS, | |
| item.velocity, | |
| side='right') - 1 | |
| events.append(Event( | |
| name='Note Velocity', | |
| time=item.start, | |
| value=velocity_index, | |
| text='{}/{}'.format(item.velocity, DEFAULT_VELOCITY_BINS[velocity_index]))) | |
| # pitch | |
| events.append(Event( | |
| name='Note On', | |
| time=item.start, | |
| value=item.pitch, | |
| text='{}'.format(item.pitch))) | |
| # duration | |
| duration = item.end - item.start | |
| index = np.argmin(abs(DEFAULT_DURATION_BINS-duration)) | |
| events.append(Event( | |
| name='Note Duration', | |
| time=item.start, | |
| value=index, | |
| text='{}/{}'.format(duration, DEFAULT_DURATION_BINS[index]))) | |
| elif item.name == 'Chord': | |
| events.append(Event( | |
| name='Chord', | |
| time=item.start, | |
| value=item.pitch, | |
| text='{}'.format(item.pitch))) | |
| elif item.name == 'Tempo': | |
| tempo = item.pitch | |
| if tempo in DEFAULT_TEMPO_INTERVALS[0]: | |
| tempo_style = Event('Tempo Class', item.start, 'slow', None) | |
| tempo_value = Event('Tempo Value', item.start, | |
| tempo-DEFAULT_TEMPO_INTERVALS[0].start, None) | |
| elif tempo in DEFAULT_TEMPO_INTERVALS[1]: | |
| tempo_style = Event('Tempo Class', item.start, 'mid', None) | |
| tempo_value = Event('Tempo Value', item.start, | |
| tempo-DEFAULT_TEMPO_INTERVALS[1].start, None) | |
| elif tempo in DEFAULT_TEMPO_INTERVALS[2]: | |
| tempo_style = Event('Tempo Class', item.start, 'fast', None) | |
| tempo_value = Event('Tempo Value', item.start, | |
| tempo-DEFAULT_TEMPO_INTERVALS[2].start, None) | |
| elif tempo < DEFAULT_TEMPO_INTERVALS[0].start: | |
| tempo_style = Event('Tempo Class', item.start, 'slow', None) | |
| tempo_value = Event('Tempo Value', item.start, 0, None) | |
| elif tempo > DEFAULT_TEMPO_INTERVALS[2].stop: | |
| tempo_style = Event('Tempo Class', item.start, 'fast', None) | |
| tempo_value = Event('Tempo Value', item.start, 59, None) | |
| events.append(tempo_style) | |
| events.append(tempo_value) | |
| return events | |
| ############################################################################################# | |
| # WRITE MIDI | |
| ############################################################################################# | |
| def word_to_event(words, word2event): | |
| events = [] | |
| for word in words: | |
| event_name, event_value = word2event.get(word).split('_') | |
| events.append(Event(event_name, None, event_value, None)) | |
| return events | |
| def write_midi(words, word2event, output_path, prompt_path=None): | |
| events = word_to_event(words, word2event) | |
| # get downbeat and note (no time) | |
| temp_notes = [] | |
| temp_chords = [] | |
| temp_tempos = [] | |
| for i in range(len(events)-3): | |
| if events[i].name == 'Bar' and i > 0: | |
| temp_notes.append('Bar') | |
| temp_chords.append('Bar') | |
| temp_tempos.append('Bar') | |
| elif events[i].name == 'Position' and \ | |
| events[i+1].name == 'Note Velocity' and \ | |
| events[i+2].name == 'Note On' and \ | |
| events[i+3].name == 'Note Duration': | |
| # start time and end time from position | |
| position = int(events[i].value.split('/')[0]) - 1 | |
| # velocity | |
| index = int(events[i+1].value) | |
| velocity = int(DEFAULT_VELOCITY_BINS[index]) | |
| # pitch | |
| pitch = int(events[i+2].value) | |
| # duration | |
| index = int(events[i+3].value) | |
| duration = DEFAULT_DURATION_BINS[index] | |
| # adding | |
| temp_notes.append([position, velocity, pitch, duration]) | |
| elif events[i].name == 'Position' and events[i+1].name == 'Chord': | |
| position = int(events[i].value.split('/')[0]) - 1 | |
| temp_chords.append([position, events[i+1].value]) | |
| elif events[i].name == 'Position' and \ | |
| events[i+1].name == 'Tempo Class' and \ | |
| events[i+2].name == 'Tempo Value': | |
| position = int(events[i].value.split('/')[0]) - 1 | |
| if events[i+1].value == 'slow': | |
| tempo = DEFAULT_TEMPO_INTERVALS[0].start + int(events[i+2].value) | |
| elif events[i+1].value == 'mid': | |
| tempo = DEFAULT_TEMPO_INTERVALS[1].start + int(events[i+2].value) | |
| elif events[i+1].value == 'fast': | |
| tempo = DEFAULT_TEMPO_INTERVALS[2].start + int(events[i+2].value) | |
| temp_tempos.append([position, tempo]) | |
| # get specific time for notes | |
| ticks_per_beat = DEFAULT_RESOLUTION | |
| ticks_per_bar = DEFAULT_RESOLUTION * 4 # assume 4/4 | |
| notes = [] | |
| current_bar = 0 | |
| for note in temp_notes: | |
| if note == 'Bar': | |
| current_bar += 1 | |
| else: | |
| position, velocity, pitch, duration = note | |
| # position (start time) | |
| current_bar_st = current_bar * ticks_per_bar | |
| current_bar_et = (current_bar + 1) * ticks_per_bar | |
| flags = np.linspace(current_bar_st, current_bar_et, DEFAULT_FRACTION, endpoint=False, dtype=int) | |
| st = flags[position] | |
| # duration (end time) | |
| et = st + duration | |
| notes.append(miditoolkit.Note(velocity, pitch, st, et)) | |
| # get specific time for chords | |
| if len(temp_chords) > 0: | |
| chords = [] | |
| current_bar = 0 | |
| for chord in temp_chords: | |
| if chord == 'Bar': | |
| current_bar += 1 | |
| else: | |
| position, value = chord | |
| # position (start time) | |
| current_bar_st = current_bar * ticks_per_bar | |
| current_bar_et = (current_bar + 1) * ticks_per_bar | |
| flags = np.linspace(current_bar_st, current_bar_et, DEFAULT_FRACTION, endpoint=False, dtype=int) | |
| st = flags[position] | |
| chords.append([st, value]) | |
| # get specific time for tempos | |
| tempos = [] | |
| current_bar = 0 | |
| for tempo in temp_tempos: | |
| if tempo == 'Bar': | |
| current_bar += 1 | |
| else: | |
| position, value = tempo | |
| # position (start time) | |
| current_bar_st = current_bar * ticks_per_bar | |
| current_bar_et = (current_bar + 1) * ticks_per_bar | |
| flags = np.linspace(current_bar_st, current_bar_et, DEFAULT_FRACTION, endpoint=False, dtype=int) | |
| st = flags[position] | |
| tempos.append([int(st), value]) | |
| # write | |
| if prompt_path: | |
| midi = miditoolkit.midi.parser.MidiFile(prompt_path) | |
| # | |
| last_time = DEFAULT_RESOLUTION * 4 * 4 | |
| # note shift | |
| for note in notes: | |
| note.start += last_time | |
| note.end += last_time | |
| midi.instruments[0].notes.extend(notes) | |
| # tempo changes | |
| temp_tempos = [] | |
| for tempo in midi.tempo_changes: | |
| if tempo.time < DEFAULT_RESOLUTION*4*4: | |
| temp_tempos.append(tempo) | |
| else: | |
| break | |
| for st, bpm in tempos: | |
| st += last_time | |
| temp_tempos.append(miditoolkit.midi.containers.TempoChange(bpm, st)) | |
| midi.tempo_changes = temp_tempos | |
| # write chord into marker | |
| if len(temp_chords) > 0: | |
| for c in chords: | |
| midi.markers.append( | |
| miditoolkit.midi.containers.Marker(text=c[1], time=c[0]+last_time)) | |
| else: | |
| midi = miditoolkit.midi.parser.MidiFile() | |
| midi.ticks_per_beat = DEFAULT_RESOLUTION | |
| # write instrument | |
| inst = miditoolkit.midi.containers.Instrument(0, is_drum=False) | |
| inst.notes = notes | |
| midi.instruments.append(inst) | |
| # write tempo | |
| tempo_changes = [] | |
| for st, bpm in tempos: | |
| tempo_changes.append(miditoolkit.midi.containers.TempoChange(bpm, st)) | |
| midi.tempo_changes = tempo_changes | |
| # write chord into marker | |
| if len(temp_chords) > 0: | |
| for c in chords: | |
| midi.markers.append( | |
| miditoolkit.midi.containers.Marker(text=c[1], time=c[0])) | |
| # write | |
| midi.dump(output_path) | |