-
Notifications
You must be signed in to change notification settings - Fork 0
/
composition.py
148 lines (117 loc) · 4.15 KB
/
composition.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
#!/usr/bin/env python
"""Composition."""
import random
import os
import math
import music21
import mido
# Default Global Variables
ranges = open("ranges.json")
ranges = eval(ranges.read())
class Voice(music21.stream.Voice):
"""Create a Music21 Voice of random notes from a selected scale."""
def __init__(self,
name="",
scale=music21.scale.MajorScale("C"),
length=12,
pitch="tenor"):
print("Voice created.")
super().__init__()
self.name = name
self.scale = scale
self.length = length
self.intervals = []
self.save_dir = os.path.join("generated", self.name)
self.pitch = pitch
self.range = ranges[self.pitch]
def __str__(self):
"""String."""
return "".join(str(note.fullName) + "\n" for note in self)
def compose(self):
"""Compose."""
self.append(music21.note.Note(self.scale.getTonic()))
notes = [
music21.note.Note(str(note))
for note in self.scale.getPitches(
self.range[0].pitch.name, self.range[1].pitch.name
)
]
while len(self) != self.length:
self.append(music21.note.Note(random.choice(notes).pitch.name))
self.save_midi()
def save_midi(self):
"""Save to midi."""
# self.show("midi")
midi_file = music21.midi.translate.streamToMidiFile(self)
if not os.path.exists(self.save_dir):
os.makedirs(self.save_dir)
midi_file.open(os.path.join(self.save_dir,
self.name + ".mid"), 'wb')
midi_file.write()
midi_file.close()
class Fugue:
"""Fugue."""
def __init__(self, name, voices, subject=None):
print("Fugue created.")
super().__init__()
self.name = name
self.voice_list = [subject] + [[None], * len(voices)]
def __str__(self):
"""String."""
return self.voice_list
def generate_answer(self):
"""Generate answer."""
print("Answer created.")
def generate_countersubject(self):
"""Generate countersubject."""
print("This program can't create a countersubject yet :(")
class Canon:
"""Canon."""
def __init__(self, lead_chords, voice_count):
self.lead_chords = lead_chords
self.voice_count = voice_count
print("Canon created.")
super().__init__()
def generate_random_chords(self):
"""Generate random chords."""
print("Generating random chords")
class CrabCanon:
"""Crab canon from Bach's musical offering."""
def __init__(self):
print("Crab Canon created.")
def midi_to_dectalk(midi, mode="phone"):
"""Convert midi files with monophonic tracks to be sung in dectalk."""
# dectalk notes range from C2 to C5 and are numbered 36 less than midi
# this function is currently very buggy
print("Converting to dectalk.")
midi_file = mido.MidiFile(midi)
words = ["uw", "ax", "ow", "ah"]
song = []
for track in midi_file.tracks:
if mode == "phone":
voice = "[:phone on]\n"
elif mode == "sing":
voice = "[:phoneme arpabet speak on]\n"
for note in track:
if note.type in ("note_on", "note_off") and note.time != 0:
freq = math.floor(music21.pitch.Pitch(note.note).frequency)
duration = math.floor(note.time)
if mode == "phone":
voice += "[:tone " + str(freq) \
+ "," + str(duration) + "]\n"
elif mode == "sing":
voice += "[" + words[0] + "<" + str(freq) \
+ "," + str(note.note - 36) + ">]\n"
song.append(voice)
for i, j in enumerate(song):
with open(os.path.join(os.path.split(midi)[0], str(i) + ".txt"),
"w") as track:
track.write(j)
print("Dectalk conversion finished.")
return song
def main():
"""Main."""
midi = os.path.join("examples", "The_Art_of_Fugue_Contrapunctus_1.mid")
midi_to_dectalk(midi)
if __name__ == "__main__":
main()