Spaces:
Running
Running
| #================================================================================== | |
| # https://huggingface.co/spaces/asigalov61/MIDI-Loops-Mixer | |
| #================================================================================== | |
| print('=' * 70) | |
| print('MIDI Loops Mixer Gradio App') | |
| print('=' * 70) | |
| print('Loading core MIDI Loops Mixer modules...') | |
| import os | |
| import copy | |
| import statistics | |
| import random | |
| import pickle | |
| import time as reqtime | |
| import datetime | |
| from pytz import timezone | |
| import tqdm | |
| print('=' * 70) | |
| print('Loading main MIDI Loops Mixer modules...') | |
| import numpy as np | |
| import TMIDIX | |
| from midi_to_colab_audio import midi_to_colab_audio | |
| from huggingface_hub import hf_hub_download | |
| import gradio as gr | |
| print('=' * 70) | |
| print('Loading aux MIDI Loops Mixer modules...') | |
| import matplotlib.pyplot as plt | |
| print('=' * 70) | |
| print('Done!') | |
| print('Enjoy! :)') | |
| print('=' * 70) | |
| #================================================================================== | |
| SOUDFONT_PATH = 'SGM-v2.01-YamahaGrand-Guit-Bass-v2.7.sf2' | |
| #================================================================================== | |
| print('Loading MIDI Loops Small Dataset...') | |
| print('=' * 70) | |
| midi_loops_dataset = pickle.load(open(hf_hub_download(repo_id='asigalov61/MIDI-Loops', | |
| filename='MIDI_Loops_Processed_Dataset_116909_Loops_CC_BY_NC_SA.pickle', | |
| repo_type='dataset' | |
| ), | |
| 'rb') | |
| ) | |
| print('=' * 70) | |
| print('Done!') | |
| print('=' * 70) | |
| print('Loaded', len(midi_loops_dataset), 'MIDI Loops') | |
| #================================================================================== | |
| def find_matches(src_array, trg_array): | |
| matches = np.all(src_array == trg_array, axis=1) | |
| matching_indices = np.where(matches)[0] | |
| return matching_indices.tolist() | |
| #================================================================================== | |
| def find_closest_tuple(tuples_list, src_tuple): | |
| def euclidean_distance(t1, t2): | |
| return sum((a - b) ** 2 for a, b in zip(t1, t2)) ** 0.5 | |
| closest_tuple = None | |
| min_distance = float('inf') | |
| for t in tuples_list: | |
| distance = euclidean_distance(t, src_tuple) | |
| if distance < min_distance: | |
| min_distance = distance | |
| closest_tuple = t | |
| return closest_tuple | |
| #================================================================================== | |
| def find_best_midx(midi_loops, midxs, trg_midx): | |
| all_midxs = midxs + [trg_midx] | |
| sidxs = [int(midx // 6) for midx in all_midxs] | |
| times_durs = [] | |
| for sidx in sidxs: | |
| score = midi_loops[sidx][2] | |
| dtimes = [e[0] for e in score if e[0] != 0] | |
| durs = [e[1] for e in score] | |
| avg_dtime = int(sum(dtimes) / len(dtimes)) | |
| avg_dur = int(sum(durs) / len(durs)) | |
| mode_dtimes= statistics.mode(dtimes) | |
| mode_durs = statistics.mode(durs) | |
| times_durs.append([avg_dtime, avg_dur, mode_dtimes, mode_durs]) | |
| best_time_dur = find_closest_tuple(times_durs[:-1], times_durs[-1]) | |
| best_midx = midxs[times_durs.index(best_time_dur)] | |
| return best_midx | |
| #================================================================================== | |
| def Mix_Loops(max_num_loops, | |
| comp_loops_mult, | |
| chords_chunks_len, | |
| loops_chords_set_len | |
| ): | |
| #=============================================================================== | |
| print('=' * 70) | |
| print('Req start time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT))) | |
| start_time = reqtime.time() | |
| print('=' * 70) | |
| print('Requested settings:') | |
| print('=' * 70) | |
| print('Max number of loops:', max_num_loops) | |
| print('Num of loops reps:', comp_loops_mult) | |
| print('Matches chords chunks len:', chords_chunks_len) | |
| print('Min number of unique chords in each loops:', loops_chords_set_len) | |
| print('=' * 70) | |
| #=============================================================================== | |
| print('Prepping dataset...') | |
| chunk_len = chords_chunks_len | |
| chunk_chords_set = loops_chords_set_len | |
| all_chords_chunks = [] | |
| midi_loops = [l for l in midi_loops_dataset if len(set(l[1])) >= chunk_chords_set] | |
| for loop in tqdm.tqdm(midi_loops): | |
| fn = loop[0] | |
| chords = loop[1] | |
| score = loop[2] | |
| all_chords_chunks.append(chords[:chunk_len]) | |
| all_chords_chunks = np.array(all_chords_chunks) | |
| print('Done!') | |
| print('=' * 70) | |
| print('Number of chords chunks:', len(all_chords_chunks)) | |
| print('=' * 70) | |
| #================================================================== | |
| print('Mixing loops...') | |
| print('=' * 70) | |
| max_tries = 100 | |
| loops_mult = 1 | |
| song_loops_counter = 0 | |
| stries = 0 | |
| while song_loops_counter < max_num_loops: | |
| if stries % 25 == 0: | |
| print('Mixing attempt #', stries) | |
| stries += 1 | |
| midxs = [] | |
| sidxs = [-1] | |
| while not midxs: | |
| song_names = [] | |
| song_chords = [] | |
| song_scores = [] | |
| song_idxs = [] | |
| sidx = -1 | |
| while sidx in sidxs: | |
| sidx = random.randint(0, len(midi_loops)-1) | |
| song_idxs.append(sidx) | |
| sidxs.append(sidx) | |
| song_names.append(midi_loops[sidx][0]) | |
| song_chords.append(midi_loops[sidx][1][-chunk_len:]) #tv | |
| song_scores.append(midi_loops[sidx][2]) | |
| song_midxs = [(song_idxs[-1]*loops_mult)+3] | |
| midxs = [song_midxs[-1]] | |
| midxs = find_matches(np.array(song_chords[-1]), all_chords_chunks) | |
| midxs = [midx for midx in midxs if midx != song_midxs[-1]] | |
| song_loops_counter = 1 | |
| rtries = 0 | |
| end = False | |
| while song_loops_counter < max_num_loops and not end: | |
| midxs = [song_midxs[-1]] | |
| midxs = [] | |
| rmidxs = [-1] | |
| midxs = find_matches(np.array(song_chords[-1]), all_chords_chunks) | |
| midxs = [midx for midx in midxs if midx != song_midxs[-1]] | |
| if midxs: | |
| midx = find_best_midx(midi_loops, midxs, song_midxs[-1]) | |
| else: | |
| midx = rmidxs[-1] | |
| if midx not in rmidxs and midx not in song_midxs: | |
| song_midxs.append(midx) | |
| sidx = int(midx // loops_mult) | |
| song_idxs.append(sidx) | |
| song = midi_loops[sidx] | |
| song_names.append(song[0]) | |
| song_chords.append(song[1][-chunk_len:]) # tv | |
| song_scores.append(song[2]) | |
| song_loops_counter += 1 | |
| else: | |
| if len(rmidxs) > 1 and rtries < max_tries: | |
| song_idxs.pop() | |
| song_midxs.pop() | |
| song_names.pop() | |
| song_chords.pop() | |
| song_scores.pop() | |
| song_loops_counter -= 1 | |
| rtries += 1 | |
| rmidxs.append(midx) | |
| else: | |
| end = True | |
| break | |
| if end: | |
| break | |
| if stries > 1000: | |
| break | |
| print('=' * 70) | |
| print('Done!') | |
| print('=' * 70) | |
| #=============================================================================== | |
| print('Creating final MIDI score...') | |
| loops_mult = comp_loops_mult | |
| final_song = [] | |
| last_max_dur = 0 | |
| last_dtime = 0 | |
| mode_dtime = 0 | |
| mode_dur = 0 | |
| for i, src_score in enumerate(song_scores): | |
| final_song.append(['text_event', 0, song_names[i]]) | |
| score = copy.deepcopy(src_score) | |
| for j in range(loops_mult): | |
| if j == loops_mult-1 and not (i == len(song_scores)-1 and j == loops_mult-1): | |
| if i > 0: | |
| last_chord = score[[e for e in range(len(score)) if score[e][0] > 0][-1]:] | |
| last_dtime = last_chord[0][0] | |
| last_max_dur = max([e[1] for e in last_chord]) | |
| dtimes = [e[0] for e in score if e[0] != 0] | |
| durs = [e[1] for e in score] | |
| mode_dtime= statistics.mode(dtimes) | |
| mode_dur = statistics.mode(durs) | |
| score[0][0] = max(mode_dtime, mode_dur) | |
| rscore = list(reversed(score)) | |
| ccount = 0 | |
| for r in range(len(rscore)): | |
| if rscore[r][0] > 0: | |
| ccount += 1 | |
| if ccount == chunk_len: | |
| break | |
| trimmed_score = score[:-(r+1)] | |
| extended_score = [['note'] + e for e in trimmed_score] | |
| final_song.extend(extended_score) | |
| else: | |
| if i > 0: | |
| last_chord = score[[e for e in range(len(score)) if score[e][0] > 0][-1]:] | |
| last_dtime = last_chord[0][0] | |
| last_max_dur = max([e[1] for e in last_chord]) | |
| dtimes = [e[0] for e in score if e[0] != 0] | |
| durs = [e[1] for e in score] | |
| mode_dtime= statistics.mode(dtimes) | |
| mode_dur = statistics.mode(durs) | |
| score[0][0] = max(mode_dtime, mode_dur) | |
| extended_score = [['note'] + e for e in score] | |
| final_song.extend(extended_score) | |
| final_song_abs = TMIDIX.delta_score_to_abs_score(final_song) | |
| print('Done!') | |
| print('=' * 70) | |
| #=============================================================================== | |
| print('Creating MIDI summary...') | |
| midi_summary = 'Number of source MIDI loops: ' + str(len(song_names) * loops_mult) + '\n' | |
| midi_summary += '-' * 40 | |
| midi_summary += '\n' | |
| for i, song_name in enumerate(song_names): | |
| son_art = song_name.split('___')[:2] | |
| son = son_art[0] | |
| art = son_art[1] | |
| midi_summary += 'Loops # ' + str((i*loops_mult)+1) + '-' + str((i*loops_mult)+loops_mult) + ': "' + son + '" by ' + art + '\n' | |
| #=============================================================================== | |
| print('Rendering results...') | |
| print('=' * 70) | |
| print('Sample MIDI events:', final_song_abs[:3]) | |
| print('=' * 70) | |
| output_score, patches, overflow_patches = TMIDIX.patch_enhanced_score_notes(final_song_abs) | |
| fn1 = "MIDI-Loops-Mixer-Composition" | |
| detailed_stats = TMIDIX.Tegridy_ms_SONG_to_MIDI_Converter(output_score, | |
| output_signature = 'MIDI Loops Mixer', | |
| output_file_name = fn1, | |
| track_name='Project Los Angeles', | |
| list_of_MIDI_patches=patches, | |
| timings_multiplier=16 | |
| ) | |
| new_fn = fn1+'.mid' | |
| audio = midi_to_colab_audio(new_fn, | |
| soundfont_path=SOUDFONT_PATH, | |
| sample_rate=16000, | |
| volume_scale=10, | |
| output_for_gradio=True | |
| ) | |
| print('Done!') | |
| print('=' * 70) | |
| #======================================================== | |
| output_midi_summary = str(midi_summary) | |
| output_midi = str(new_fn) | |
| output_audio = (16000, audio) | |
| output_plot = TMIDIX.plot_ms_SONG(output_score, | |
| plot_title=output_midi, | |
| timings_multiplier=16, | |
| return_plt=True | |
| ) | |
| print('Output MIDI file name:', output_midi) | |
| print('=' * 70) | |
| print('Output MIDI summary:') | |
| print('-' * 70) | |
| print(output_midi_summary) | |
| print('=' * 70) | |
| #======================================================== | |
| print('-' * 70) | |
| print('Req end time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT))) | |
| print('-' * 70) | |
| print('Req execution time:', (reqtime.time() - start_time), 'sec') | |
| return output_midi_summary, output_audio, output_plot, output_midi | |
| #================================================================================== | |
| PDT = timezone('US/Pacific') | |
| print('=' * 70) | |
| print('App start time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT))) | |
| print('=' * 70) | |
| #================================================================================== | |
| with gr.Blocks() as demo: | |
| #================================================================================== | |
| gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>MIDI Loops Mixer</h1>") | |
| gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Mix random MIDI loops into one coherent music composition</h1>") | |
| gr.HTML(""" | |
| <p> | |
| <a href="https://huggingface.co/spaces/asigalov61/MIDI-Loops-Mixer?duplicate=true"> | |
| <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-md.svg" alt="Duplicate in Hugging Face"> | |
| </a> | |
| </p> | |
| """) | |
| #================================================================================== | |
| gr.Markdown("## Mixing options") | |
| max_num_loops = gr.Slider(2, 10, value=4, step=1, label="Maximum number of loops to mix") | |
| comp_loops_mult = gr.Slider(2, 4, value=2, step=1, label="Number of loops repetitions") | |
| chords_chunks_len = gr.Slider(6, 10, value=6, step=1, label="Number of loops chords to match") | |
| loops_chords_set_len = gr.Slider(16, 24, value=16, step=1, label="Minimum number of unique chords in each loop") | |
| mix_btn = gr.Button("Mix", variant="primary") | |
| gr.Markdown("## Mixing results") | |
| output_midi_summary = gr.Textbox(label="MIDI summary") | |
| output_audio = gr.Audio(label="MIDI audio", format="wav", elem_id="midi_audio") | |
| output_plot = gr.Plot(label="MIDI score plot") | |
| output_midi = gr.File(label="MIDI file", file_types=[".mid"]) | |
| mix_btn.click(Mix_Loops, | |
| [ | |
| max_num_loops, | |
| comp_loops_mult, | |
| chords_chunks_len, | |
| loops_chords_set_len | |
| ], | |
| [ | |
| output_midi_summary, | |
| output_audio, | |
| output_plot, | |
| output_midi | |
| ] | |
| ) | |
| #================================================================================== | |
| demo.launch() | |
| #================================================================================== |