Spaces:
Runtime error
Runtime error
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
|
|