#================================================================================== # 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("

MIDI Remixer

") gr.Markdown("

Remix repeating parts of any MIDI into one song

") gr.HTML("""

Duplicate in Hugging Face

""") #================================================================================== 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() #==================================================================================