#================================================================================== # 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 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 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 = TMIDIX.Tegridy_Any_Pickle_File_Reader('MIDI-Loops-Dataset-Small-CC-BY-NC-SA.pickle') 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][0])) >= chunk_chords_set] for loop in tqdm.tqdm(midi_loops): fn = loop[0] chords = loop[1] score = loop[2] for c in chords: all_chords_chunks.append(c[: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][0][-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]] if stries > 1000: break 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][0][-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 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("