va35 commited on
Commit
5fc3f34
1 Parent(s): 8ce6b5b

Naive attempt using Markov chains

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
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
- #TODO: look at this later
23
- pass
24
- if not os.path.exists('tracks'):
25
- # os.mkdir('tracks')
26
- i = 0
27
- #Parse midi files into tracks folder
28
- for path in abs_paths('data'):
29
- print(path)
30
- mid = MidiFile(path)
31
- for i, track in enumerate(mid.tracks):
32
- print('Track {}: {}'.format(i, track.name))
33
- for message in track:
34
- print(message)
35
- break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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