asigalov61's picture
Update app.py
e4ed439 verified
raw
history blame
13.5 kB
#==================================================================================
# https://huggingface.co/spaces/asigalov61/MIDI-Remixer
#==================================================================================
print('=' * 70)
print('MIDI Remixer Gradio App')
print('=' * 70)
print('Loading core MIDI Remixer modules...')
import os
import copy
import statistics
import random
from collections import Counter
import time as reqtime
import datetime
from pytz import timezone
import tqdm
print('=' * 70)
print('Loading main MIDI Remixer modules...')
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 Remixer 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'
#==================================================================================
def split_by_sublist(main_list, sublist):
n = len(sublist)
indices = []
result = []
# Find all starting indices where sublist matches main_list
for i in range(len(main_list) - n + 1):
if main_list[i:i + n] == sublist:
indices.append(i)
if not indices:
# If the sublist is not found, return the entire main_list as one segment
return [main_list]
# Use the indices to split the main_list
for idx, start_index in enumerate(indices):
if idx < len(indices) - 1:
# Slice from current sublist start to the next sublist start
end_index = indices[idx + 1]
segment = main_list[start_index:end_index]
else:
# For the last sublist occurrence, include the rest of the list
segment = main_list[start_index:]
result.append(segment)
return result
#==================================================================================
def fuzzy_find(values, threshold=256):
result = set()
for i in range(len(values)):
for j in range(i+1, len(values)):
if abs(values[i] - values[j]) <= threshold:
result.add(values[j])
result.add(values[i])
return sorted(result)
#==================================================================================
def kmp_search(main_list, sublist):
if not sublist:
return -1, -1 # Sublist is empty
# Preprocess the sublist to get the longest prefix-suffix array
lps = [0] * len(sublist)
length = 0 # Length of the previous longest prefix suffix
i = 1
while i < len(sublist):
if sublist[i] == sublist[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
# Start the search
i = j = 0 # Index for main_list and sublist
while i < len(main_list):
if main_list[i] == sublist[j]:
i += 1
j += 1
if j == len(sublist):
start_index = i - j
end_index = i - 1
return start_index, end_index
elif i < len(main_list) and main_list[i] != sublist[j]:
if j != 0:
j = lps[j - 1]
else:
i += 1
return -1, -1 # Sublist not found
#==================================================================================
def load_midi(midi_file):
print('Loading MIDI...')
raw_score = TMIDIX.midi2single_track_ms_score(midi_file)
escore_notes = TMIDIX.advanced_score_processor(raw_score, return_enhanced_score_notes=True)[0]
escore_notes = TMIDIX.augment_enhanced_score_notes(escore_notes, sort_drums_last=True)
zscore = TMIDIX.recalculate_score_timings(escore_notes)
cscore = TMIDIX.chordify_score([1000, zscore])
tones_chords = []
tones_chords_idxs = []
for i, c in enumerate(cscore):
pitches = [e[4] for e in c if e[3] != 9]
if pitches:
tones_chord = sorted(set([p % 12 for p in pitches]))
if tones_chord in TMIDIX.ALL_CHORDS_SORTED:
chord_token = TMIDIX.ALL_CHORDS_SORTED.index(tones_chord)
else:
tones_chord = TMIDIX.check_and_fix_tones_chord(tones_chord)
chord_token = TMIDIX.ALL_CHORDS_SORTED.index(tones_chord)
tones_chords.append(chord_token)
tones_chords_idxs.append(i)
print('Done!')
print('=' * 70)
print('MIDI has', len(tones_chords), 'chords')
print('=' * 70)
return cscore, tones_chords, tones_chords_idxs
#==================================================================================
def Remix_MIDI(input_midi,
chords_chunks_len,
num_mix_chunks
):
#===============================================================================
print('=' * 70)
print('Req start time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT)))
start_time = reqtime.time()
print('=' * 70)
fn = os.path.basename(input_midi)
fn1 = fn.split('.')[0]
print('=' * 70)
print('Requested settings:')
print('=' * 70)
print('Input MIDI file name:', fn)
print('Chords chunks len:', chords_chunks_len)
print('Num mix chunks:', num_mix_chunks)
print('=' * 70)
#===============================================================================
cscore, tones_chords, tones_chords_idxs = load_midi(input_midi.name)
#===============================================================================
print('Prepping chords chunks...')
chunk_len = chords_chunks_len
all_chords_chunks = Counter()
for i in range(0, len(tones_chords)):
chunk = tones_chords[i:i+chunk_len]
if len(chunk) == chunk_len:
all_chords_chunks[tuple(chunk)] += 1
print('Done!')
print('=' * 70)
print('Number of chords chunks:', len(all_chords_chunks))
print('=' * 70)
#==================================================================
print('Remixing MIDI...')
print('=' * 70)
other_chunks = [list(k) for k, v in all_chords_chunks.most_common(10)]
all_match_chunks = {}
for mc_chunk in other_chunks:
chunk_lens = fuzzy_find([len(l) for l in split_by_sublist(tones_chords, mc_chunk) if len(l) > chunk_len])
match_chunks = [l for l in split_by_sublist(tones_chords, mc_chunk) if len(l) in chunk_lens]
all_match_chunks[tuple(mc_chunk)] = match_chunks
#==================================================================
chunks_map = random.choices(other_chunks, k=num_mix_chunks)
#==================================================================
matched_chunks = []
start_chunk = []
res = -1
tries = 0
while res == -1 and len(start_chunk[:res]) < chunk_len and tries < 100:
start_chunk = random.choice(all_match_chunks[tuple(chunks_map[0])])
res = kmp_search(start_chunk, chunks_map[1])[0]
tries += 1
#print(res)
if tries < 100:
matched_chunks.append(start_chunk[:res])
else:
print('FAIL!')
#==================================================================
mchunks = [-1]
tries = 0
while -1 in mchunks and tries < 100:
mchunks = []
for i, chunk in enumerate(chunks_map[1:-1]):
start_chunk = random.choice(all_match_chunks[tuple(chunk)])
res = kmp_search(start_chunk, chunks_map[i+2])[0]
#print(res)
mchunks.append(start_chunk[:res])
tries += 1
if tries < 100:
matched_chunks.extend(mchunks)
else:
print('FAIL!')
#==================================================================
print('=' * 70)
print('Done!')
print('=' * 70)
#===============================================================================
print('Creating final MIDI score...')
all_matches_dscores = []
for match in matched_chunks:
start, end = kmp_search(tones_chords, match)
sidx = tones_chords_idxs[start]
eidx = tones_chords_idxs[end]
all_matches_dscores.append(TMIDIX.delta_score_notes(TMIDIX.flatten(cscore[sidx:eidx+1])))
#===============================================================================
new_dscore = all_matches_dscores[0]
for score in all_matches_dscores[1:]:
score[0][1] = statistics.mode([e[1] for e in new_dscore[-75:] if e[1] != 0 and e[3] != 9])
new_dscore.extend(score)
new_escore = TMIDIX.delta_score_to_abs_score(new_dscore)
#===============================================================================
print('Done!')
print('=' * 70)
#===============================================================================
print('Rendering results...')
print('=' * 70)
print('Sample MIDI events:', new_escore[:3])
print('=' * 70)
output_score, patches, overflow_patches = TMIDIX.patch_enhanced_score_notes(new_escore)
fn1 = "MIDI-Remixer-Composition"
detailed_stats = TMIDIX.Tegridy_ms_SONG_to_MIDI_Converter(output_score,
output_signature = 'MIDI Remixer',
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 = 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('-' * 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_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 Remixer</h1>")
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Remix repeating parts of any MIDI into one song</h1>")
gr.HTML("""
<p>
<a href="https://huggingface.co/spaces/asigalov61/MIDI-Remixer?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("## Upload source MIDI")
input_midi = gr.File(label="Input MIDI", file_types=[".midi", ".mid", ".kar"])
gr.Markdown("## Mixing options")
chords_chunks_len = gr.Slider(3, 7, value=4, step=1, label="Number of chords to match for each repeating chunk")
num_mix_chunks = gr.Slider(4, 20, value=10, step=1, label="Number of repeating chunks to mix")
mix_btn = gr.Button("Remix", variant="primary")
gr.Markdown("## Mixing results")
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(Remix_MIDI,
[input_midi,
chords_chunks_len,
num_mix_chunks
],
[output_audio,
output_plot,
output_midi
]
)
#==================================================================================
demo.launch()
#==================================================================================