asigalov61's picture
Update app.py
6b75c99 verified
raw
history blame
16.6 kB
# =================================================================================================
# https://huggingface.co/spaces/asigalov61/Chords-Progressions-Generator
# =================================================================================================
import os
import time as reqtime
import datetime
from pytz import timezone
import gradio as gr
import numpy as np
import os
import random
from collections import Counter
from tqdm import tqdm
import TMIDIX
from midi_to_colab_audio import midi_to_colab_audio
# =================================================================================================
def Generate_Chords_Progression(total_song_length_in_chords_chunks,
chords_chunks_memory_length,
chord_time_step,
melody_MIDI_patch_number,
chords_progression_MIDI_patch_number,
base_MIDI_patch_number
):
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('Total song length in chords chunks:', total_song_length_in_chords_chunks)
print('Chords chunks memory length:', chords_chunks_memory_length)
print('Chord time step:', chord_time_step)
print('Melody MIDI patch number:', melody_MIDI_patch_number)
print('Chords progression MIDI patch number:', chords_progression_MIDI_patch_number)
print('Base MIDI patch number:', base_MIDI_patch_number)
print('=' * 70)
#==================================================================
print('=' * 70)
print('Pitches Chords Progressions Generator')
print('=' * 70)
print('=' * 70)
print('Chunk-by-chunk generation')
print('=' * 70)
print('Generating...')
print('=' * 70)
matching_long_chords_chunks = []
ridx = random.randint(0, len(all_long_chords_tokens_chunks)-1)
matching_long_chords_chunks.append(ridx)
max_song_len = 0
tries = 0
while len(matching_long_chords_chunks) < total_song_length_in_chords_chunks:
matching_long_chords_chunks = []
ridx = random.randint(0, len(all_long_chords_tokens_chunks)-1)
matching_long_chords_chunks.append(ridx)
seen = [ridx]
for a in range(16):
schunk = all_long_chords_tokens_chunks[matching_long_chords_chunks[-1]]
trg_long_chunk = np.array(schunk[-chunk_size:])
idxs = np.where((src_long_chunks == trg_long_chunk).all(axis=1))[0].tolist()
if len(idxs) > 1:
eidx = random.choice(idxs)
tr = 0
while eidx in seen and tr < 5:
eidx = random.choice(idxs)
tr += 1
if eidx not in seen:
matching_long_chords_chunks.append(eidx)
seen.append(eidx)
if chords_chunks_memory_length > 0:
seen = seen[-chords_chunks_memory_length:]
elif chords_chunks_memory_length == 0:
seen = []
else:
break
else:
break
if len(matching_long_chords_chunks) > max_song_len:
print('Current song length:', len(matching_long_chords_chunks), 'chords chunks')
print('=' * 70)
final_song = matching_long_chords_chunks
max_song_len = max(max_song_len, len(matching_long_chords_chunks))
tries += 1
if tries % 500 == 0:
print('Number of passed tries:', tries)
print('=' * 70)
if len(matching_long_chords_chunks) > max_song_len:
print(len(matching_long_chords_chunks))
final_song = matching_long_chords_chunks
f_song = []
for mat in final_song:
f_song.extend(all_long_good_chords_chunks[mat][:-chunk_size])
f_song.extend(all_long_good_chords_chunks[mat][-chunk_size:])
print('Generated final song after', tries, 'tries with', len(final_song), 'chords chunks and', len(f_song), 'chords')
print('=' * 70)
print('Done!')
print('=' * 70)
#===============================================================================
print('Rendering results...')
print('=' * 70)
output_score = []
time = 0
patches = [0] * 16
patches[0] = chords_progression_MIDI_patch
if base_MIDI_patch_number > -1:
patches[2] = base_MIDI_patch_number
if melody_MIDI_patch_number > -1:
patches[3] = melody_MIDI_patch_number
chords_labels = []
for i, s in enumerate(f_song):
time += chord_time_step
dur = chord_time_step
chord_str = str(i+1)
for t in sorted(set([t % 12 for t in s])):
chord_str += '-' + str(t)
chords_labels.append(['text_event', time, chord_str])
for p in s:
output_score.append(['note', time, dur, 0, p, max(40, p), chords_progression_MIDI_patch])
if base_MIDI_patch_number > -1:
output_score.append(['note', time, dur, 2, (s[-1] % 12)+24, 120-(s[-1] % 12), base_MIDI_patch_number])
if melody_MIDI_patch_number > -1:
output_score = TMIDIX.add_melody_to_enhanced_score_notes(output_score, melody_patch=melody_MIDI_patch_number)
midi_score = sorted(chords_labels + output_score, key=lambda x: x[1])
fn1 = "Pitches-Chords-Progression-Composition"
detailed_stats = TMIDIX.Tegridy_ms_SONG_to_MIDI_Converter(midi_score,
output_signature = 'Pitches Chords Progression',
output_file_name = fn1,
track_name='Project Los Angeles',
list_of_MIDI_patches=patches
)
new_fn = fn1+'.mid'
audio = midi_to_colab_audio(new_fn,
soundfont_path=soundfont,
sample_rate=16000,
volume_scale=10,
output_for_gradio=True
)
print('Done!')
print('=' * 70)
#========================================================
output_midi_title = str(fn1)
output_midi = str(new_fn)
output_audio = (16000, audio)
output_plot = TMIDIX.plot_ms_SONG(output_score, plot_title=output_midi, return_plt=True)
#========================================================
chords_progression_summary_string = '=' * 70
chords_progression_summary_string += '\n'
all_song_chords = []
for pc in f_song:
tones_chord = tuple(sorted(set([p % 12 for p in pc])))
all_song_chords.append([pc, tones_chord])
print('=' * 70)
print('Total number of chords:', len(all_song_chords))
print('=' * 70)
print('Most common pitches chord:', list(Counter(tuple([a[0] for a in all_song_chords])).most_common(1)[0][0]), '===', Counter(tuple([a[0] for a in all_song_chords])).most_common(1)[0][1], 'count')
print('=' * 70)
print('Most common tones chord:', list(Counter(tuple([a[1] for a in all_song_chords])).most_common(1)[0][0]), '===', Counter(tuple([a[1] for a in all_song_chords])).most_common(1)[0][1], 'count')
print('=' * 70)
print('Sorted unique songs chords set:', len(sorted(set(tuple([a[1] for a in all_song_chords])))), 'count')
print('=' * 70)
for c in sorted(set(tuple([a[1] for a in all_song_chords]))):
print(list(c))
print('=' * 70)
print('Grouped songs chords set:', len(TMIDIX.grouped_set(tuple([a[1] for a in all_song_chords]))), ' count')
print('=' * 70)
for c in TMIDIX.grouped_set(tuple([a[1] for a in all_song_chords])):
print(list(c))
print('=' * 70)
print('All songs chords')
print('=' * 70)
for i, pc_tc in enumerate(all_song_chords):
print('Song chord #', i)
print(list(pc_tc[0]), '===', list(pc_tc[1]))
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, chords_progression_summary_string
# =================================================================================================
if __name__ == "__main__":
PDT = timezone('US/Pacific')
print('=' * 70)
print('App start time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT)))
print('=' * 70)
#===============================================================================
soundfont = "SGM-v2.01-YamahaGrand-Guit-Bass-v2.7.sf2"
print('=' * 70)
print('Loading Pitches Chords Progressions dataset...')
print('=' * 70)
good_chords_chunks = TMIDIX.Tegridy_Any_Pickle_File_Reader('pitches_chords_progressions_5_3_15')
print('=' * 70)
print('Done!')
print('=' * 70)
#===============================================================================
minimum_chords_chunk_length = 4 # @param {"type":"slider","min":4,"max":8,"step":1}
chords_chunks_overlap_value = 4 # @param {"type":"slider","min":2,"max":8,"step":1}
print('=' * 70)
print('Selecting chords chunks...')
print('=' * 70)
chunk_size = minimum_chords_chunk_length
long_chords_chunks = []
for c in tqdm(good_chords_chunks):
if chunk_size + chords_chunks_overlap_value <= len(c):
long_chords_chunks.append(c)
print('Done!')
print('=' * 70)
print('Selected chords chunks of minimum length:', minimum_chords_chunk_length+chords_chunks_overlap_value)
print('=' * 70)
print('Total number of selected chord chunks:', len(long_chords_chunks))
print('=' * 70)
chords_chunks_multiplicatrion_factor = 6 # @param {"type":"slider","min":1,"max":6,"step":1}
#===============================================================================
# Helper chord function
#===============================================================================
def check_chord(chord):
tones_chord = sorted(set([p % 12 for p in chord]))
new_tones_chord = []
if 0 in tones_chord and 11 in tones_chord:
tones_chord.remove(11)
for t in tones_chord:
if t+1 in tones_chord:
tones_chord.remove(t+1)
if t-1 in tones_chord:
tones_chord.remove(t-1)
new_chord = tuple()
for p in chord:
if p % 12 in tones_chord:
new_chord += tuple([p])
if len(new_chord) > 2:
return new_chord
else:
return None
#===============================================================================
print('=' * 70)
print('Multiplying chords chunks...')
print('=' * 70)
print('Chords chunks will be multiplied', chords_chunks_multiplicatrion_factor * 2, 'times' )
print('=' * 70)
long_chords_chunks_mult = set()
for c in tqdm(long_chords_chunks):
for tv in range(-chords_chunks_multiplicatrion_factor, chords_chunks_multiplicatrion_factor):
gc = []
for cc in c:
chord = [max(1, min(127, p+tv)) for p in cc]
checked_chord = check_chord(chord)
if checked_chord is not None:
gc.append(checked_chord)
if len(gc) == len(c) or (len(gc) >= chunk_size + chords_chunks_overlap_value and gc == c[:len(gc)]) or (len(gc) >= chunk_size + chords_chunks_overlap_value and gc == c[len(gc):]):
long_chords_chunks_mult.add(tuple(gc))
print('Done!')
print('=' * 70)
print('Total number of multiplied chords chunks:', len(long_chords_chunks_mult))
print('=' * 70)
#===============================================================================
print('=' * 70)
print('Creating chords dictionary...')
print('=' * 70)
long_tones_chords_dict = set()
for a in tqdm(long_chords_chunks_mult):
for aa in a:
tones_chord = tuple(sorted(set([p % 12 for p in aa])))
long_tones_chords_dict.add(tones_chord)
long_tones_chords_dict = list(long_tones_chords_dict)
print('=' * 70)
print('Resulting chords dictionary size:', len(long_tones_chords_dict))
print('=' * 70)
print('Preparing chords chunks...')
print('=' * 70)
all_long_chords_tokens_chunks = []
all_long_good_chords_chunks = []
for a in tqdm(long_chords_chunks_mult):
chunk = []
for aa in a:
tones_chord = tuple(sorted(set([p % 12 for p in aa])))
chunk.append(long_tones_chords_dict.index(tones_chord))
if chunk:
all_long_chords_tokens_chunks.append(chunk)
all_long_good_chords_chunks.append(a)
print('Done!')
print('=' * 70)
print('Loading chords chunks...')
src_long_chunks = np.array([a[:chunk_size] for a in all_long_chords_tokens_chunks])
print('Done!')
print('=' * 70)
print('Total chords chunks count:', len(all_long_good_chords_chunks))
print('=' * 70)
#===============================================================================
app = gr.Blocks()
with app:
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Chords Progressions Generator</h1>")
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Generate unique chords progressions</h1>")
gr.Markdown(
"![Visitors](https://api.visitorbadge.io/api/visitors?path=asigalov61.Chords-Progressions-Generator&style=flat)\n\n"
"This is a demo for Tegridy MIDI Dataset\n\n"
"Check out [Tegridy MIDI Dataset](https://github.com/asigalov61/Tegridy-MIDI-Dataset) on GitHub!\n\n"
"[Open In Colab]"
"(https://colab.research.google.com/github/asigalov61/Tegridy-MIDI-Dataset/blob/master/Chords-Progressions/Pitches_Chords_Progressions_Generator.ipynb)"
" for all options, faster execution and endless generation"
)
gr.Markdown("## Select generation options")
total_song_length_in_chords_chunks = gr.Slider(5, 20, value=13, step=1, label="Total song length in chords chunks")
chords_chunks_memory_length = gr.Slider(-1, 30, value=-1, step=1, label="Chords chunks memory length")
chord_time_step = gr.Slider(100, 1000, value=500, step=50, label="Chord time step")
melody_MIDI_patch_number = gr.Slider(0, 127, value=40, step=1, label="Melody MIDI patch number")
chords_progression_MIDI_patch_number = gr.Slider(0, 127, value=0, step=1, label="Chords progression MIDI patch number")
base_MIDI_patch_number = gr.Slider(0, 127, value=35, step=1, label="Base MIDI patch number")
run_btn = gr.Button("generate", variant="primary")
gr.Markdown("## Generation results")
output_audio = gr.Audio(label="Output MIDI audio", format="wav", elem_id="midi_audio")
output_plot = gr.Plot(label="Output MIDI score plot")
output_midi = gr.File(label="Output MIDI file", file_types=[".mid"])
output_cp_summary = gr.Textbox(label="Generated chords progression info and stats")
run_event = run_btn.click(Generate_Chords_Progression,
[total_song_length_in_chords_chunks,
chords_chunks_memory_length,
chord_time_step,
melody_MIDI_patch_number,
chords_progression_MIDI_patch_number,
base_MIDI_patch_number],
[output_audio, output_plot, output_midi, output_cp_summary]
)
app.queue().launch()