1ALLOWED_TONE_NAMES = [
2 "A", "A#",
3 "B",
4 "C", "C#",
5 "D", "D#",
6 "E",
7 "F", "F#",
8 "G", "G#",
9]
10
11SEMITONE_REPRESENTATION = [
12 "unison",
13 "minor 2nd",
14 "major 2nd",
15 "minor 3rd",
16 "major 3rd",
17 "perfect 4th",
18 "diminished 5th",
19 "perfect 5th",
20 "minor 6th",
21 "major 6th",
22 "minor 7th",
23 "major 7th",
24]
25
26
27class Tone:
28 def __init__(self, name):
29 if name not in ALLOWED_TONE_NAMES:
30 raise ValueError("Name should be a valid tone.")
31
32 self.__name = name
33
34 def __str__(self):
35 return self.__name
36
37 def __eq__(self, other):
38 if not isinstance(other, Tone):
39 raise TypeError("Incompatible types. Tone object should be compared only to Tone object.")
40
41 return str(self) == str(other)
42
43 def __ne__(self, other):
44 return not self.__eq__(other)
45
46 def __hash__(self):
47 return hash(self.__name)
48
49 def __add__(self, other):
50 if isinstance(other, Tone):
51 return Chord(self, other)
52 elif isinstance(other, Interval):
53 tone_index = ALLOWED_TONE_NAMES.index(str(self))
54 new_tone_index = tone_index + other.semitone_number
55
56 new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)]
57
58 return Tone(new_tone_name)
59
60 raise TypeError("Incompatible types. Tone object should be added up only with Tone or Interval objects.")
61
62
63 def __sub__(self, other):
64 if isinstance(other, Tone):
65 tone_index = ALLOWED_TONE_NAMES.index(str(self))
66 other_tone_index = ALLOWED_TONE_NAMES.index(str(other))
67
68 diff = abs(tone_index - other_tone_index)
69
70 return Interval(diff)
71 elif isinstance(other, Interval):
72 tone_index = ALLOWED_TONE_NAMES.index(str(self))
73 new_tone_index = tone_index - other.semitone_number
74
75 new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)]
76
77 return Tone(new_tone_name)
78
79 raise TypeError("Incompatible types. Tone object should be subtracted only by Tone object.")
80
81
82class Interval:
83 def __init__(self, semitone_number):
84 self.__semitone_number = semitone_number
85
86 def __str__(self):
87 return SEMITONE_REPRESENTATION[self.__semitone_number % len(SEMITONE_REPRESENTATION)]
88
89 def __add__(self, other):
90 if isinstance(other, Interval):
91 new_semitone = self.semitone_number + other.semitone_number
92
93 return Interval(new_semitone)
94
95 raise TypeError("Invalid operation")
96
97 def __sub__(self, other):
98 raise TypeError("Invalid operation")
99
100 def __neg__(self):
101 return Interval(-self.semitone_number)
102
103 @property
104 def semitone_number(self):
105 return self.__semitone_number
106
107
108class Chord:
109 def __init__(self, main_tone, *args):
110 self.__main_tone = main_tone
111 others_tones = [str(tone) for tone in set([tone for tone in args if tone != main_tone])]
112
113 if len(others_tones) == 0:
114 raise TypeError("Cannot have a chord made of only 1 unique tone")
115
116 # fix order
117 fixed_order_tones = []
118
119 current_tone_index = ALLOWED_TONE_NAMES.index(str(main_tone))
120
121 while True:
122 current_tone_index += 1
123 current_tone = ALLOWED_TONE_NAMES[current_tone_index % len(ALLOWED_TONE_NAMES)]
124
125 if current_tone == str(main_tone):
126 break
127
128 if current_tone in others_tones:
129 fixed_order_tones.append(Tone(current_tone))
130
131 self.__other_tones = fixed_order_tones
132
133 def __str__(self):
134 joined_other_tones = "-".join(map(str, self.__other_tones))
135
136 return f"{str(self.__main_tone)}-{joined_other_tones}"
137
138 def __add__(self, other):
139 if isinstance(other, Tone):
140 return Chord(self.__main_tone, *self.__other_tones, other)
141 elif isinstance(other, Chord):
142 return Chord(self.__main_tone, *self.__other_tones, other.__main_tone, *other.__other_tones)
143
144 raise TypeError("Incompatible types. Chord object should be added up only with Tone object.")
145
146 def __sub__(self, other):
147 if not isinstance(other, Tone):
148 raise TypeError("Incompatible types. Chord object should be subtracted only by Tone object.")
149
150 if other not in self.__other_tones:
151 raise TypeError(f"Cannot remove tone {other} from chord {self}")
152
153 return Chord(self.__main_tone, *[tone for tone in self.__other_tones if tone != other])
154
155 def __check_majority(self, majority="minor"):
156 main_tone_index = ALLOWED_TONE_NAMES.index(str(self.__main_tone))
157
158 for tone in self.__other_tones:
159 tone_index = ALLOWED_TONE_NAMES.index(str(tone))
160 diff = abs(main_tone_index - tone_index)
161
162 if SEMITONE_REPRESENTATION[diff] == f"{majority} 3rd":
163 return True
164
165 return False
166
167 def is_minor(self):
168 return self.__check_majority("minor")
169
170 def is_major(self):
171 return self.__check_majority("major")
172
173 def is_power_chord(self):
174 return not self.is_minor() and not self.is_major()
175
176 def transposed(self, interval):
177 if not isinstance(interval, Interval):
178 raise TypeError("Function expects Interval object to work properly but other object type was given.")
179
180 new_main_tone = self.__main_tone + interval
181 new_other_tones = [tone + interval for tone in self.__other_tones]
182
183 return Chord(new_main_tone, *new_other_tones)
...................................F.
======================================================================
FAIL: test_tone_subtraction_inverse (test.TestOperations.test_tone_subtraction_inverse)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 178, in test_tone_subtraction_inverse
self.assertEqual(str(perfect_4th), "perfect 4th")
AssertionError: 'perfect 5th' != 'perfect 4th'
- perfect 5th
? ^
+ perfect 4th
? ^
----------------------------------------------------------------------
Ran 37 tests in 0.001s
FAILED (failures=1)
f | 1 | f | 1 | ||
2 | ALLOWED_TONE_NAMES = [ | 2 | ALLOWED_TONE_NAMES = [ | ||
3 | "A", "A#", | 3 | "A", "A#", | ||
4 | "B", | 4 | "B", | ||
5 | "C", "C#", | 5 | "C", "C#", | ||
6 | "D", "D#", | 6 | "D", "D#", | ||
7 | "E", | 7 | "E", | ||
8 | "F", "F#", | 8 | "F", "F#", | ||
9 | "G", "G#", | 9 | "G", "G#", | ||
10 | ] | 10 | ] | ||
11 | 11 | ||||
12 | SEMITONE_REPRESENTATION = [ | 12 | SEMITONE_REPRESENTATION = [ | ||
13 | "unison", | 13 | "unison", | ||
14 | "minor 2nd", | 14 | "minor 2nd", | ||
15 | "major 2nd", | 15 | "major 2nd", | ||
16 | "minor 3rd", | 16 | "minor 3rd", | ||
17 | "major 3rd", | 17 | "major 3rd", | ||
18 | "perfect 4th", | 18 | "perfect 4th", | ||
19 | "diminished 5th", | 19 | "diminished 5th", | ||
20 | "perfect 5th", | 20 | "perfect 5th", | ||
21 | "minor 6th", | 21 | "minor 6th", | ||
22 | "major 6th", | 22 | "major 6th", | ||
23 | "minor 7th", | 23 | "minor 7th", | ||
24 | "major 7th", | 24 | "major 7th", | ||
25 | ] | 25 | ] | ||
26 | 26 | ||||
27 | 27 | ||||
28 | class Tone: | 28 | class Tone: | ||
29 | def __init__(self, name): | 29 | def __init__(self, name): | ||
30 | if name not in ALLOWED_TONE_NAMES: | 30 | if name not in ALLOWED_TONE_NAMES: | ||
31 | raise ValueError("Name should be a valid tone.") | 31 | raise ValueError("Name should be a valid tone.") | ||
32 | 32 | ||||
33 | self.__name = name | 33 | self.__name = name | ||
34 | 34 | ||||
35 | def __str__(self): | 35 | def __str__(self): | ||
36 | return self.__name | 36 | return self.__name | ||
37 | 37 | ||||
38 | def __eq__(self, other): | 38 | def __eq__(self, other): | ||
39 | if not isinstance(other, Tone): | 39 | if not isinstance(other, Tone): | ||
40 | raise TypeError("Incompatible types. Tone object should be compared only to Tone object.") | 40 | raise TypeError("Incompatible types. Tone object should be compared only to Tone object.") | ||
41 | 41 | ||||
42 | return str(self) == str(other) | 42 | return str(self) == str(other) | ||
43 | 43 | ||||
44 | def __ne__(self, other): | 44 | def __ne__(self, other): | ||
45 | return not self.__eq__(other) | 45 | return not self.__eq__(other) | ||
46 | 46 | ||||
47 | def __hash__(self): | 47 | def __hash__(self): | ||
48 | return hash(self.__name) | 48 | return hash(self.__name) | ||
49 | 49 | ||||
50 | def __add__(self, other): | 50 | def __add__(self, other): | ||
51 | if isinstance(other, Tone): | 51 | if isinstance(other, Tone): | ||
52 | return Chord(self, other) | 52 | return Chord(self, other) | ||
53 | elif isinstance(other, Interval): | 53 | elif isinstance(other, Interval): | ||
54 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) | 54 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) | ||
55 | new_tone_index = tone_index + other.semitone_number | 55 | new_tone_index = tone_index + other.semitone_number | ||
56 | 56 | ||||
57 | new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)] | 57 | new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)] | ||
58 | 58 | ||||
59 | return Tone(new_tone_name) | 59 | return Tone(new_tone_name) | ||
60 | 60 | ||||
61 | raise TypeError("Incompatible types. Tone object should be added up only with Tone or Interval objects.") | 61 | raise TypeError("Incompatible types. Tone object should be added up only with Tone or Interval objects.") | ||
62 | 62 | ||||
63 | 63 | ||||
64 | def __sub__(self, other): | 64 | def __sub__(self, other): | ||
65 | if isinstance(other, Tone): | 65 | if isinstance(other, Tone): | ||
66 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) | 66 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) | ||
67 | other_tone_index = ALLOWED_TONE_NAMES.index(str(other)) | 67 | other_tone_index = ALLOWED_TONE_NAMES.index(str(other)) | ||
68 | 68 | ||||
69 | diff = abs(tone_index - other_tone_index) | 69 | diff = abs(tone_index - other_tone_index) | ||
70 | 70 | ||||
71 | return Interval(diff) | 71 | return Interval(diff) | ||
72 | elif isinstance(other, Interval): | 72 | elif isinstance(other, Interval): | ||
73 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) | 73 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) | ||
74 | new_tone_index = tone_index - other.semitone_number | 74 | new_tone_index = tone_index - other.semitone_number | ||
75 | 75 | ||||
76 | new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)] | 76 | new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)] | ||
77 | 77 | ||||
78 | return Tone(new_tone_name) | 78 | return Tone(new_tone_name) | ||
79 | 79 | ||||
80 | raise TypeError("Incompatible types. Tone object should be subtracted only by Tone object.") | 80 | raise TypeError("Incompatible types. Tone object should be subtracted only by Tone object.") | ||
81 | 81 | ||||
82 | 82 | ||||
83 | class Interval: | 83 | class Interval: | ||
84 | def __init__(self, semitone_number): | 84 | def __init__(self, semitone_number): | ||
85 | self.__semitone_number = semitone_number | 85 | self.__semitone_number = semitone_number | ||
86 | 86 | ||||
87 | def __str__(self): | 87 | def __str__(self): | ||
88 | return SEMITONE_REPRESENTATION[self.__semitone_number % len(SEMITONE_REPRESENTATION)] | 88 | return SEMITONE_REPRESENTATION[self.__semitone_number % len(SEMITONE_REPRESENTATION)] | ||
89 | 89 | ||||
90 | def __add__(self, other): | 90 | def __add__(self, other): | ||
91 | if isinstance(other, Interval): | 91 | if isinstance(other, Interval): | ||
92 | new_semitone = self.semitone_number + other.semitone_number | 92 | new_semitone = self.semitone_number + other.semitone_number | ||
93 | 93 | ||||
94 | return Interval(new_semitone) | 94 | return Interval(new_semitone) | ||
95 | 95 | ||||
96 | raise TypeError("Invalid operation") | 96 | raise TypeError("Invalid operation") | ||
97 | 97 | ||||
98 | def __sub__(self, other): | 98 | def __sub__(self, other): | ||
99 | raise TypeError("Invalid operation") | 99 | raise TypeError("Invalid operation") | ||
100 | 100 | ||||
101 | def __neg__(self): | 101 | def __neg__(self): | ||
102 | return Interval(-self.semitone_number) | 102 | return Interval(-self.semitone_number) | ||
103 | 103 | ||||
104 | @property | 104 | @property | ||
105 | def semitone_number(self): | 105 | def semitone_number(self): | ||
106 | return self.__semitone_number | 106 | return self.__semitone_number | ||
107 | 107 | ||||
108 | 108 | ||||
109 | class Chord: | 109 | class Chord: | ||
110 | def __init__(self, main_tone, *args): | 110 | def __init__(self, main_tone, *args): | ||
111 | self.__main_tone = main_tone | 111 | self.__main_tone = main_tone | ||
112 | others_tones = [str(tone) for tone in set([tone for tone in args if tone != main_tone])] | 112 | others_tones = [str(tone) for tone in set([tone for tone in args if tone != main_tone])] | ||
113 | 113 | ||||
114 | if len(others_tones) == 0: | 114 | if len(others_tones) == 0: | ||
115 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 115 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
116 | 116 | ||||
117 | # fix order | 117 | # fix order | ||
118 | fixed_order_tones = [] | 118 | fixed_order_tones = [] | ||
119 | 119 | ||||
120 | current_tone_index = ALLOWED_TONE_NAMES.index(str(main_tone)) | 120 | current_tone_index = ALLOWED_TONE_NAMES.index(str(main_tone)) | ||
121 | 121 | ||||
n | 122 | while (True): | n | 122 | while True: |
123 | current_tone_index += 1 | 123 | current_tone_index += 1 | ||
124 | current_tone = ALLOWED_TONE_NAMES[current_tone_index % len(ALLOWED_TONE_NAMES)] | 124 | current_tone = ALLOWED_TONE_NAMES[current_tone_index % len(ALLOWED_TONE_NAMES)] | ||
125 | 125 | ||||
126 | if current_tone == str(main_tone): | 126 | if current_tone == str(main_tone): | ||
127 | break | 127 | break | ||
128 | 128 | ||||
129 | if current_tone in others_tones: | 129 | if current_tone in others_tones: | ||
130 | fixed_order_tones.append(Tone(current_tone)) | 130 | fixed_order_tones.append(Tone(current_tone)) | ||
131 | 131 | ||||
132 | self.__other_tones = fixed_order_tones | 132 | self.__other_tones = fixed_order_tones | ||
133 | 133 | ||||
134 | def __str__(self): | 134 | def __str__(self): | ||
n | 135 | return str(self.__main_tone) + "-" + "-".join(map(str, self.__other_tones)) | n | 135 | joined_other_tones = "-".join(map(str, self.__other_tones)) |
136 | |||||
137 | return f"{str(self.__main_tone)}-{joined_other_tones}" | ||||
136 | 138 | ||||
137 | def __add__(self, other): | 139 | def __add__(self, other): | ||
138 | if isinstance(other, Tone): | 140 | if isinstance(other, Tone): | ||
139 | return Chord(self.__main_tone, *self.__other_tones, other) | 141 | return Chord(self.__main_tone, *self.__other_tones, other) | ||
140 | elif isinstance(other, Chord): | 142 | elif isinstance(other, Chord): | ||
141 | return Chord(self.__main_tone, *self.__other_tones, other.__main_tone, *other.__other_tones) | 143 | return Chord(self.__main_tone, *self.__other_tones, other.__main_tone, *other.__other_tones) | ||
142 | 144 | ||||
143 | raise TypeError("Incompatible types. Chord object should be added up only with Tone object.") | 145 | raise TypeError("Incompatible types. Chord object should be added up only with Tone object.") | ||
144 | 146 | ||||
145 | def __sub__(self, other): | 147 | def __sub__(self, other): | ||
146 | if not isinstance(other, Tone): | 148 | if not isinstance(other, Tone): | ||
147 | raise TypeError("Incompatible types. Chord object should be subtracted only by Tone object.") | 149 | raise TypeError("Incompatible types. Chord object should be subtracted only by Tone object.") | ||
148 | 150 | ||||
149 | if other not in self.__other_tones: | 151 | if other not in self.__other_tones: | ||
150 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | 152 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||
151 | 153 | ||||
152 | return Chord(self.__main_tone, *[tone for tone in self.__other_tones if tone != other]) | 154 | return Chord(self.__main_tone, *[tone for tone in self.__other_tones if tone != other]) | ||
153 | 155 | ||||
154 | def __check_majority(self, majority="minor"): | 156 | def __check_majority(self, majority="minor"): | ||
155 | main_tone_index = ALLOWED_TONE_NAMES.index(str(self.__main_tone)) | 157 | main_tone_index = ALLOWED_TONE_NAMES.index(str(self.__main_tone)) | ||
156 | 158 | ||||
157 | for tone in self.__other_tones: | 159 | for tone in self.__other_tones: | ||
158 | tone_index = ALLOWED_TONE_NAMES.index(str(tone)) | 160 | tone_index = ALLOWED_TONE_NAMES.index(str(tone)) | ||
159 | diff = abs(main_tone_index - tone_index) | 161 | diff = abs(main_tone_index - tone_index) | ||
160 | 162 | ||||
161 | if SEMITONE_REPRESENTATION[diff] == f"{majority} 3rd": | 163 | if SEMITONE_REPRESENTATION[diff] == f"{majority} 3rd": | ||
162 | return True | 164 | return True | ||
163 | 165 | ||||
164 | return False | 166 | return False | ||
165 | 167 | ||||
166 | def is_minor(self): | 168 | def is_minor(self): | ||
167 | return self.__check_majority("minor") | 169 | return self.__check_majority("minor") | ||
168 | 170 | ||||
169 | def is_major(self): | 171 | def is_major(self): | ||
170 | return self.__check_majority("major") | 172 | return self.__check_majority("major") | ||
171 | 173 | ||||
172 | def is_power_chord(self): | 174 | def is_power_chord(self): | ||
173 | return not self.is_minor() and not self.is_major() | 175 | return not self.is_minor() and not self.is_major() | ||
174 | 176 | ||||
175 | def transposed(self, interval): | 177 | def transposed(self, interval): | ||
176 | if not isinstance(interval, Interval): | 178 | if not isinstance(interval, Interval): | ||
177 | raise TypeError("Function expects Interval object to work properly but other object type was given.") | 179 | raise TypeError("Function expects Interval object to work properly but other object type was given.") | ||
178 | 180 | ||||
179 | new_main_tone = self.__main_tone + interval | 181 | new_main_tone = self.__main_tone + interval | ||
t | 180 | new_other_tones = [] | t | 182 | new_other_tones = [tone + interval for tone in self.__other_tones] |
181 | |||||
182 | for tone in self.__other_tones: | ||||
183 | new_other_tones.append(tone + interval) | ||||
184 | 183 | ||||
185 | return Chord(new_main_tone, *new_other_tones) | 184 | return Chord(new_main_tone, *new_other_tones) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | f | 1 | ||
n | 2 | allowed_tone_names = [ | n | 2 | ALLOWED_TONE_NAMES = [ |
3 | "A", "A#", | 3 | "A", "A#", | ||
4 | "B", | 4 | "B", | ||
5 | "C", "C#", | 5 | "C", "C#", | ||
6 | "D", "D#", | 6 | "D", "D#", | ||
7 | "E", | 7 | "E", | ||
8 | "F", "F#", | 8 | "F", "F#", | ||
9 | "G", "G#", | 9 | "G", "G#", | ||
10 | ] | 10 | ] | ||
11 | 11 | ||||
n | 12 | semitone_representation = [ | n | 12 | SEMITONE_REPRESENTATION = [ |
13 | "unison", | 13 | "unison", | ||
14 | "minor 2nd", | 14 | "minor 2nd", | ||
15 | "major 2nd", | 15 | "major 2nd", | ||
16 | "minor 3rd", | 16 | "minor 3rd", | ||
17 | "major 3rd", | 17 | "major 3rd", | ||
18 | "perfect 4th", | 18 | "perfect 4th", | ||
19 | "diminished 5th", | 19 | "diminished 5th", | ||
20 | "perfect 5th", | 20 | "perfect 5th", | ||
21 | "minor 6th", | 21 | "minor 6th", | ||
22 | "major 6th", | 22 | "major 6th", | ||
23 | "minor 7th", | 23 | "minor 7th", | ||
24 | "major 7th", | 24 | "major 7th", | ||
25 | ] | 25 | ] | ||
26 | 26 | ||||
27 | 27 | ||||
28 | class Tone: | 28 | class Tone: | ||
29 | def __init__(self, name): | 29 | def __init__(self, name): | ||
n | 30 | if name not in allowed_tone_names: | n | 30 | if name not in ALLOWED_TONE_NAMES: |
31 | raise ValueError("Name should be a valid tone.") | 31 | raise ValueError("Name should be a valid tone.") | ||
32 | 32 | ||||
33 | self.__name = name | 33 | self.__name = name | ||
34 | 34 | ||||
35 | def __str__(self): | 35 | def __str__(self): | ||
36 | return self.__name | 36 | return self.__name | ||
37 | 37 | ||||
38 | def __eq__(self, other): | 38 | def __eq__(self, other): | ||
39 | if not isinstance(other, Tone): | 39 | if not isinstance(other, Tone): | ||
40 | raise TypeError("Incompatible types. Tone object should be compared only to Tone object.") | 40 | raise TypeError("Incompatible types. Tone object should be compared only to Tone object.") | ||
41 | 41 | ||||
42 | return str(self) == str(other) | 42 | return str(self) == str(other) | ||
43 | 43 | ||||
44 | def __ne__(self, other): | 44 | def __ne__(self, other): | ||
45 | return not self.__eq__(other) | 45 | return not self.__eq__(other) | ||
46 | 46 | ||||
47 | def __hash__(self): | 47 | def __hash__(self): | ||
48 | return hash(self.__name) | 48 | return hash(self.__name) | ||
49 | 49 | ||||
50 | def __add__(self, other): | 50 | def __add__(self, other): | ||
51 | if isinstance(other, Tone): | 51 | if isinstance(other, Tone): | ||
52 | return Chord(self, other) | 52 | return Chord(self, other) | ||
53 | elif isinstance(other, Interval): | 53 | elif isinstance(other, Interval): | ||
n | 54 | tone_index = allowed_tone_names.index(str(self)) | n | 54 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) |
55 | new_tone_index = tone_index + other.get_semitone_number() | 55 | new_tone_index = tone_index + other.semitone_number | ||
56 | 56 | ||||
n | 57 | new_tone_name = allowed_tone_names[new_tone_index % len(allowed_tone_names)] | n | 57 | new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)] |
58 | 58 | ||||
59 | return Tone(new_tone_name) | 59 | return Tone(new_tone_name) | ||
60 | 60 | ||||
61 | raise TypeError("Incompatible types. Tone object should be added up only with Tone or Interval objects.") | 61 | raise TypeError("Incompatible types. Tone object should be added up only with Tone or Interval objects.") | ||
62 | 62 | ||||
63 | 63 | ||||
64 | def __sub__(self, other): | 64 | def __sub__(self, other): | ||
65 | if isinstance(other, Tone): | 65 | if isinstance(other, Tone): | ||
n | 66 | tone_index = allowed_tone_names.index(str(self)) | n | 66 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) |
67 | other_tone_index = allowed_tone_names.index(str(other)) | 67 | other_tone_index = ALLOWED_TONE_NAMES.index(str(other)) | ||
68 | 68 | ||||
69 | diff = abs(tone_index - other_tone_index) | 69 | diff = abs(tone_index - other_tone_index) | ||
70 | 70 | ||||
71 | return Interval(diff) | 71 | return Interval(diff) | ||
72 | elif isinstance(other, Interval): | 72 | elif isinstance(other, Interval): | ||
n | 73 | tone_index = allowed_tone_names.index(str(self)) | n | 73 | tone_index = ALLOWED_TONE_NAMES.index(str(self)) |
74 | new_tone_index = tone_index - other.get_semitone_number() | 74 | new_tone_index = tone_index - other.semitone_number | ||
75 | 75 | ||||
n | 76 | new_tone_name = allowed_tone_names[new_tone_index % len(allowed_tone_names)] | n | 76 | new_tone_name = ALLOWED_TONE_NAMES[new_tone_index % len(ALLOWED_TONE_NAMES)] |
77 | 77 | ||||
78 | return Tone(new_tone_name) | 78 | return Tone(new_tone_name) | ||
79 | 79 | ||||
80 | raise TypeError("Incompatible types. Tone object should be subtracted only by Tone object.") | 80 | raise TypeError("Incompatible types. Tone object should be subtracted only by Tone object.") | ||
81 | 81 | ||||
82 | 82 | ||||
83 | class Interval: | 83 | class Interval: | ||
84 | def __init__(self, semitone_number): | 84 | def __init__(self, semitone_number): | ||
85 | self.__semitone_number = semitone_number | 85 | self.__semitone_number = semitone_number | ||
86 | 86 | ||||
87 | def __str__(self): | 87 | def __str__(self): | ||
n | 88 | return semitone_representation[self.__semitone_number % len(semitone_representation)] | n | 88 | return SEMITONE_REPRESENTATION[self.__semitone_number % len(SEMITONE_REPRESENTATION)] |
89 | 89 | ||||
90 | def __add__(self, other): | 90 | def __add__(self, other): | ||
91 | if isinstance(other, Interval): | 91 | if isinstance(other, Interval): | ||
n | 92 | new_semitone = self.get_semitone_number() + other.get_semitone_number() | n | 92 | new_semitone = self.semitone_number + other.semitone_number |
93 | 93 | ||||
94 | return Interval(new_semitone) | 94 | return Interval(new_semitone) | ||
95 | 95 | ||||
96 | raise TypeError("Invalid operation") | 96 | raise TypeError("Invalid operation") | ||
97 | 97 | ||||
98 | def __sub__(self, other): | 98 | def __sub__(self, other): | ||
99 | raise TypeError("Invalid operation") | 99 | raise TypeError("Invalid operation") | ||
100 | 100 | ||||
101 | def __neg__(self): | 101 | def __neg__(self): | ||
n | 102 | return Interval(self.get_semitone_number() * -1) | n | 102 | return Interval(-self.semitone_number) |
103 | 103 | ||||
n | n | 104 | @property | ||
104 | def get_semitone_number(self): | 105 | def semitone_number(self): | ||
105 | return self.__semitone_number | 106 | return self.__semitone_number | ||
106 | 107 | ||||
107 | 108 | ||||
108 | class Chord: | 109 | class Chord: | ||
109 | def __init__(self, main_tone, *args): | 110 | def __init__(self, main_tone, *args): | ||
110 | self.__main_tone = main_tone | 111 | self.__main_tone = main_tone | ||
111 | others_tones = [str(tone) for tone in set([tone for tone in args if tone != main_tone])] | 112 | others_tones = [str(tone) for tone in set([tone for tone in args if tone != main_tone])] | ||
112 | 113 | ||||
113 | if len(others_tones) == 0: | 114 | if len(others_tones) == 0: | ||
114 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 115 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
115 | 116 | ||||
116 | # fix order | 117 | # fix order | ||
117 | fixed_order_tones = [] | 118 | fixed_order_tones = [] | ||
118 | 119 | ||||
n | 119 | current_tone_index = allowed_tone_names.index(str(main_tone)) | n | 120 | current_tone_index = ALLOWED_TONE_NAMES.index(str(main_tone)) |
120 | 121 | ||||
121 | while (True): | 122 | while (True): | ||
122 | current_tone_index += 1 | 123 | current_tone_index += 1 | ||
n | 123 | current_tone = allowed_tone_names[current_tone_index % len(allowed_tone_names)] | n | 124 | current_tone = ALLOWED_TONE_NAMES[current_tone_index % len(ALLOWED_TONE_NAMES)] |
124 | 125 | ||||
125 | if current_tone == str(main_tone): | 126 | if current_tone == str(main_tone): | ||
126 | break | 127 | break | ||
127 | 128 | ||||
128 | if current_tone in others_tones: | 129 | if current_tone in others_tones: | ||
129 | fixed_order_tones.append(Tone(current_tone)) | 130 | fixed_order_tones.append(Tone(current_tone)) | ||
130 | 131 | ||||
131 | self.__other_tones = fixed_order_tones | 132 | self.__other_tones = fixed_order_tones | ||
132 | 133 | ||||
133 | def __str__(self): | 134 | def __str__(self): | ||
134 | return str(self.__main_tone) + "-" + "-".join(map(str, self.__other_tones)) | 135 | return str(self.__main_tone) + "-" + "-".join(map(str, self.__other_tones)) | ||
135 | 136 | ||||
136 | def __add__(self, other): | 137 | def __add__(self, other): | ||
137 | if isinstance(other, Tone): | 138 | if isinstance(other, Tone): | ||
138 | return Chord(self.__main_tone, *self.__other_tones, other) | 139 | return Chord(self.__main_tone, *self.__other_tones, other) | ||
139 | elif isinstance(other, Chord): | 140 | elif isinstance(other, Chord): | ||
140 | return Chord(self.__main_tone, *self.__other_tones, other.__main_tone, *other.__other_tones) | 141 | return Chord(self.__main_tone, *self.__other_tones, other.__main_tone, *other.__other_tones) | ||
141 | 142 | ||||
142 | raise TypeError("Incompatible types. Chord object should be added up only with Tone object.") | 143 | raise TypeError("Incompatible types. Chord object should be added up only with Tone object.") | ||
143 | 144 | ||||
144 | def __sub__(self, other): | 145 | def __sub__(self, other): | ||
145 | if not isinstance(other, Tone): | 146 | if not isinstance(other, Tone): | ||
146 | raise TypeError("Incompatible types. Chord object should be subtracted only by Tone object.") | 147 | raise TypeError("Incompatible types. Chord object should be subtracted only by Tone object.") | ||
147 | 148 | ||||
148 | if other not in self.__other_tones: | 149 | if other not in self.__other_tones: | ||
149 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | 150 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||
150 | 151 | ||||
151 | return Chord(self.__main_tone, *[tone for tone in self.__other_tones if tone != other]) | 152 | return Chord(self.__main_tone, *[tone for tone in self.__other_tones if tone != other]) | ||
152 | 153 | ||||
153 | def __check_majority(self, majority="minor"): | 154 | def __check_majority(self, majority="minor"): | ||
n | 154 | main_tone_index = allowed_tone_names.index(str(self.__main_tone)) | n | 155 | main_tone_index = ALLOWED_TONE_NAMES.index(str(self.__main_tone)) |
155 | 156 | ||||
156 | for tone in self.__other_tones: | 157 | for tone in self.__other_tones: | ||
n | 157 | tone_index = allowed_tone_names.index(str(tone)) | n | 158 | tone_index = ALLOWED_TONE_NAMES.index(str(tone)) |
158 | diff = abs(main_tone_index - tone_index) | 159 | diff = abs(main_tone_index - tone_index) | ||
159 | 160 | ||||
t | 160 | if semitone_representation[diff] == f"{majority} 3rd": | t | 161 | if SEMITONE_REPRESENTATION[diff] == f"{majority} 3rd": |
161 | return True | 162 | return True | ||
162 | 163 | ||||
163 | return False | 164 | return False | ||
164 | 165 | ||||
165 | def is_minor(self): | 166 | def is_minor(self): | ||
166 | return self.__check_majority("minor") | 167 | return self.__check_majority("minor") | ||
167 | 168 | ||||
168 | def is_major(self): | 169 | def is_major(self): | ||
169 | return self.__check_majority("major") | 170 | return self.__check_majority("major") | ||
170 | 171 | ||||
171 | def is_power_chord(self): | 172 | def is_power_chord(self): | ||
172 | return not self.is_minor() and not self.is_major() | 173 | return not self.is_minor() and not self.is_major() | ||
173 | 174 | ||||
174 | def transposed(self, interval): | 175 | def transposed(self, interval): | ||
175 | if not isinstance(interval, Interval): | 176 | if not isinstance(interval, Interval): | ||
176 | raise TypeError("Function expects Interval object to work properly but other object type was given.") | 177 | raise TypeError("Function expects Interval object to work properly but other object type was given.") | ||
177 | 178 | ||||
178 | new_main_tone = self.__main_tone + interval | 179 | new_main_tone = self.__main_tone + interval | ||
179 | new_other_tones = [] | 180 | new_other_tones = [] | ||
180 | 181 | ||||
181 | for tone in self.__other_tones: | 182 | for tone in self.__other_tones: | ||
182 | new_other_tones.append(tone + interval) | 183 | new_other_tones.append(tone + interval) | ||
183 | 184 | ||||
184 | return Chord(new_main_tone, *new_other_tones) | 185 | return Chord(new_main_tone, *new_other_tones) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|