MIDI-Match / app.py
asigalov61's picture
Update app.py
f3d6db0
import argparse
import glob
import os.path
import gradio as gr
import pickle
import tqdm
import json
import MIDI
from midi_synthesizer import synthesis
import copy
from collections import Counter
import random
import statistics
import matplotlib.pyplot as plt
#==========================================================================================================
in_space = os.getenv("SYSTEM") == "spaces"
#==========================================================================================================
def match_midi(midi, max_match_ratio, progress=gr.Progress()):
print('=' * 70)
print('Loading MIDI file...')
#==================================================
score = MIDI.midi2score(midi)
events_matrix = []
track_count = 0
for s in score:
if track_count > 0:
track = s
track.sort(key=lambda x: x[1])
events_matrix.extend(track)
else:
midi_ticks = s
track_count += 1
events_matrix.sort(key=lambda x: x[1])
mult_pitches_counts = []
for i in range(-6, 6):
events_matrix1 = []
for e in events_matrix:
ev = copy.deepcopy(e)
if e[0] == 'note':
if e[3] == 9:
ev[4] = ((e[4] % 128) + 128)
else:
ev[4] = ((e[4] % 128) + i)
events_matrix1.append(ev)
pitches_counts = [[y[0],y[1]] for y in Counter([y[4] for y in events_matrix1 if y[0] == 'note']).most_common()]
pitches_counts.sort(key=lambda x: x[0], reverse=True)
mult_pitches_counts.append(pitches_counts)
patches_list = sorted(list(set([y[3] for y in events_matrix if y[0] == 'patch_change'])))
#==================================================
ms_score = MIDI.midi2ms_score(midi)
ms_events_matrix = []
itrack1 = 1
while itrack1 < len(ms_score):
for event in ms_score[itrack1]:
if event[0] == 'note':
ms_events_matrix.append(event)
itrack1 += 1
ms_events_matrix.sort(key=lambda x: x[1])
chords = []
pe = ms_events_matrix[0]
cho = []
for e in ms_events_matrix:
if (e[1] - pe[1]) == 0:
if e[3] != 9:
if (e[4] % 12) not in cho:
cho.append(e[4] % 12)
else:
if len(cho) > 0:
chords.append(sorted(cho))
cho = []
if e[3] != 9:
if (e[4] % 12) not in cho:
cho.append(e[4] % 12)
pe = e
if len(cho) > 0:
chords.append(sorted(cho))
ms_chords_counts = sorted([[list(key), val] for key,val in Counter([tuple(c) for c in chords if len(c) > 1]).most_common()], reverse=True, key = lambda x: x[1])
times = []
pt = ms_events_matrix[0][1]
start = True
for e in ms_events_matrix:
if (e[1]-pt) != 0 or start == True:
times.append((e[1]-pt))
start = False
pt = e[1]
durs = [e[2] for e in ms_events_matrix]
vels = [e[5] for e in ms_events_matrix]
avg_time = int(sum(times) / len(times))
avg_dur = int(sum(durs) / len(durs))
mode_time = statistics.mode(times)
mode_dur = statistics.mode(durs)
median_time = int(statistics.median(times))
median_dur = int(statistics.median(durs))
#==================================================
print('=' * 70)
print('Done!')
print('=' * 70)
#==========================================================================================================
#@title MIDI Pitches Search
#@markdown Match ratio control option
maximum_match_ratio_to_search_for = max_match_ratio #@param {type:"slider", min:0, max:1, step:0.01}
#@markdown MIDI pitches search options
pitches_counts_cutoff_threshold_ratio = 0 #@param {type:"slider", min:0, max:1, step:0.05}
search_transposed_pitches = False #@param {type:"boolean"}
skip_exact_matches = False #@param {type:"boolean"}
#@markdown Additional search options
add_pitches_counts_ratios = False #@param {type:"boolean"}
add_timings_ratios = False #@param {type:"boolean"}
add_durations_ratios = False #@param {type:"boolean"}
print('=' * 70)
print('MIDI Pitches Search')
print('=' * 70)
final_ratios = []
for d in progress.tqdm(meta_data):
p_counts = d[1][10][1]
p_counts.sort(reverse = True, key = lambda x: x[1])
max_p_count = p_counts[0][1]
trimmed_p_counts = [y for y in p_counts if y[1] >= (max_p_count * pitches_counts_cutoff_threshold_ratio)]
total_p_counts = sum([y[1] for y in trimmed_p_counts])
if search_transposed_pitches:
search_pitches = mult_pitches_counts
else:
search_pitches = [mult_pitches_counts[6]]
#===================================================
ratios_list = []
#===================================================
atrat = [0]
if add_timings_ratios:
source_times = [avg_time,
median_time,
mode_time]
match_times = meta_data[0][1][3][1]
times_ratios = []
for i in range(len(source_times)):
maxtratio = max(source_times[i], match_times[i])
mintratio = min(source_times[i], match_times[i])
times_ratios.append(mintratio / maxtratio)
avg_times_ratio = sum(times_ratios) / len(times_ratios)
atrat[0] = avg_times_ratio
#===================================================
adrat = [0]
if add_durations_ratios:
source_durs = [avg_dur,
median_dur,
mode_dur]
match_durs = meta_data[0][1][4][1]
durs_ratios = []
for i in range(len(source_durs)):
maxtratio = max(source_durs[i], match_durs[i])
mintratio = min(source_durs[i], match_durs[i])
durs_ratios.append(mintratio / maxtratio)
avg_durs_ratio = sum(durs_ratios) / len(durs_ratios)
adrat[0] = avg_durs_ratio
#===================================================
for m in search_pitches:
sprat = []
m.sort(reverse = True, key = lambda x: x[1])
max_pitches_count = m[0][1]
trimmed_pitches_counts = [y for y in m if y[1] >= (max_pitches_count * pitches_counts_cutoff_threshold_ratio)]
total_pitches_counts = sum([y[1] for y in trimmed_pitches_counts])
same_pitches = set([T[0] for T in trimmed_p_counts]) & set([m[0] for m in trimmed_pitches_counts])
num_same_pitches = len(same_pitches)
if num_same_pitches == len(trimmed_pitches_counts):
same_pitches_ratio = (num_same_pitches / len(trimmed_p_counts))
else:
same_pitches_ratio = (num_same_pitches / max(len(trimmed_p_counts), len(trimmed_pitches_counts)))
if skip_exact_matches:
if same_pitches_ratio == 1:
same_pitches_ratio = 0
sprat.append(same_pitches_ratio)
#===================================================
spcrat = [0]
if add_pitches_counts_ratios:
same_trimmed_p_counts = sorted([T for T in trimmed_p_counts if T[0] in same_pitches], reverse = True)
same_trimmed_pitches_counts = sorted([T for T in trimmed_pitches_counts if T[0] in same_pitches], reverse = True)
same_trimmed_p_counts_ratios = [[s[0], s[1] / total_p_counts] for s in same_trimmed_p_counts]
same_trimmed_pitches_counts_ratios = [[s[0], s[1] / total_pitches_counts] for s in same_trimmed_pitches_counts]
same_pitches_counts_ratios = []
for i in range(len(same_trimmed_p_counts_ratios)):
mincratio = min(same_trimmed_p_counts_ratios[i][1], same_trimmed_pitches_counts_ratios[i][1])
maxcratio = max(same_trimmed_p_counts_ratios[i][1], same_trimmed_pitches_counts_ratios[i][1])
same_pitches_counts_ratios.append([same_trimmed_p_counts_ratios[i][0], mincratio / maxcratio])
same_counts_ratios = [s[1] for s in same_pitches_counts_ratios]
if len(same_counts_ratios) > 0:
avg_same_pitches_counts_ratio = sum(same_counts_ratios) / len(same_counts_ratios)
else:
avg_same_pitches_counts_ratio = 0
spcrat[0] = avg_same_pitches_counts_ratio
#===================================================
r_list = [sprat[0]]
if add_pitches_counts_ratios:
r_list.append(spcrat[0])
if add_timings_ratios:
r_list.append(atrat[0])
if add_durations_ratios:
r_list.append(adrat[0])
ratios_list.append(r_list)
#===================================================
avg_ratios_list = []
for r in ratios_list:
avg_ratios_list.append(sum(r) / len(r))
#===================================================
final_ratio = max(avg_ratios_list)
if final_ratio > maximum_match_ratio_to_search_for:
final_ratio = 0
final_ratios.append(final_ratio)
#===================================================
max_ratio = max(final_ratios)
max_ratio_index = final_ratios.index(max_ratio)
print('FOUND')
print('=' * 70)
print('Match ratio', max_ratio)
print('MIDI file name', meta_data[max_ratio_index][0])
print('=' * 70)
fn = meta_data[max_ratio_index][0]
#==========================================================================================================
md = meta_data[max_ratio_index]
mid_seq = md[1][17:-1]
mid_seq_ticks = md[1][16][1]
mdata = md[1][:16]
txt_mdata = ''
txt_mdata += '==============================================================' + chr(10)
txt_mdata += 'MIDI MATCH RATIO: ' + str(max_ratio) + chr(10)
txt_mdata += '==============================================================' + chr(10)
txt_mdata += 'MIDI MATCH MD5 HASH: ' + str(fn) + chr(10)
txt_mdata += '==============================================================' + chr(10)
for m in mdata:
txt_mdata += str(m[0]) + ': ' + str(m[1])
txt_mdata += chr(10)
txt_mdata += '==============================================================' + chr(10)
for m in [d for d in md[1][16:] if d[0] != 'note']:
txt_mdata += str(m)
txt_mdata += chr(10)
txt_mdata += '==============================================================' + chr(10)
txt_mdata += 'MIDI MATCH RATIO: ' + str(max_ratio) + chr(10)
txt_mdata += '==============================================================' + chr(10)
txt_mdata += 'MIDI MATCH MD5 HASH: ' + str(fn) + chr(10)
txt_mdata += '==============================================================' + chr(10)
x = []
y = []
c = []
colors = ['red', 'yellow', 'green', 'cyan',
'blue', 'pink', 'orange', 'purple',
'gray', 'white', 'gold', 'silver',
'lightgreen', 'indigo', 'maroon', 'turquoise']
for s in [m for m in mid_seq if m[0] == 'note']:
x.append(s[1])
y.append(s[4])
c.append(colors[s[3]])
plt.close()
plt.figure(figsize=(14,5))
ax=plt.axes(title='MIDI Match Plot')
ax.set_facecolor('black')
plt.scatter(x,y, c=c)
plt.xlabel("Time in MIDI ticks")
plt.ylabel("MIDI Pitch")
with open(f"MIDI-Match-Sample.mid", 'wb') as f:
f.write(MIDI.score2midi([mid_seq_ticks, mid_seq]))
audio = synthesis(MIDI.score2opus([mid_seq_ticks, mid_seq]), soundfont_path)
yield txt_mdata, "MIDI-Match-Sample.mid", (44100, audio), plt
#==========================================================================================================
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--share", action="store_true", default=False, help="share gradio app")
parser.add_argument("--port", type=int, default=7860, help="gradio server port")
parser.add_argument("--max-gen", type=int, default=1024, help="max")
opt = parser.parse_args()
soundfont_path = "SGM-v2.01-YamahaGrand-Guit-Bass-v2.7.sf2"
meta_data_path = "meta-data/LAMDa_META_DATA_81000.pickle"
print('Loading meta-data...')
with open(meta_data_path, 'rb') as f:
meta_data = pickle.load(f)
print('Done!')
app = gr.Blocks()
with app:
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>MIDI Match</h1>")
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Upload any MIDI file to find its closest match</h1>")
gr.Markdown("![Visitors](https://api.visitorbadge.io/api/visitors?path=asigalov61.MIDI-Match&style=flat)\n\n"
"Los Angeles MIDI Dataset Search and Explore Demo\n\n"
"Please see [Los Angeles MIDI Dataset](https://github.com/asigalov61/Los-Angeles-MIDI-Dataset) for more information and features\n\n"
"[Open In Colab]"
"(https://colab.research.google.com/github/asigalov61/Los-Angeles-MIDI-Dataset/blob/main/Los_Angeles_MIDI_Dataset_Search_and_Explore.ipynb)"
" for faster execution"
)
gr.Markdown("# Upload MIDI")
maximum_match_ratio = gr.Slider(0.5, 1, value=1.0, label="Maximum match ratio to search for", info="Lower this value to see less precise matches")
input_midi = gr.File(label="Input MIDI", file_types=[".midi", ".mid", ".kar"], type="binary")
gr.Markdown("# Match results")
output_audio = gr.Audio(label="Output MIDI match sample audio", format="mp3", elem_id="midi_audio")
output_plot = gr.Plot(label="Output MIDI match sample plot")
output_midi = gr.File(label="Output MIDI match sample MIDI", file_types=[".mid"])
output_midi_seq = gr.Textbox(label="Output MIDI match metadata")
run_event = input_midi.upload(match_midi, [input_midi, maximum_match_ratio],
[output_midi_seq, output_midi, output_audio, output_plot])
app.queue(1).launch(server_port=opt.port, share=opt.share, inbrowser=True)