-
Notifications
You must be signed in to change notification settings - Fork 2
/
helper.py
196 lines (133 loc) · 4.59 KB
/
helper.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
from music21 import midi, note, stream, chord, duration
from music21.pitch import PitchException
from markov_model import add_chords
import subprocess
import os
rhythms = {
'q': 1.0,
'.q': 1.5,
'w': 4.0,
'h': 2.0,
'.h': 3.0,
'8': 0.5,
'16': 0.25
}
rhythms_back = {0.25: '16',
1.0: 'q',
2.0: 'h',
3.0: 'hd',
4.0: 'w',
0.5: '8',
1.5: 'qd'}
def parse_melody(melody):
"""Takes in a string of note names and returns a music21 object.
"""
piece = stream.Part()
for item in melody:
dur = duration.Duration(item[1])
if item[0] != 'r':
n = note.Note(item[0])
else:
n = note.Rest()
n.duration = dur
piece.append(n)
return piece
def combine_notes_and_chords(notes, chords):
"""Given two music21 part objects, combine them into a single piece."""
piece = stream.Stream()
piece.append(notes)
piece.append(chords)
return piece
def make_chords(chords, lengths):
"""Given a list of chord notes and the chord length, combine them to make
music21 chord objects.
"""
accomp = stream.Part()
for i in range(len(chords)):
try:
c = chord.Chord(chords[i]).closedPosition(forceOctave=3)
d = duration.Duration(lengths[i])
c.duration = d
except PitchException:
c = note.Rest()
d = duration.Duration(lengths[i])
c.duration = d
accomp.append(c)
return accomp
def get_measures(melody, accomp):
"""Separates the music output into lists of notes by measure."""
melody_measures = melody.makeMeasures()
accomp_measures = accomp.makeMeasures()
melody_measure_notes = []
accomp_measure_notes = []
for measure in melody_measures:
this_measure = []
for n in measure.notesAndRests:
if type(n) == note.Note:
this_measure.append((n.nameWithOctave, rhythms_back[n.duration.quarterLength]))
elif type(n) == note.Rest:
this_measure.append(('r', n.duration))
melody_measure_notes.append(this_measure)
for measure in accomp_measures:
this_measure = []
for n in measure.notesAndRests:
if type(n) == chord.Chord:
this_measure.append(([p.nameWithOctave for p in n.pitches], rhythms_back[n.duration.quarterLength]))
accomp_measure_notes.append(this_measure)
return melody_measure_notes, accomp_measure_notes
def new_song(melody, user_id=''):
""" Creates a new song from a user input melody.
"""
path = 'static/user_files/user' + str(user_id) + '/temp/'
if not os.path.exists(path):
os.makedirs(path)
mid = path + 'temp_song.mid'
wav = path + 'temp_song.wav'
new_melody = []
for item in melody.split():
n, d = item.split('\\')
new_melody.append((n, rhythms[d]))
parsed_input = parse_melody(new_melody)
chords, lengths = add_chords(new_melody)
accomp = make_chords(chords, lengths)
note_measures, chord_measures = get_measures(parsed_input, accomp)
song = combine_notes_and_chords(parsed_input, accomp)
# write to midi file
mf = midi.translate.streamToMidiFile(song)
mf.open(mid, 'wb')
mf.write()
mf.close()
# convert to .wav format
subprocess.call(['timidity ' + mid + ' -Ow -o ' + wav], shell=True)
return note_measures, chord_measures
def save_file(path, filename, user_id=None):
"""Given a path name, moves the temporary song.wav file to the given path.
"""
if not os.path.exists(path):
os.makedirs(path)
os.rename('static/user_files/user' + str(user_id) + '/temp/temp_song.wav', path + filename)
def save_image(path, filename, data):
"""Saves the image to disk."""
if not os.path.exists(path):
os.makedirs(path)
image = data[22:]
missing_padding = len(image) % 4
if missing_padding != 0:
image += b'=' * (4 - missing_padding)
fh = open(path+filename, "wb")
fh.write(image.decode('base64'))
fh.close()
def validate_input(melody):
possible_notes = ['a', 'b', 'c', 'd', 'e', 'f', 'g',
'A', 'B', 'C', 'D', 'E', 'F', 'G']
for m in melody.split():
n, d = m.split('\\')
if d not in rhythms:
return False
if n[0] not in possible_notes:
return False
if n[-1] not in [str(s) for s in range(10)]:
return False
if len(n) == 3 and n[1] not in ['-', '#']:
return False
return True