-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathharmony.py
66 lines (62 loc) · 1.81 KB
/
harmony.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
try:
from statistics import mean
except ImportError:
def mean(x):
return sum(x)/len(x)
from music import inversion, Chord
import re
parser = re.compile(r"^([iv]+|[IV]+)([+|o]?)((?:[0-9]+)(?:\/[0-9]+)*)?$")
intervals = {
"I": 1, "II": 2, "III": 3, "IV": 4, "V": 5, "VI": 6, "VII": 7
}
fig_base_parser = {
None: (False, 0),
"6": (False, 1),
"6/4": (False, 2),
"7": (True, 0),
"6/5": (True, 1),
"4/3": (True, 2),
"2": (True, 3)
}
def diatonics(base):
return [base, base+2, base+4, base+5, base+7, base+9, base+11, base+12]
def parse_num_not(base, num_not):
interval_num, aug_or_dim, fig_base = parser.match(num_not).groups()
interval = intervals[interval_num.upper()]
base = diatonics(base)[interval-1]
seventh, chord_inversion = fig_base_parser[fig_base]
major = interval_num.isupper()
if aug_or_dim == "+":
chord = Chord([base, base+4, base+7])
elif aug_or_dim == "o":
chord = Chord([base, base+3, base+6])
else:
if major:
chord = Chord([base, base+4, base+7])
else:
chord = Chord([base, base+3, base+7])
if seventh:
chord.append(base+10)
return inversion(chord, chord_inversion)
def voice_leading(chords):
vl_chords = []
for i, c in enumerate(chords):
if i == 0:
vl_chords.append(c)
else:
vl_chords.append(voice_lead(vl_chords[i-1], c))
return vl_chords
def voice_lead(chord1, chord2):
avg_chord1 = mean(chord1)
avg_chord2 = mean(chord2)
while avg_chord2 > avg_chord1:
chord2 <<= 12
avg_chord2 = mean(chord2)
dist1 = avg_chord1 - avg_chord2
chord2 >>= 12
avg_chord2 = mean(chord2)
dist2 = abs(avg_chord1 - avg_chord2)
if dist1 < dist2:
return chord2 << 12
else:
return chord2