-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alex
authored
Jan 27, 2021
1 parent
f56e2f8
commit c835ed6
Showing
1 changed file
with
324 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,324 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Markovify_Piano.ipynb | ||
Automatically generated by Colaboratory. | ||
Original file is located at | ||
https://colab.research.google.com/drive/17SGUaA8r6nZyHHH3huOmgcsVUDH6HBWE | ||
# Markovify Piano | ||
*** | ||
## Based upon absolutely amazing markovify package of @jsvine: https://github.com/jsvine/markovify | ||
## Powered by tegridy-tools TMIDI 1.4 Processors | ||
*** | ||
### Project Los Angeles | ||
### Tegridy Code 2021 | ||
*** | ||
# Setup environment | ||
""" | ||
|
||
#@title Install dependencies | ||
!git clone https://github.com/asigalov61/tegridy-tools | ||
!pip install unidecode | ||
!pip install tqdm | ||
!apt install fluidsynth #Pip does not work for some reason. Only apt works | ||
!pip install midi2audio | ||
|
||
#@title Load needed modules | ||
print('Loading needed modules. Please wait...') | ||
|
||
import sys | ||
import os | ||
import json | ||
|
||
os.chdir('/content/tegridy-tools/tegridy-tools/') | ||
import TMIDI | ||
import markovify | ||
os.chdir('/content/') | ||
|
||
from pprint import pprint | ||
|
||
import tqdm.auto | ||
|
||
from midi2audio import FluidSynth | ||
from IPython.display import display, Javascript, HTML, Audio | ||
|
||
from google.colab import output, drive | ||
|
||
print('Creating Dataset dir...') | ||
if not os.path.exists('/content/Dataset'): | ||
os.makedirs('/content/Dataset') | ||
|
||
os.chdir('/content/') | ||
print('Loading complete. Enjoy! :)') | ||
|
||
"""# Download/upload desired MIDI dataset | ||
## NOTE: Dataset must be sufficiently large and homogenous for Markov chain to train/perform properly. | ||
""" | ||
|
||
# Commented out IPython magic to ensure Python compatibility. | ||
#@title Download World Melody-ABC-Tunes MIDI dataset | ||
|
||
#@markdown Works best stand-alone/as-is for the optimal results | ||
# %cd /content/Dataset/ | ||
|
||
!wget --no-check-certificate -O Melody-ABC-Tunes-Dataset-CC-BY-NC-SA.zip "https://onedrive.live.com/download?cid=8A0D502FC99C608F&resid=8A0D502FC99C608F%2118458&authkey=AOXe4AaDtageB-k" | ||
!unzip -j '/content/Dataset/Melody-ABC-Tunes-Dataset-CC-BY-NC-SA.zip' | ||
!rm '/content/Dataset/Melody-ABC-Tunes-Dataset-CC-BY-NC-SA.zip' | ||
|
||
# %cd /content/ | ||
|
||
"""# Process the MIDI dataset | ||
## NOTE: If you are not sure what settings to select, please use original defaults | ||
""" | ||
|
||
#@title Process MIDIs to special MIDI dataset with Tegridy MIDI Processor | ||
#@markdown NOTES: | ||
|
||
#@markdown 1) Best results are achieved with the single-track, single-channel, single-instrument MIDI 0 files. | ||
|
||
#@markdown 2) MIDI Channel = -1 means all MIDI channels but drums will be processed. MIDI Channel = 16 means all channels will be processed. Otherwise, only single indicated MIDI channel will be processed. | ||
|
||
file_name_to_output_dataset_to = "/content/Markovify-Piano-Music-MIDI-Dataset" #@param {type:"string"} | ||
desired_MIDI_channel_to_process = 0 #@param {type:"slider", min:-1, max:15, step:1} | ||
|
||
MIDI_events_time_denominator = 10 | ||
melody_notes_in_chords = True | ||
|
||
print('TMIDI Processor') | ||
print('Starting up...') | ||
|
||
########### | ||
|
||
average_note_pitch = 0 | ||
min_note = 127 | ||
max_note = 0 | ||
|
||
files_count = 0 | ||
|
||
ev = 0 | ||
|
||
chords_list_f = [] | ||
melody_list_f = [] | ||
|
||
chords_list = [] | ||
chords_count = 0 | ||
|
||
melody_chords = [] | ||
melody_count = 0 | ||
|
||
########### | ||
|
||
print('Loading MIDI files...') | ||
print('This may take a while on a large dataset in particular.') | ||
|
||
dataset_addr = "/content/Dataset/" | ||
os.chdir(dataset_addr) | ||
filez = os.listdir(dataset_addr) | ||
|
||
print('Processing MIDI files. Please wait...') | ||
for f in tqdm.auto.tqdm(filez): | ||
try: | ||
files_count += 1 | ||
chords_list, melody = TMIDI.Tegridy_MIDI_Processor(f, | ||
desired_MIDI_channel_to_process, | ||
MIDI_events_time_denominator, | ||
) | ||
|
||
fn = os.path.basename(f) | ||
fno = fn.split('.')[0].replace(' ', '_') | ||
|
||
chords_l, melody_l = TMIDI.Tegridy_Chords_Converter(chords_list, | ||
melody, | ||
fno, | ||
melody_notes_in_chords) | ||
|
||
chords_list_f.extend(chords_l) | ||
melody_list_f.extend(melody_l) | ||
chords_count += len(chords_list) | ||
melody_count += len(melody_l) | ||
|
||
except: | ||
print('Problematic MIDI:', f) | ||
continue | ||
|
||
average_note_pitch = int((min_note + max_note) / 2) | ||
|
||
print('Task complete :)') | ||
print('==================================================') | ||
print('Number of processed dataset MIDI files:', files_count) | ||
print('Average note pitch =', average_note_pitch) | ||
#print('Min note pitch =', min_note) | ||
#print('Max note pitch =', max_note) | ||
#print('Number of MIDI events recorded:', len(events_matrix)) | ||
print('Number of MIDI chords recorded:', chords_count) | ||
print('The longest chord:', len(max(chords_list_f, key=len)), 'notes') | ||
print(max(chords_list_f, key=len)) | ||
print('Number of recorded melody events:', len(melody_list_f)) | ||
print('First melody event:', melody_list_f[0], 'Last Melody event:', melody_list_f[-1]) | ||
print('Total number of MIDI events recorded:', len(chords_list_f)) | ||
|
||
# Dataset | ||
MusicDataset = [chords_list_f, melody_list_f] | ||
|
||
# Writing dataset to pickle file | ||
TMIDI.Tegridy_Pickle_File_Writer(MusicDataset, file_name_to_output_dataset_to) | ||
|
||
#@title Process MIDI Dataset to TXT Dataset (w/Tegridy MIDI-TXT Processor) | ||
full_path_to_TXT_dataset = "/content/Markovify-Piano-Music-TXT-Dataset.txt" #@param {type:"string"} | ||
simulate_velocity = True #@param {type:"boolean"} | ||
|
||
reduce_MIDI_channels = False | ||
reduce_notes_velocities = False | ||
line_by_line_dataset = True | ||
chords_durations_multiplier = 1 | ||
|
||
# MIDI Dataset to txt dataset converter | ||
print('TMIDI-TXT Processor') | ||
print('Starting up...') | ||
|
||
if simulate_velocity: | ||
print('Simulated velocity mode is enabled.') | ||
|
||
TXT = '' | ||
number_of_chords = 0 | ||
number_of_bad_chords = 0 | ||
dataset_name = 'DATASET=Intelligent_VIRTUOSO_TXT_Music_Dataset' | ||
|
||
TXT, number_of_chords, number_of_bad_chords = TMIDI.Tegridy_MIDI_TXT_Processor(dataset_name, | ||
chords_list_f, | ||
melody_list_f, | ||
simulate_velocity, | ||
line_by_line_dataset, | ||
0, | ||
chords_durations_multiplier, | ||
) | ||
|
||
print('Number of chords recorded: ', number_of_chords) | ||
print('Number of bad/skipped chords: ', number_of_bad_chords) | ||
print('Done!') | ||
|
||
TXT1, n = TMIDI.Tegridy_TXT_Reducer(TXT, | ||
include_MIDI_channels=reduce_MIDI_channels, | ||
include_notes_velocities=reduce_notes_velocities, | ||
line_by_line_output_dataset=False) | ||
|
||
TMIDI.Tegridy_TXT_Dataset_File_Writer(full_path_to_TXT_dataset, TXT_String=TXT1) | ||
|
||
"""# Load processed TXT MIDI dataset into memory""" | ||
|
||
#@title Load/Reload processed TXT dataset | ||
full_path_to_TXT_dataset = "/content/Markovify-Piano-Music-TXT-Dataset.txt" #@param {type:"string"} | ||
|
||
print('Loading TXT MIDI dataset. Please wait...') | ||
with open(full_path_to_TXT_dataset) as f: | ||
text = f.read() | ||
print('Dataset loaded! Enjoy :)') | ||
|
||
"""# Train TXT Markov chain/model""" | ||
|
||
#@title Train Markov-chain/model | ||
markov_chain_state_size = 4 #@param {type:"slider", min:1, max:10, step:1} | ||
|
||
print('Training Markov chain/model. Please wait...') | ||
markov_text_model = markovify.NewlineText(text, well_formed=False, state_size=markov_chain_state_size) | ||
|
||
print('Model is ready! Enjoy :)') | ||
|
||
#@title Save the model | ||
full_path_to_json_save_file = "/content/Markovify-Piano-Music-Model.json" #@param {type:"string"} | ||
|
||
print('Converting model to json...') | ||
model_json = markov_text_model.to_json() | ||
|
||
print('Saving model as json file...') | ||
with open(full_path_to_json_save_file, 'w') as f: | ||
json.dump(model_json, f) | ||
|
||
print('Task complete! Enjoy! :)') | ||
|
||
#@title Load/Re-load saved model | ||
full_path_to_json_save_file = "/content/Markovify-Piano-Music-Model.json" #@param {type:"string"} | ||
|
||
print('Loading model from json file...') | ||
f = open(full_path_to_json_save_file) | ||
model_json = json.load(f) | ||
|
||
print('Restoring the model...') | ||
markov_text_model = markovify.Text.from_json(model_json) | ||
|
||
print('Model loaded and restored! Enjoy! :)') | ||
|
||
"""# Generate music composition""" | ||
|
||
#@title Generate Music | ||
|
||
#@markdown HINT: Each note = 3-5 characters depending on the MIDI processing settings above | ||
minimum_number_of_characters_to_generate = 5000 #@param {type:"slider", min:100, max:10000, step:100} | ||
number_of_cycles_to_try_to_generate_desired_result = 5000 #@param {type:"slider", min:10, max:10000, step:10} | ||
minimum_notes_to_generate = 210 #@param {type:"slider", min:10, max:1000, step:10} | ||
print_generated_song = False #@param {type:"boolean"} | ||
|
||
Output_TXT_String = '' | ||
|
||
attempt = 0 | ||
|
||
print('Generating music composition. Please wait...') | ||
|
||
while (len(Output_TXT_String.split(' ')[1:])-2) < minimum_notes_to_generate: | ||
out = markov_text_model.make_sentence(min_chars=minimum_number_of_characters_to_generate, | ||
tries=number_of_cycles_to_try_to_generate_desired_result) | ||
|
||
Output_TXT_String = ''.join(out) | ||
print('Attempt #', attempt) | ||
attempt += 1 | ||
|
||
print('Generation complete!') | ||
print('=' * 70) | ||
print(Output_TXT_String.split(' ')[0], 'with', len(Output_TXT_String.split(' ')[1:])-2, 'notes.') | ||
print('=' * 70) | ||
|
||
if print_generated_song: | ||
pprint(Output_TXT_String) | ||
print('=' * 70) | ||
|
||
"""# Convert generated music composition to MIDI file and download/listen to the output :)""" | ||
|
||
#@title Convert generated song to MIDI | ||
download_generated_composition = False #@param {type:"boolean"} | ||
show_detailed_MIDI_stats = False #@param {type:"boolean"} | ||
|
||
print('Converting generated song from TXT to MIDI events...') | ||
SONG, SONG_Name = TMIDI.Tegridy_Reduced_TXT_to_Notes_Converter(Output_TXT_String, line_by_line_dataset=False, has_MIDI_channels=False, has_velocities=False) | ||
|
||
fname = TMIDI.Tegridy_File_Time_Stamp('/content/Markovify_Piano_Composition_Generated_on_') | ||
print('Composition', fname + '.mid') | ||
|
||
detailed_stats = TMIDI.Tegridy_SONG_to_MIDI_Converter(SONG=SONG, | ||
output_signature='Markovify_Piano', | ||
track_name=SONG_Name, | ||
output_file_name=fname) | ||
|
||
if download_generated_composition: | ||
print('Downloading your composition now...') | ||
from google.colab import files | ||
files.download(fname + '.mid') | ||
|
||
if show_detailed_MIDI_stats: | ||
print('Detailed MIDI stats:') | ||
pprint(detailed_stats) | ||
|
||
#@title Listen to the last generated composition | ||
#@markdown NOTE: May be very slow with the long compositions | ||
print('Synthesizing the last output MIDI. Please stand-by... ') | ||
FluidSynth("/usr/share/sounds/sf2/FluidR3_GM.sf2", 16000).midi_to_audio(str(fname + '.mid'), str(fname + '.wav')) | ||
Audio(str(fname + '.wav'), rate=16000) |