Spaces:
Sleeping
Sleeping
va35
commited on
Commit
·
5fc3f34
1
Parent(s):
8ce6b5b
Naive attempt using Markov chains
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- markov_chain.py +233 -16
- new_song.mid +0 -0
- output.txt +0 -0
- prepared.csv +0 -0
- tracks/0.npy +0 -0
- tracks/1.npy +0 -0
- tracks/10.npy +0 -0
- tracks/100.npy +0 -0
- tracks/101.npy +0 -0
- tracks/102.npy +0 -0
- tracks/103.npy +0 -0
- tracks/104.npy +0 -0
- tracks/105.npy +0 -0
- tracks/106.npy +0 -0
- tracks/107.npy +0 -0
- tracks/108.npy +0 -0
- tracks/109.npy +0 -0
- tracks/11.npy +0 -0
- tracks/110.npy +0 -0
- tracks/111.npy +0 -0
- tracks/112.npy +0 -0
- tracks/113.npy +0 -0
- tracks/114.npy +0 -0
- tracks/115.npy +0 -0
- tracks/116.npy +0 -0
- tracks/117.npy +0 -0
- tracks/118.npy +0 -0
- tracks/119.npy +0 -0
- tracks/12.npy +0 -0
- tracks/120.npy +0 -0
- tracks/121.npy +0 -0
- tracks/122.npy +0 -0
- tracks/123.npy +0 -0
- tracks/124.npy +0 -0
- tracks/125.npy +0 -0
- tracks/126.npy +0 -0
- tracks/127.npy +0 -0
- tracks/128.npy +0 -0
- tracks/129.npy +0 -0
- tracks/13.npy +0 -0
- tracks/130.npy +0 -0
- tracks/131.npy +0 -0
- tracks/132.npy +0 -0
- tracks/133.npy +0 -0
- tracks/134.npy +0 -0
- tracks/135.npy +0 -0
- tracks/136.npy +0 -0
- tracks/137.npy +0 -0
- tracks/138.npy +0 -0
- tracks/139.npy +0 -0
markov_chain.py
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
|
|
| 1 |
import os
|
| 2 |
-
from mido import MidiFile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
#number of notes to be used for prediction
|
| 4 |
window = 3
|
| 5 |
|
|
@@ -18,18 +25,228 @@ def abs_paths(dir):
|
|
| 18 |
for dir_path,_,filenames in os.walk(dir):
|
| 19 |
for f in filenames:
|
| 20 |
yield os.path.abspath(os.path.join(dir_path, f))
|
| 21 |
-
def pitch_to_int():
|
| 22 |
-
#
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
import os
|
| 3 |
+
# from mido import MidiFile
|
| 4 |
+
import mido
|
| 5 |
+
import music21
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pandas as pd
|
| 8 |
+
from music21 import *
|
| 9 |
+
from mido import Message, MidiFile, MidiTrack
|
| 10 |
#number of notes to be used for prediction
|
| 11 |
window = 3
|
| 12 |
|
|
|
|
| 25 |
for dir_path,_,filenames in os.walk(dir):
|
| 26 |
for f in filenames:
|
| 27 |
yield os.path.abspath(os.path.join(dir_path, f))
|
| 28 |
+
def pitch_to_int(nameWithOctave):
|
| 29 |
+
# letter names with corresponding values
|
| 30 |
+
letter_dict = {'C':0,'D':2,'E':4,'F':5,'G':7,'A':9,'B':11}
|
| 31 |
+
# parse characters from strings
|
| 32 |
+
chars = list(nameWithOctave)
|
| 33 |
+
# convert octave number to corresponding midi value
|
| 34 |
+
octave = 12*(int(chars[-1])+1)
|
| 35 |
+
# select value from letter_dict using first character
|
| 36 |
+
note = letter_dict[chars[0]]
|
| 37 |
+
# set accidental value
|
| 38 |
+
accidental = 0
|
| 39 |
+
# does accidental exist?
|
| 40 |
+
if not len(chars)==2:
|
| 41 |
+
# increase (sharp) or decrease (flat) value by one
|
| 42 |
+
accidental = 1 if chars[1]=='#' else -1
|
| 43 |
+
# return sum of these numbers, middle C(4) == 60
|
| 44 |
+
return octave + note + accidental
|
| 45 |
+
def generate_notes():
|
| 46 |
+
df_notes = pd.read_csv('prepared.csv')
|
| 47 |
+
print(df_notes.shape)
|
| 48 |
+
# define arrays for generated notes and durations
|
| 49 |
+
gen_notes = []
|
| 50 |
+
gen_durations = []
|
| 51 |
+
# define note and duration feature columns based on names
|
| 52 |
+
features = df_notes.columns[:-2]
|
| 53 |
+
note_features = [s for s in features if "note" in s]
|
| 54 |
+
duration_features = [s for s in features if "duration" in s]
|
| 55 |
+
# define target columns
|
| 56 |
+
note_target = df_notes.columns[-2]
|
| 57 |
+
duration_target = df_notes.columns[-1]
|
| 58 |
+
|
| 59 |
+
# sample random row from dataframe and define start notes and durations
|
| 60 |
+
initial_sample = df_notes.sample()
|
| 61 |
+
start_notes = list(initial_sample[note_features].values[0])
|
| 62 |
+
start_durations = list(initial_sample[duration_features].values[0])
|
| 63 |
+
# append starting notes and durations to gen arrays
|
| 64 |
+
for note in start_notes:
|
| 65 |
+
gen_notes.append(int(note))
|
| 66 |
+
for duration in start_durations:
|
| 67 |
+
gen_durations.append(duration)
|
| 68 |
+
|
| 69 |
+
for i in range(num_notes) :
|
| 70 |
+
rows = df_notes
|
| 71 |
+
for i in range(window-1):
|
| 72 |
+
rows = rows.loc[df_notes[note_features[i]] == start_notes[i]]
|
| 73 |
+
rows = rows.loc[df_notes[duration_features[i]]== start_durations[i]]
|
| 74 |
+
|
| 75 |
+
#This gives the same effect as probability.
|
| 76 |
+
# We effectively sample from a list which might have more than 1 C note, Hence increasing its probability
|
| 77 |
+
#Sometime, The start_notes and durations could be selected in such a way that we cannot generate any further notes uptill num_notes,
|
| 78 |
+
#This means there maybe some combinations of notes such as 76,68 which are not there in the dataset and hence cannot be sampled.
|
| 79 |
+
#In such cases, the only way about it would be to reset the start notes, because we cannot sample from an empty row
|
| 80 |
+
#So here we check if any rows which we ta
|
| 81 |
+
if len(rows):
|
| 82 |
+
next_sample = rows.sample()
|
| 83 |
+
next_note = next_sample[note_target].values[0]
|
| 84 |
+
next_duration = next_sample[duration_target].values[0]
|
| 85 |
+
gen_notes.append(int(next_note))
|
| 86 |
+
gen_durations.append(next_duration)
|
| 87 |
+
|
| 88 |
+
start_notes.pop()
|
| 89 |
+
start_durations.pop()
|
| 90 |
+
|
| 91 |
+
start_notes.append(next_note)
|
| 92 |
+
start_durations.append(next_duration)
|
| 93 |
+
else:
|
| 94 |
+
#Received empty row
|
| 95 |
+
# print("Exiting!!!!!!")
|
| 96 |
+
#restarting again to get new start notes
|
| 97 |
+
return [],[]
|
| 98 |
+
|
| 99 |
+
# print(rows[note_target].value_counts(normalize=True))
|
| 100 |
+
# print(rows[duration_target].value_counts(normalize=True))
|
| 101 |
+
|
| 102 |
+
return gen_notes, gen_durations
|
| 103 |
+
|
| 104 |
+
#MAIN FUNCTION
|
| 105 |
+
if __name__=="__main__":
|
| 106 |
+
# https://stackoverflow.com/questions/49462107/how-can-i-get-all-piano-parts-from-a-music21-score
|
| 107 |
+
if not os.path.exists('tracks'):
|
| 108 |
+
os.mkdir('tracks')
|
| 109 |
+
i = 0
|
| 110 |
+
#Parse midi files into tracks folder
|
| 111 |
+
for path in abs_paths('data'):
|
| 112 |
+
print(path)
|
| 113 |
+
# mid = MidiFile(path)
|
| 114 |
+
piece = converter.parse(path)
|
| 115 |
+
print(list(piece.parts))
|
| 116 |
+
for part in piece.parts:
|
| 117 |
+
part_notes = []
|
| 118 |
+
#get all note messages from all tracks
|
| 119 |
+
for event in part:
|
| 120 |
+
if getattr(event, 'isNote', None) and event.isNote:
|
| 121 |
+
print('note in {}'.format(part))
|
| 122 |
+
|
| 123 |
+
#check if note is in accepted length
|
| 124 |
+
#convert string to numerical value
|
| 125 |
+
if event.quarterLength in accepeted_lengths:
|
| 126 |
+
part_notes.append([pitch_to_int(event.nameWithOctave), event.quarterLength])
|
| 127 |
+
if not len(part_notes) == 0:
|
| 128 |
+
np.save('tracks/{}.npy'.format(i), np.array(part_notes))
|
| 129 |
+
i+=1
|
| 130 |
+
print('Number of tracks parsed: {}'.format(i))
|
| 131 |
+
if not os.path.exists('prepared.csv'):
|
| 132 |
+
columns = []
|
| 133 |
+
for i in range(window):
|
| 134 |
+
columns.append('note' + str(i))
|
| 135 |
+
columns.append('duration' + str(i))
|
| 136 |
+
df_notes = pd.DataFrame(columns=columns)
|
| 137 |
+
# append segments from each track as rows to dataframe
|
| 138 |
+
for path in abs_paths('tracks'):
|
| 139 |
+
notes = np.load(path)
|
| 140 |
+
for i in range(len(notes)-window):
|
| 141 |
+
# take every x notes and durations
|
| 142 |
+
segment = notes[i:i+window].flatten()
|
| 143 |
+
# make into pd.Series row
|
| 144 |
+
row = pd.Series(segment, index=df_notes.columns)
|
| 145 |
+
# append row to dataframe
|
| 146 |
+
df_notes = df_notes.append(row, ignore_index=True)
|
| 147 |
+
# export
|
| 148 |
+
df_notes.to_csv('prepared.csv', index=False)
|
| 149 |
+
success = False
|
| 150 |
+
gen_notes =[]
|
| 151 |
+
gen_durations =[]
|
| 152 |
+
|
| 153 |
+
#Retry mechanism
|
| 154 |
+
while len(gen_notes)<num_notes:
|
| 155 |
+
gen_notes,gen_durations = generate_notes()
|
| 156 |
+
|
| 157 |
+
# import
|
| 158 |
+
# df_notes = pd.read_csv('prepared.csv')
|
| 159 |
+
# print(df_notes.shape)
|
| 160 |
+
# # define arrays for generated notes and durations
|
| 161 |
+
# gen_notes = []
|
| 162 |
+
# gen_durations = []
|
| 163 |
+
# # define note and duration feature columns based on names
|
| 164 |
+
# features = df_notes.columns[:-2]
|
| 165 |
+
# note_features = [s for s in features if "note" in s]
|
| 166 |
+
# duration_features = [s for s in features if "duration" in s]
|
| 167 |
+
# # define target columns
|
| 168 |
+
# note_target = df_notes.columns[-2]
|
| 169 |
+
# duration_target = df_notes.columns[-1]
|
| 170 |
+
|
| 171 |
+
# # sample random row from dataframe and define start notes and durations
|
| 172 |
+
# initial_sample = df_notes.sample()
|
| 173 |
+
# start_notes = list(initial_sample[note_features].values[0])
|
| 174 |
+
# start_durations = list(initial_sample[duration_features].values[0])
|
| 175 |
+
# # append starting notes and durations to gen arrays
|
| 176 |
+
# for note in start_notes:
|
| 177 |
+
# gen_notes.append(int(note))
|
| 178 |
+
# for duration in start_durations:
|
| 179 |
+
# gen_durations.append(duration)
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
# for i in range(num_notes) :
|
| 183 |
+
# rows = df_notes
|
| 184 |
+
# for i in range(window-1):
|
| 185 |
+
# rows = rows.loc[df_notes[note_features[i]] == start_notes[i]]
|
| 186 |
+
# rows = rows.loc[df_notes[duration_features[i]]== start_durations[i]]
|
| 187 |
+
|
| 188 |
+
# #This gives the same effect as probability.
|
| 189 |
+
# # We effectively sample from a list which might have more than 1 C note, Hence increasing its probability
|
| 190 |
+
# #Sometime, The start_notes and durations could be selected in such a way that we cannot generate any further notes uptill num_notes,
|
| 191 |
+
# #This means there maybe some combinations of notes such as 76,68 which are not there in the dataset and hence cannot be sampled.
|
| 192 |
+
# #In such cases, the only way about it would be to reset the start notes, because we cannot sample from an empty row
|
| 193 |
+
# #So here we check if any rows which we ta
|
| 194 |
+
# if len(rows):
|
| 195 |
+
# next_sample = rows.sample()
|
| 196 |
+
# next_note = next_sample[note_target].values[0]
|
| 197 |
+
# next_duration = next_sample[duration_target].values[0]
|
| 198 |
+
# gen_notes.append(int(next_note))
|
| 199 |
+
# gen_durations.append(next_duration)
|
| 200 |
+
|
| 201 |
+
# start_notes.pop()
|
| 202 |
+
# start_durations.pop()
|
| 203 |
+
|
| 204 |
+
# start_notes.append(next_note)
|
| 205 |
+
# start_durations.append(next_duration)
|
| 206 |
+
# else:
|
| 207 |
+
# #Received empty row
|
| 208 |
+
# print("Exiting!!!!!!")
|
| 209 |
+
# print(rows[note_target].value_counts(normalize=True))
|
| 210 |
+
# print(rows[duration_target].value_counts(normalize=True))
|
| 211 |
+
|
| 212 |
+
print('Generated notes/durations'.format(num_notes))
|
| 213 |
+
print(gen_notes)
|
| 214 |
+
print(gen_durations)
|
| 215 |
+
|
| 216 |
+
mid = MidiFile()
|
| 217 |
+
track = MidiTrack()
|
| 218 |
+
mid.tracks.append(track)
|
| 219 |
+
for i in range(num_notes):
|
| 220 |
+
track.append(Message('note_on', channel=0, note=gen_notes[i], velocity=60, time=0))
|
| 221 |
+
track.append(Message('note_on', channel=0, note=gen_notes[i], velocity=0,time=int(gen_durations[i]*quarter_note_ticks)))
|
| 222 |
+
mid.save('new_song.mid')
|
| 223 |
+
#create new midi file which can be engraved
|
| 224 |
+
#https://mido.readthedocs.io/en/latest/midi_files.html , crreating a New file sectoin
|
| 225 |
+
# mid = MidiFile()
|
| 226 |
+
# track = MidiTrack
|
| 227 |
+
# mid.tracks.append(track)
|
| 228 |
+
|
| 229 |
+
# for i in range(num_notes):
|
| 230 |
+
# track.append(Message('note_on', channel=0, note=gen_notes[i], velocity=60, time=0))
|
| 231 |
+
# track.append(Message('note_on', channel=0, note=gen_notes[i], velocity=0,time=int(gen_durations[i]*quarter_note_ticks)))
|
| 232 |
+
# mid.save('output.mid')
|
| 233 |
+
# def inspect_midi():
|
| 234 |
+
# #Just inspecting midi file 1.
|
| 235 |
+
# for path in abs_paths('data'):
|
| 236 |
+
# # print(path)
|
| 237 |
+
# mid = MidiFile(path)
|
| 238 |
+
# for i, track in enumerate(mid.tracks):
|
| 239 |
+
# print('Track {}: {}'.format(i, track.name))
|
| 240 |
+
# for message in track:
|
| 241 |
+
# print(message)
|
| 242 |
+
# break
|
| 243 |
+
# inspect_midi()
|
| 244 |
+
# def isolate_note_on_msgs():
|
| 245 |
+
# #round each note duration to 250ms
|
| 246 |
+
# #Build adjaceny matrix
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
# LILYPOND COMMANDS : To be used for generating music scores
|
| 250 |
+
# Installation : sudo apt-get install -y lilypond
|
| 251 |
+
# !midi2ly "new_song.ly"
|
| 252 |
+
# !lilypond -fpng "new_song-midi.ly"
|
new_song.mid
ADDED
|
Binary file (632 Bytes). View file
|
|
|
output.txt
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
prepared.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tracks/0.npy
ADDED
|
Binary file (2.08 kB). View file
|
|
|
tracks/1.npy
ADDED
|
Binary file (12.4 kB). View file
|
|
|
tracks/10.npy
ADDED
|
Binary file (1.94 kB). View file
|
|
|
tracks/100.npy
ADDED
|
Binary file (10.8 kB). View file
|
|
|
tracks/101.npy
ADDED
|
Binary file (3.63 kB). View file
|
|
|
tracks/102.npy
ADDED
|
Binary file (160 Bytes). View file
|
|
|
tracks/103.npy
ADDED
|
Binary file (6.85 kB). View file
|
|
|
tracks/104.npy
ADDED
|
Binary file (11 kB). View file
|
|
|
tracks/105.npy
ADDED
|
Binary file (4.26 kB). View file
|
|
|
tracks/106.npy
ADDED
|
Binary file (5.52 kB). View file
|
|
|
tracks/107.npy
ADDED
|
Binary file (8.64 kB). View file
|
|
|
tracks/108.npy
ADDED
|
Binary file (9.98 kB). View file
|
|
|
tracks/109.npy
ADDED
|
Binary file (1.09 kB). View file
|
|
|
tracks/11.npy
ADDED
|
Binary file (144 Bytes). View file
|
|
|
tracks/110.npy
ADDED
|
Binary file (6.62 kB). View file
|
|
|
tracks/111.npy
ADDED
|
Binary file (6.85 kB). View file
|
|
|
tracks/112.npy
ADDED
|
Binary file (6.21 kB). View file
|
|
|
tracks/113.npy
ADDED
|
Binary file (3.04 kB). View file
|
|
|
tracks/114.npy
ADDED
|
Binary file (17.6 kB). View file
|
|
|
tracks/115.npy
ADDED
|
Binary file (4.08 kB). View file
|
|
|
tracks/116.npy
ADDED
|
Binary file (2.53 kB). View file
|
|
|
tracks/117.npy
ADDED
|
Binary file (23 kB). View file
|
|
|
tracks/118.npy
ADDED
|
Binary file (7.62 kB). View file
|
|
|
tracks/119.npy
ADDED
|
Binary file (4 kB). View file
|
|
|
tracks/12.npy
ADDED
|
Binary file (15.1 kB). View file
|
|
|
tracks/120.npy
ADDED
|
Binary file (1.79 kB). View file
|
|
|
tracks/121.npy
ADDED
|
Binary file (5.41 kB). View file
|
|
|
tracks/122.npy
ADDED
|
Binary file (2.62 kB). View file
|
|
|
tracks/123.npy
ADDED
|
Binary file (4.7 kB). View file
|
|
|
tracks/124.npy
ADDED
|
Binary file (3.2 kB). View file
|
|
|
tracks/125.npy
ADDED
|
Binary file (2.82 kB). View file
|
|
|
tracks/126.npy
ADDED
|
Binary file (3.31 kB). View file
|
|
|
tracks/127.npy
ADDED
|
Binary file (9.74 kB). View file
|
|
|
tracks/128.npy
ADDED
|
Binary file (20.9 kB). View file
|
|
|
tracks/129.npy
ADDED
|
Binary file (16.9 kB). View file
|
|
|
tracks/13.npy
ADDED
|
Binary file (1.17 kB). View file
|
|
|
tracks/130.npy
ADDED
|
Binary file (35.6 kB). View file
|
|
|
tracks/131.npy
ADDED
|
Binary file (6.18 kB). View file
|
|
|
tracks/132.npy
ADDED
|
Binary file (1.15 kB). View file
|
|
|
tracks/133.npy
ADDED
|
Binary file (5.89 kB). View file
|
|
|
tracks/134.npy
ADDED
|
Binary file (3.44 kB). View file
|
|
|
tracks/135.npy
ADDED
|
Binary file (4.74 kB). View file
|
|
|
tracks/136.npy
ADDED
|
Binary file (1.5 kB). View file
|
|
|
tracks/137.npy
ADDED
|
Binary file (7.07 kB). View file
|
|
|
tracks/138.npy
ADDED
|
Binary file (5.49 kB). View file
|
|
|
tracks/139.npy
ADDED
|
Binary file (1.18 kB). View file
|
|
|