asigalov61's picture
Update app.py
6ea0519 verified
raw
history blame
15.3 kB
#==================================================================================
# 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("<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(4, 8, value=5, step=1, label="Number of loops chords to match")
loops_chords_set_len = gr.Slider(10, 20, value=13, 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()
#==================================================================================