-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmidihelper.py
136 lines (108 loc) · 4.79 KB
/
midihelper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import glob
import pickle
import os
import numpy as np
from keras.utils import np_utils
from music21 import converter, instrument, note, chord, stream
class MidiHelper:
def __init__(self, path, name):
self.path = path
self.name = name
self.notes = []
self.notes = self.get_notes()
def create_notes(self):
self.notes = []
for file in glob.glob(self.path + "/*.mid"):
midi = converter.parse(file)
parts = instrument.partitionByInstrument(midi)
notes_to_parse = parts.parts[0].recurse() if parts else midi.flat.notes
last_offset_notes = 0
last_offset_chords = 0
for element in notes_to_parse:
if isinstance(element, note.Note):
offset = element.offset - last_offset_notes
last_offset_notes = element.offset
self.notes.append(str(element.pitch) + "," + str(offset))
elif isinstance(element, chord.Chord):
offset = element.offset - last_offset_chords
last_offset_chords = element.offset
self.notes.append('_'.join(str(n) for n in element.normalOrder) + "," + str(offset))
# save notes so that loading is faster
with open(self.name, 'wb') as f:
pickle.dump(self.notes, f)
return self.notes
def get_notes(self):
if self.notes and len(self.notes) != 0:
return self.notes
if os.path.isfile(self.name):
print('Loaded notes from file')
with open(self.name, 'rb') as f:
self.notes = pickle.load(f)
return self.notes
else:
print('Create new notes out of midi files')
return self.create_notes()
def get_note_to_int_mapping(self):
# get all pitch names
pitch_names = sorted(set(self.notes))
# create a dictionary to map pitches to integers
note_to_int = dict((note, number) for number, note in enumerate(pitch_names))
return note_to_int
def get_size_of_output_classes(self):
return len(set(self.notes))
def generate_sequences(self, sequence_length):
note_to_int = self.get_note_to_int_mapping()
network_input = []
network_output = []
# create input sequences and the corresponding outputs
for i in range(0, len(self.notes) - sequence_length, 1):
sequence_in = self.notes[i:i + sequence_length]
sequence_out = self.notes[i + sequence_length]
network_input.append([note_to_int[char] for char in sequence_in])
network_output.append(note_to_int[sequence_out])
n_patterns = len(network_input)
# reshape the input into a format compatible with LSTM layers
network_input = np.reshape(network_input, (n_patterns, sequence_length, 1))
# normalize input
network_input = network_input / float(self.get_size_of_output_classes())
network_output = np_utils.to_categorical(network_output)
return network_input, network_output
def generate_midi_from_prediction(prediction_output, file_name):
offset = 0
output_notes = []
# create note and chord objects based on the values generated by the model
for pattern in prediction_output:
pattern_and_offset = pattern.split(',')
print(pattern_and_offset)
if len(pattern_and_offset) != 2:
pattern_and_offset = [pattern, 0.5]
pattern = pattern_and_offset[0]
# pattern is a chord
if ('_' in pattern) or pattern.isdigit():
notes_in_chord = pattern.split('_')
notes = []
for current_note in notes_in_chord:
new_note = note.Note(int(current_note))
new_note.storedInstrument = instrument.Piano()
notes.append(new_note)
new_chord = chord.Chord(notes)
new_chord.offset = offset
output_notes.append(new_chord)
# pattern is a note
else:
new_note = note.Note(pattern)
new_note.offset = offset
new_note.storedInstrument = instrument.Piano()
output_notes.append(new_note)
# increase offset each iteration so that notes do not stack
if "/" in pattern_and_offset[1]:
numbers = pattern_and_offset[1].split('/')
if len(numbers) == 2:
pattern_and_offset[1] = float(numbers[0]) / float(numbers[1])
else:
pattern_and_offset[1] = 0.5 # if not two numbers are given set a standard offset
offset += float(pattern_and_offset[1])
if not file_name.endswith('.mid'):
file_name = file_name + '.mid'
midi_stream = stream.Stream(output_notes)
midi_stream.write('midi', fp=file_name)