-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy paththeory.py
182 lines (159 loc) · 6.05 KB
/
theory.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
#!/usr/bin/python3
import pypond
import muse
from circular import Circular
import re
import math
def _rotate(l, n):
"""Return a copy of list/yuple 'l' as a list rotated by increment 'n'
Negative 'n' shifts everything to the right (0 -> 1, 1 -> 2, etc..., last -> 0)
Positive 'n' shifts values left."""
length = len(l)
return [l[(n + m) % length] for m in range(length)]
class TheoryClass(object):
CircleOfFifths = Circular(('c', 'g', 'd', 'a', 'e', 'b', 'f#', 'db', 'ab', 'eb', 'bb', 'f'))
OrderOfFlats = Circular(('b', 'e', 'a', 'd', 'g', 'c', 'f'))
SharpsMajor = (1, 1, 1, 1, 1, 1, 0)
SharpsMinor = (1, 1, 1, 0, 0, 0, 0)
fourthInterval = 5 # 5 half-steps make a perfect fourth
fifthInterval = 7 # 7 half-steps make a perfect fifth
_clefTreble = 'treble'
_reClefTreble = re.compile("[Tt]((reble)|(REBLE))?$")
_clefBass = 'bass'
_reClefBass = re.compile("[Bb]((ass)|(ASS))?$")
_clefAlto = 'alto'
_reClefAlto = re.compile("[Aa]((lto)|(LTO))?$")
_clefTenor = 'tenor'
_reClefTenor = re.compile("[Tt]((enor)|(ENOR))$")
_encodingClef = {
_clefTreble : 0,
_clefBass : 1,
_clefAlto : 2,
_clefTenor : 3
}
@classmethod
def getKeyByInterval(cls, key, interval):
"""Returns a Key object of the same key quality as 'key', with tonic rooted
up a perfect-fifth from that of 'key'"""
interval = pypond._int(interval)
if interval == None:
raise pypond.Error_Interval("Cannot interpret interval {}".format(interval))
note = pypond.Note(key.getTonicName())
nextNote = note.getNoteByInterval(interval)
quality = key.getQuality()
nextKey = muse.Key(nextNote.getNoteName())
nextKey.setQuality(quality)
return nextKey
@classmethod
def _fifthsToInterval(cls, nFifths):
return (nFifths*cls.fifthInterval) % 12
@classmethod
def _fourthsToInterval(cls, nFourths):
return (nFourths*cls.fourthInterval) % 12
@classmethod
def _intervalToFourths(cls, interval):
return (interval*cls.fourthInterval) % 12
@classmethod
def _intervalToFifths(cls, interval):
return (interval*cls.fifthsInterval) % 12
@classmethod
def getKeyByFourths(cls, key, nFourths):
"""Get the key (of the same key quality as 'key') which is
nFourths degrees away on the circle of Fourths.
E.g. : key = C; nFourths = 2; return Bb
E.g. : key = A; nFourths = 4; return F
E.g. : key = Eb; nFourths = -1; return Bb
"""
interval = cls._fourthsToInterval(nFourths)
return cls.getKeyByInterval(key, interval)
@classmethod
def getKeyByFifths(cls, key, nFifths):
return cls.getKeyByFourths(key, -nFifths)
@classmethod
def getKeyNextFifth(cls, key):
"""Returns a Key object of the same key quality as 'key', with tonic rooted
up a perfect-fifth from that of 'key'"""
return cls.getKeyByInterval(cls.fifthInterval)
@classmethod
def getKeyNextFourth(cls, key):
"""Returns a Key object of the same key quality as 'key', with tonic rooted
up a perfect-fourth from that of 'key'"""
return cls.getKeyByInterval(cls.fourthInterval)
@classmethod
def isSharpKey(cls, key):
"""Return True if 'key' is major and tonic has # or is : B, E, A, D, G, C
If 'key' is minor or dim, returns True if tonic has a # or if tonic is B, E, A
Does not handle double-flats"""
flat = pypond.Note.encodingAccidentals.get(pypond.Note.flatChar)
sharp = pypond.Note.encodingAccidentals.get(pypond.Note.sharpChar)
acc = key.getTonic().getAccidental()
if acc == flat:
return False
elif acc == sharp:
return True
# If there is no accidental in the tonic name
quality = key.getQuality()
# These are my best guesses
if quality == muse._KeyQuality.major:
offset = 0
elif quality == muse._KeyQuality.minor:
offset = 3
elif quality == muse._KeyQuality.dimwh:
offset = 3
elif quality == muse._KeyQuality.dimhw:
offset = 3
elif quality == muse._KeyQuality.chromatic:
offset = 0
elif quality == muse._KeyQuality.wholetone:
offset = 0
else:
offset = 0
noteName = key.getTonic().getNoteName().lower()
l = len(cls.OrderOfFlats)
#print("noteName = {}".format(noteName))
index = cls.OrderOfFlats.index(noteName) + offset
if (index < l) and (index >= 0):
return cls.SharpsMajor[index]
elif index < 0:
return True
else:
return False
@classmethod
def _clefParser(cls, clefstring):
"""Parse clef string input and return the corresponding encoding"""
if cls._reClefTreble.match(clefstring):
clefstring = cls._clefTreble
elif cls._reClefBass.match(clefstring):
clefstring = cls._clefBass
elif cls._reClefAlto.match(clefstring):
clefstring = cls._clefAlto
elif cls._reClefTenor.match(clefstring):
clefstring = cls._clefTenor
else:
raise Error_ClefParser("Cannot parse clef string: {}".format(clefstring))
return None
return cls._encodingClef[clefstring]
@classmethod
def _getClefString(cls, clef):
"""Return the clef string corresponding to a particular clef integer"""
for item in cls._encodingClef.items():
if item[1] == clef:
return item[0]
return None
class Error_ClefParser(Exception):
pass
def _testTheoryClass(args):
pass
def _testRotate(args):
USAGE = "python3 {0} <listObject> <rotateAmount>".format(args[0])
if len(args) > 2:
f = eval(argv[1])
n = int(argv[2])
else:
print(USAGE)
return
print(_rotate(f, n))
if __name__ == "__main__":
import sys
argv = sys.argv
_testTheoryClass(argv)