-
Notifications
You must be signed in to change notification settings - Fork 111
/
generate.py
153 lines (127 loc) · 4.91 KB
/
generate.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
import numpy as np
import tensorflow as tf
from collections import deque
import midi
import argparse
from constants import *
from util import *
from dataset import *
from tqdm import tqdm
from midi_util import midi_encode
class MusicGeneration:
"""
Represents a music generation
"""
def __init__(self, style, default_temp=1):
self.notes_memory = deque([np.zeros((NUM_NOTES, NOTE_UNITS)) for _ in range(SEQ_LEN)], maxlen=SEQ_LEN)
self.beat_memory = deque([np.zeros(NOTES_PER_BAR) for _ in range(SEQ_LEN)], maxlen=SEQ_LEN)
self.style_memory = deque([style for _ in range(SEQ_LEN)], maxlen=SEQ_LEN)
# The next note being built
self.next_note = np.zeros((NUM_NOTES, NOTE_UNITS))
self.silent_time = NOTES_PER_BAR
# The outputs
self.results = []
# The temperature
self.default_temp = default_temp
self.temperature = default_temp
def build_time_inputs(self):
return (
np.array(self.notes_memory),
np.array(self.beat_memory),
np.array(self.style_memory)
)
def build_note_inputs(self, note_features):
# Timesteps = 1 (No temporal dimension)
return (
np.array(note_features),
np.array([self.next_note]),
np.array(list(self.style_memory)[-1:])
)
def choose(self, prob, n):
vol = prob[n, -1]
prob = apply_temperature(prob[n, :-1], self.temperature)
# Flip notes randomly
if np.random.random() <= prob[0]:
self.next_note[n, 0] = 1
# Apply volume
self.next_note[n, 2] = vol
# Flip articulation
if np.random.random() <= prob[1]:
self.next_note[n, 1] = 1
def end_time(self, t):
"""
Finish generation for this time step.
"""
# Increase temperature while silent.
if np.count_nonzero(self.next_note) == 0:
self.silent_time += 1
if self.silent_time >= NOTES_PER_BAR:
self.temperature += 0.1
else:
self.silent_time = 0
self.temperature = self.default_temp
self.notes_memory.append(self.next_note)
# Consistent with dataset representation
self.beat_memory.append(compute_beat(t, NOTES_PER_BAR))
self.results.append(self.next_note)
# Reset next note
self.next_note = np.zeros((NUM_NOTES, NOTE_UNITS))
return self.results[-1]
def apply_temperature(prob, temperature):
"""
Applies temperature to a sigmoid vector.
"""
# Apply temperature
if temperature != 1:
# Inverse sigmoid
x = -np.log(1 / prob - 1)
# Apply temperature to sigmoid function
prob = 1 / (1 + np.exp(-x / temperature))
return prob
def process_inputs(ins):
ins = list(zip(*ins))
ins = [np.array(i) for i in ins]
return ins
def generate(models, num_bars, styles):
print('Generating with styles:', styles)
_, time_model, note_model = models
generations = [MusicGeneration(style) for style in styles]
for t in tqdm(range(NOTES_PER_BAR * num_bars)):
# Produce note-invariant features
ins = process_inputs([g.build_time_inputs() for g in generations])
# Pick only the last time step
note_features = time_model.predict(ins)
note_features = np.array(note_features)[:, -1:, :]
# Generate each note conditioned on previous
for n in range(NUM_NOTES):
ins = process_inputs([g.build_note_inputs(note_features[i, :, :, :]) for i, g in enumerate(generations)])
predictions = np.array(note_model.predict(ins))
for i, g in enumerate(generations):
# Remove the temporal dimension
g.choose(predictions[i][-1], n)
# Move one time step
yield [g.end_time(t) for g in generations]
def write_file(name, results):
"""
Takes a list of all notes generated per track and writes it to file
"""
results = zip(*list(results))
for i, result in enumerate(results):
fpath = os.path.join(SAMPLES_DIR, name + '_' + str(i) + '.mid')
print('Writing file', fpath)
os.makedirs(os.path.dirname(fpath), exist_ok=True)
mf = midi_encode(unclamp_midi(result))
midi.write_midifile(fpath, mf)
def main():
parser = argparse.ArgumentParser(description='Generates music.')
parser.add_argument('--bars', default=32, type=int, help='Number of bars to generate')
parser.add_argument('--styles', default=None, type=int, nargs='+', help='Styles to mix together')
args = parser.parse_args()
models = build_or_load()
styles = [compute_genre(i) for i in range(len(genre))]
if args.styles:
# Custom style
styles = [np.mean([one_hot(i, NUM_STYLES) for i in args.styles], axis=0)]
write_file('output', generate(models, args.bars, styles))
if __name__ == '__main__':
main()