1import copy
2
3CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
4TONES_COUNT = 12
5
6class Tone:
7 def __init__(self, name):
8 self._name = name
9
10 def __str__(self):
11 return self._name
12
13 def __eq__(self, other):
14 return str(self) == str(other)
15
16 def __hash__(self):
17 return hash(self._name)
18
19 def __add__(self, other):
20 if isinstance(other, Tone):
21 return Chord(self, other)
22 elif isinstance(other, Interval):
23 self_index = CHROMATIC_SCALE.index(str(self))
24 other_index = other.interval_index
25 return Tone(CHROMATIC_SCALE[(self_index + other_index) % TONES_COUNT])
26
27
28 def __sub__(self, other):
29 if isinstance(other, Tone):
30 return Interval(CHROMATIC_SCALE.index(str(self)) - CHROMATIC_SCALE.index(str(other)))
31 elif isinstance(other, Interval):
32 self_index = CHROMATIC_SCALE.index(str(self))
33 other_index = other.interval_index
34 return Tone(CHROMATIC_SCALE[TONES_COUNT - (abs(other_index - self_index) % TONES_COUNT)])
35
36class Interval:
37 __NUMBER_OF_SEMITONES_TO_STRING = ['unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th',
38 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th']
39
40 @property
41 def interval_index(self):
42 return self._number_of_semitones
43
44 def __init__(self, number_of_semitones):
45 self._number_of_semitones = number_of_semitones % TONES_COUNT
46
47 def __str__(self):
48 return self.__NUMBER_OF_SEMITONES_TO_STRING[abs(self._number_of_semitones)]
49
50 def __neg__(self):
51 return Interval(-self._number_of_semitones)
52
53 def __add__(self, other):
54 if isinstance(other, Tone):
55 raise TypeError("Invalid operation")
56 return Interval(self._number_of_semitones + other._number_of_semitones)
57
58 def __sub__(self, other):
59 if isinstance(other, Tone):
60 raise TypeError("Invalid operation")
61
62class Chord:
63 def __init__(self, *args):
64 seen = set()
65 self._tones = [tone for tone in args if not (tone in seen or seen.add(tone))]
66
67 if len(self._tones) <= 1:
68 raise TypeError("Cannot have a chord made of only 1 unique tone")
69
70 self._main_tone = self[0]
71 self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone))
72
73 def add_tone(self, tone):
74 if tone not in self._tones:
75 self._tones.append(tone)
76
77 def remove_tone(self, tone):
78 if tone not in self._tones:
79 raise TypeError(f"Cannot remove tone {str(tone)} from chord {str(self)}")
80
81 if tone == self._main_tone:
82 self._main_tone = self[1]
83 self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone))
84 self._tones = list(filter(lambda t: str(t) != str(tone), self._tones))
85
86 def __get_tone_index(self, tone):
87 return CHROMATIC_SCALE.index(str(tone))
88
89 def __str__(self):
90 return '-'.join(list(filter(lambda tone: str(tone) in self._tones, CHROMATIC_SCALE[self._main_tone_index:] + CHROMATIC_SCALE[:self._main_tone_index])))
91
92 def __add__(self, other):
93 if isinstance(other, Tone):
94 new_chord = copy.deepcopy(self)
95 new_chord.add_tone(other)
96 return new_chord
97 elif isinstance(other, Chord):
98 new_chord = copy.deepcopy(self)
99
100 for tone in other._tones:
101 if tone not in new_chord._tones:
102 new_chord._tones.append(tone)
103
104 return new_chord
105
106 def __sub__(self, other):
107 if isinstance(other, Tone):
108 new_chord = copy.deepcopy(self)
109 new_chord.remove_tone(other)
110
111 if len(new_chord._tones) < 2:
112 raise TypeError("Cannot have a chord made of only 1 unique tone")
113 return new_chord
114
115 def __getitem__(self, index):
116 return self._tones[index]
117
118 def __setitem__(self, index, value):
119 self._tones[index] = value
120
121 def is_minor(self):
122 for tone in self[1:]:
123 curr_tone_idx = self.__get_tone_index(tone)
124 if(abs(self._main_tone_index - curr_tone_idx) == 3):
125 return True
126
127 return False
128
129 def is_major(self):
130 for tone in self[1:]:
131 curr_tone_idx = self.__get_tone_index(tone)
132 if(abs(self._main_tone_index - curr_tone_idx) == 4):
133 return True
134
135 return False
136
137 def is_power_chord(self):
138 return not (self.is_major() or self.is_minor())
139
140 def transposed(self, interval):
141 step = interval.interval_index
142 new_chord = copy.deepcopy(self)
143 new_chord._tones.clear()
144
145 for tone in self._tones:
146 tone_index = self.__get_tone_index(tone)
147 new_tone_index = (tone_index + step) % TONES_COUNT
148 if tone == self._main_tone:
149 new_chord._main_tone = Tone(CHROMATIC_SCALE[new_tone_index])
150 new_chord._main_tone_index = new_tone_index
151 new_chord._tones.append(Tone(CHROMATIC_SCALE[new_tone_index]))
152
153 return new_chord
....FFF......................F.......
======================================================================
FAIL: test_is_major (test.TestBasicChordFunctionality.test_is_major)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 107, in test_is_major
self.assertTrue(a_major_chord.is_major())
AssertionError: False is not true
======================================================================
FAIL: test_is_minor (test.TestBasicChordFunctionality.test_is_minor)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 90, in test_is_minor
self.assertTrue(a_minor_chord.is_minor())
AssertionError: False is not true
======================================================================
FAIL: test_is_power_chord (test.TestBasicChordFunctionality.test_is_power_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 116, in test_is_power_chord
self.assertFalse(a_minor_chord.is_power_chord())
AssertionError: True is not false
======================================================================
FAIL: test_subtract_interval_from_tone_same_tone (test.TestOperations.test_subtract_interval_from_tone_same_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 223, in test_subtract_interval_from_tone_same_tone
self.assertEqual(str(still_g), "G")
AssertionError: 'F' != 'G'
- F
+ G
----------------------------------------------------------------------
Ran 37 tests in 0.003s
FAILED (failures=4)
f | 1 | import copy | f | 1 | import copy |
2 | 2 | ||||
3 | CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | 3 | CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | ||
4 | TONES_COUNT = 12 | 4 | TONES_COUNT = 12 | ||
5 | 5 | ||||
6 | class Tone: | 6 | class Tone: | ||
7 | def __init__(self, name): | 7 | def __init__(self, name): | ||
8 | self._name = name | 8 | self._name = name | ||
9 | 9 | ||||
10 | def __str__(self): | 10 | def __str__(self): | ||
11 | return self._name | 11 | return self._name | ||
12 | 12 | ||||
13 | def __eq__(self, other): | 13 | def __eq__(self, other): | ||
14 | return str(self) == str(other) | 14 | return str(self) == str(other) | ||
15 | 15 | ||||
16 | def __hash__(self): | 16 | def __hash__(self): | ||
17 | return hash(self._name) | 17 | return hash(self._name) | ||
18 | 18 | ||||
19 | def __add__(self, other): | 19 | def __add__(self, other): | ||
20 | if isinstance(other, Tone): | 20 | if isinstance(other, Tone): | ||
21 | return Chord(self, other) | 21 | return Chord(self, other) | ||
22 | elif isinstance(other, Interval): | 22 | elif isinstance(other, Interval): | ||
23 | self_index = CHROMATIC_SCALE.index(str(self)) | 23 | self_index = CHROMATIC_SCALE.index(str(self)) | ||
24 | other_index = other.interval_index | 24 | other_index = other.interval_index | ||
25 | return Tone(CHROMATIC_SCALE[(self_index + other_index) % TONES_COUNT]) | 25 | return Tone(CHROMATIC_SCALE[(self_index + other_index) % TONES_COUNT]) | ||
26 | 26 | ||||
27 | 27 | ||||
28 | def __sub__(self, other): | 28 | def __sub__(self, other): | ||
29 | if isinstance(other, Tone): | 29 | if isinstance(other, Tone): | ||
30 | return Interval(CHROMATIC_SCALE.index(str(self)) - CHROMATIC_SCALE.index(str(other))) | 30 | return Interval(CHROMATIC_SCALE.index(str(self)) - CHROMATIC_SCALE.index(str(other))) | ||
31 | elif isinstance(other, Interval): | 31 | elif isinstance(other, Interval): | ||
32 | self_index = CHROMATIC_SCALE.index(str(self)) | 32 | self_index = CHROMATIC_SCALE.index(str(self)) | ||
33 | other_index = other.interval_index | 33 | other_index = other.interval_index | ||
34 | return Tone(CHROMATIC_SCALE[TONES_COUNT - (abs(other_index - self_index) % TONES_COUNT)]) | 34 | return Tone(CHROMATIC_SCALE[TONES_COUNT - (abs(other_index - self_index) % TONES_COUNT)]) | ||
35 | 35 | ||||
36 | class Interval: | 36 | class Interval: | ||
37 | __NUMBER_OF_SEMITONES_TO_STRING = ['unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', | 37 | __NUMBER_OF_SEMITONES_TO_STRING = ['unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', | ||
38 | 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th'] | 38 | 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th'] | ||
39 | 39 | ||||
40 | @property | 40 | @property | ||
41 | def interval_index(self): | 41 | def interval_index(self): | ||
42 | return self._number_of_semitones | 42 | return self._number_of_semitones | ||
43 | 43 | ||||
44 | def __init__(self, number_of_semitones): | 44 | def __init__(self, number_of_semitones): | ||
45 | self._number_of_semitones = number_of_semitones % TONES_COUNT | 45 | self._number_of_semitones = number_of_semitones % TONES_COUNT | ||
46 | 46 | ||||
47 | def __str__(self): | 47 | def __str__(self): | ||
48 | return self.__NUMBER_OF_SEMITONES_TO_STRING[abs(self._number_of_semitones)] | 48 | return self.__NUMBER_OF_SEMITONES_TO_STRING[abs(self._number_of_semitones)] | ||
49 | 49 | ||||
50 | def __neg__(self): | 50 | def __neg__(self): | ||
51 | return Interval(-self._number_of_semitones) | 51 | return Interval(-self._number_of_semitones) | ||
52 | 52 | ||||
53 | def __add__(self, other): | 53 | def __add__(self, other): | ||
54 | if isinstance(other, Tone): | 54 | if isinstance(other, Tone): | ||
55 | raise TypeError("Invalid operation") | 55 | raise TypeError("Invalid operation") | ||
56 | return Interval(self._number_of_semitones + other._number_of_semitones) | 56 | return Interval(self._number_of_semitones + other._number_of_semitones) | ||
57 | 57 | ||||
58 | def __sub__(self, other): | 58 | def __sub__(self, other): | ||
59 | if isinstance(other, Tone): | 59 | if isinstance(other, Tone): | ||
60 | raise TypeError("Invalid operation") | 60 | raise TypeError("Invalid operation") | ||
61 | 61 | ||||
62 | class Chord: | 62 | class Chord: | ||
63 | def __init__(self, *args): | 63 | def __init__(self, *args): | ||
64 | seen = set() | 64 | seen = set() | ||
65 | self._tones = [tone for tone in args if not (tone in seen or seen.add(tone))] | 65 | self._tones = [tone for tone in args if not (tone in seen or seen.add(tone))] | ||
66 | 66 | ||||
67 | if len(self._tones) <= 1: | 67 | if len(self._tones) <= 1: | ||
68 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 68 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
69 | 69 | ||||
70 | self._main_tone = self[0] | 70 | self._main_tone = self[0] | ||
71 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | 71 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | ||
72 | 72 | ||||
73 | def add_tone(self, tone): | 73 | def add_tone(self, tone): | ||
74 | if tone not in self._tones: | 74 | if tone not in self._tones: | ||
75 | self._tones.append(tone) | 75 | self._tones.append(tone) | ||
76 | 76 | ||||
77 | def remove_tone(self, tone): | 77 | def remove_tone(self, tone): | ||
78 | if tone not in self._tones: | 78 | if tone not in self._tones: | ||
79 | raise TypeError(f"Cannot remove tone {str(tone)} from chord {str(self)}") | 79 | raise TypeError(f"Cannot remove tone {str(tone)} from chord {str(self)}") | ||
80 | 80 | ||||
81 | if tone == self._main_tone: | 81 | if tone == self._main_tone: | ||
82 | self._main_tone = self[1] | 82 | self._main_tone = self[1] | ||
83 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | 83 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | ||
84 | self._tones = list(filter(lambda t: str(t) != str(tone), self._tones)) | 84 | self._tones = list(filter(lambda t: str(t) != str(tone), self._tones)) | ||
85 | 85 | ||||
86 | def __get_tone_index(self, tone): | 86 | def __get_tone_index(self, tone): | ||
87 | return CHROMATIC_SCALE.index(str(tone)) | 87 | return CHROMATIC_SCALE.index(str(tone)) | ||
88 | 88 | ||||
89 | def __str__(self): | 89 | def __str__(self): | ||
90 | return '-'.join(list(filter(lambda tone: str(tone) in self._tones, CHROMATIC_SCALE[self._main_tone_index:] + CHROMATIC_SCALE[:self._main_tone_index]))) | 90 | return '-'.join(list(filter(lambda tone: str(tone) in self._tones, CHROMATIC_SCALE[self._main_tone_index:] + CHROMATIC_SCALE[:self._main_tone_index]))) | ||
91 | 91 | ||||
92 | def __add__(self, other): | 92 | def __add__(self, other): | ||
93 | if isinstance(other, Tone): | 93 | if isinstance(other, Tone): | ||
94 | new_chord = copy.deepcopy(self) | 94 | new_chord = copy.deepcopy(self) | ||
95 | new_chord.add_tone(other) | 95 | new_chord.add_tone(other) | ||
96 | return new_chord | 96 | return new_chord | ||
97 | elif isinstance(other, Chord): | 97 | elif isinstance(other, Chord): | ||
98 | new_chord = copy.deepcopy(self) | 98 | new_chord = copy.deepcopy(self) | ||
99 | 99 | ||||
100 | for tone in other._tones: | 100 | for tone in other._tones: | ||
101 | if tone not in new_chord._tones: | 101 | if tone not in new_chord._tones: | ||
102 | new_chord._tones.append(tone) | 102 | new_chord._tones.append(tone) | ||
103 | 103 | ||||
104 | return new_chord | 104 | return new_chord | ||
105 | 105 | ||||
106 | def __sub__(self, other): | 106 | def __sub__(self, other): | ||
107 | if isinstance(other, Tone): | 107 | if isinstance(other, Tone): | ||
108 | new_chord = copy.deepcopy(self) | 108 | new_chord = copy.deepcopy(self) | ||
109 | new_chord.remove_tone(other) | 109 | new_chord.remove_tone(other) | ||
110 | 110 | ||||
111 | if len(new_chord._tones) < 2: | 111 | if len(new_chord._tones) < 2: | ||
112 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 112 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
113 | return new_chord | 113 | return new_chord | ||
114 | 114 | ||||
115 | def __getitem__(self, index): | 115 | def __getitem__(self, index): | ||
116 | return self._tones[index] | 116 | return self._tones[index] | ||
117 | 117 | ||||
118 | def __setitem__(self, index, value): | 118 | def __setitem__(self, index, value): | ||
119 | self._tones[index] = value | 119 | self._tones[index] = value | ||
120 | 120 | ||||
121 | def is_minor(self): | 121 | def is_minor(self): | ||
n | 122 | for tone in self._tones[1:]: | n | 122 | for tone in self[1:]: |
123 | curr_tone_idx = self.__get_tone_index(tone) | 123 | curr_tone_idx = self.__get_tone_index(tone) | ||
124 | if(abs(self._main_tone_index - curr_tone_idx) == 3): | 124 | if(abs(self._main_tone_index - curr_tone_idx) == 3): | ||
125 | return True | 125 | return True | ||
126 | 126 | ||||
127 | return False | 127 | return False | ||
128 | 128 | ||||
129 | def is_major(self): | 129 | def is_major(self): | ||
t | 130 | for tone in self._tones[1:]: | t | 130 | for tone in self[1:]: |
131 | curr_tone_idx = self.__get_tone_index(tone) | 131 | curr_tone_idx = self.__get_tone_index(tone) | ||
132 | if(abs(self._main_tone_index - curr_tone_idx) == 4): | 132 | if(abs(self._main_tone_index - curr_tone_idx) == 4): | ||
133 | return True | 133 | return True | ||
134 | 134 | ||||
135 | return False | 135 | return False | ||
136 | 136 | ||||
137 | def is_power_chord(self): | 137 | def is_power_chord(self): | ||
138 | return not (self.is_major() or self.is_minor()) | 138 | return not (self.is_major() or self.is_minor()) | ||
139 | 139 | ||||
140 | def transposed(self, interval): | 140 | def transposed(self, interval): | ||
141 | step = interval.interval_index | 141 | step = interval.interval_index | ||
142 | new_chord = copy.deepcopy(self) | 142 | new_chord = copy.deepcopy(self) | ||
143 | new_chord._tones.clear() | 143 | new_chord._tones.clear() | ||
144 | 144 | ||||
145 | for tone in self._tones: | 145 | for tone in self._tones: | ||
146 | tone_index = self.__get_tone_index(tone) | 146 | tone_index = self.__get_tone_index(tone) | ||
147 | new_tone_index = (tone_index + step) % TONES_COUNT | 147 | new_tone_index = (tone_index + step) % TONES_COUNT | ||
148 | if tone == self._main_tone: | 148 | if tone == self._main_tone: | ||
149 | new_chord._main_tone = Tone(CHROMATIC_SCALE[new_tone_index]) | 149 | new_chord._main_tone = Tone(CHROMATIC_SCALE[new_tone_index]) | ||
150 | new_chord._main_tone_index = new_tone_index | 150 | new_chord._main_tone_index = new_tone_index | ||
151 | new_chord._tones.append(Tone(CHROMATIC_SCALE[new_tone_index])) | 151 | new_chord._tones.append(Tone(CHROMATIC_SCALE[new_tone_index])) | ||
152 | 152 | ||||
153 | return new_chord | 153 | return new_chord |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import copy | f | 1 | import copy |
2 | 2 | ||||
3 | CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | 3 | CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | ||
4 | TONES_COUNT = 12 | 4 | TONES_COUNT = 12 | ||
5 | 5 | ||||
6 | class Tone: | 6 | class Tone: | ||
7 | def __init__(self, name): | 7 | def __init__(self, name): | ||
8 | self._name = name | 8 | self._name = name | ||
9 | 9 | ||||
10 | def __str__(self): | 10 | def __str__(self): | ||
11 | return self._name | 11 | return self._name | ||
12 | 12 | ||||
13 | def __eq__(self, other): | 13 | def __eq__(self, other): | ||
14 | return str(self) == str(other) | 14 | return str(self) == str(other) | ||
15 | 15 | ||||
16 | def __hash__(self): | 16 | def __hash__(self): | ||
17 | return hash(self._name) | 17 | return hash(self._name) | ||
18 | 18 | ||||
19 | def __add__(self, other): | 19 | def __add__(self, other): | ||
20 | if isinstance(other, Tone): | 20 | if isinstance(other, Tone): | ||
21 | return Chord(self, other) | 21 | return Chord(self, other) | ||
22 | elif isinstance(other, Interval): | 22 | elif isinstance(other, Interval): | ||
23 | self_index = CHROMATIC_SCALE.index(str(self)) | 23 | self_index = CHROMATIC_SCALE.index(str(self)) | ||
n | 24 | other_index = other.get_interval_index | n | 24 | other_index = other.interval_index |
25 | return Tone(CHROMATIC_SCALE[(self_index + other_index) % TONES_COUNT]) | 25 | return Tone(CHROMATIC_SCALE[(self_index + other_index) % TONES_COUNT]) | ||
26 | 26 | ||||
27 | 27 | ||||
28 | def __sub__(self, other): | 28 | def __sub__(self, other): | ||
29 | if isinstance(other, Tone): | 29 | if isinstance(other, Tone): | ||
30 | return Interval(CHROMATIC_SCALE.index(str(self)) - CHROMATIC_SCALE.index(str(other))) | 30 | return Interval(CHROMATIC_SCALE.index(str(self)) - CHROMATIC_SCALE.index(str(other))) | ||
31 | elif isinstance(other, Interval): | 31 | elif isinstance(other, Interval): | ||
32 | self_index = CHROMATIC_SCALE.index(str(self)) | 32 | self_index = CHROMATIC_SCALE.index(str(self)) | ||
n | 33 | other_index = other.get_interval_index | n | 33 | other_index = other.interval_index |
34 | return Tone(CHROMATIC_SCALE[TONES_COUNT - (abs(other_index - self_index) % TONES_COUNT)]) | 34 | return Tone(CHROMATIC_SCALE[TONES_COUNT - (abs(other_index - self_index) % TONES_COUNT)]) | ||
n | 35 | n | |||
36 | |||||
37 | 35 | ||||
38 | class Interval: | 36 | class Interval: | ||
n | 39 | __number_of_semitones_to_string = {0: 'unison', 1: 'minor 2nd', 2: 'major 2nd', 3: 'minor 3rd', 4: 'major 3rd', 5: 'perfect 4th', | n | 37 | __NUMBER_OF_SEMITONES_TO_STRING = ['unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', |
40 | 6: 'diminished 5th', 7: 'perfect 5th', 8: 'minor 6th', 9: 'major 6th', 10: 'minor 7th', 11: 'major 7th'} | 38 | 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th'] | ||
41 | 39 | ||||
42 | @property | 40 | @property | ||
n | 43 | def get_interval_index(self): | n | 41 | def interval_index(self): |
44 | return self._number_of_semitones | 42 | return self._number_of_semitones | ||
45 | 43 | ||||
46 | def __init__(self, number_of_semitones): | 44 | def __init__(self, number_of_semitones): | ||
47 | self._number_of_semitones = number_of_semitones % TONES_COUNT | 45 | self._number_of_semitones = number_of_semitones % TONES_COUNT | ||
48 | 46 | ||||
49 | def __str__(self): | 47 | def __str__(self): | ||
n | 50 | return self.__number_of_semitones_to_string[abs(self._number_of_semitones)] | n | 48 | return self.__NUMBER_OF_SEMITONES_TO_STRING[abs(self._number_of_semitones)] |
51 | 49 | ||||
52 | def __neg__(self): | 50 | def __neg__(self): | ||
53 | return Interval(-self._number_of_semitones) | 51 | return Interval(-self._number_of_semitones) | ||
54 | 52 | ||||
55 | def __add__(self, other): | 53 | def __add__(self, other): | ||
56 | if isinstance(other, Tone): | 54 | if isinstance(other, Tone): | ||
57 | raise TypeError("Invalid operation") | 55 | raise TypeError("Invalid operation") | ||
n | 58 | else: | n | ||
59 | return Interval(self._number_of_semitones + other._number_of_semitones) | 56 | return Interval(self._number_of_semitones + other._number_of_semitones) | ||
60 | 57 | ||||
61 | def __sub__(self, other): | 58 | def __sub__(self, other): | ||
62 | if isinstance(other, Tone): | 59 | if isinstance(other, Tone): | ||
63 | raise TypeError("Invalid operation") | 60 | raise TypeError("Invalid operation") | ||
64 | 61 | ||||
65 | class Chord: | 62 | class Chord: | ||
66 | def __init__(self, *args): | 63 | def __init__(self, *args): | ||
n | 67 | self._tones = [] | n | ||
68 | seen = set() | 64 | seen = set() | ||
69 | self._tones = [tone for tone in args if not (tone in seen or seen.add(tone))] | 65 | self._tones = [tone for tone in args if not (tone in seen or seen.add(tone))] | ||
70 | 66 | ||||
71 | if len(self._tones) <= 1: | 67 | if len(self._tones) <= 1: | ||
72 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 68 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
73 | 69 | ||||
74 | self._main_tone = self[0] | 70 | self._main_tone = self[0] | ||
75 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | 71 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | ||
76 | 72 | ||||
n | 77 | def __add_tone(self, tone): | n | 73 | def add_tone(self, tone): |
78 | if tone not in self._tones: | 74 | if tone not in self._tones: | ||
79 | self._tones.append(tone) | 75 | self._tones.append(tone) | ||
80 | 76 | ||||
n | 81 | def __remove_tone(self, tone): | n | 77 | def remove_tone(self, tone): |
82 | if tone not in self._tones: | 78 | if tone not in self._tones: | ||
83 | raise TypeError(f"Cannot remove tone {str(tone)} from chord {str(self)}") | 79 | raise TypeError(f"Cannot remove tone {str(tone)} from chord {str(self)}") | ||
84 | 80 | ||||
85 | if tone == self._main_tone: | 81 | if tone == self._main_tone: | ||
86 | self._main_tone = self[1] | 82 | self._main_tone = self[1] | ||
n | 87 | #print(self._main_tone) | n | ||
88 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | 83 | self._main_tone_index = CHROMATIC_SCALE.index(str(self._main_tone)) | ||
89 | self._tones = list(filter(lambda t: str(t) != str(tone), self._tones)) | 84 | self._tones = list(filter(lambda t: str(t) != str(tone), self._tones)) | ||
90 | 85 | ||||
91 | def __get_tone_index(self, tone): | 86 | def __get_tone_index(self, tone): | ||
92 | return CHROMATIC_SCALE.index(str(tone)) | 87 | return CHROMATIC_SCALE.index(str(tone)) | ||
93 | 88 | ||||
94 | def __str__(self): | 89 | def __str__(self): | ||
n | 95 | return '-'.join(list(filter(lambda tone: str(tone) in list(map(str, self._tones)), CHROMATIC_SCALE[self._main_tone_index:])) + | n | 90 | return '-'.join(list(filter(lambda tone: str(tone) in self._tones, CHROMATIC_SCALE[self._main_tone_index:] + CHROMATIC_SCALE[:self._main_tone_index]))) |
96 | list(filter(lambda tone: str(tone) in list(map(str, self._tones)), CHROMATIC_SCALE[:self._main_tone_index]))) | 91 | |||
97 | |||||
98 | def __add__(self, other): | 92 | def __add__(self, other): | ||
99 | if isinstance(other, Tone): | 93 | if isinstance(other, Tone): | ||
100 | new_chord = copy.deepcopy(self) | 94 | new_chord = copy.deepcopy(self) | ||
n | 101 | new_chord.__add_tone(other) | n | 95 | new_chord.add_tone(other) |
102 | return new_chord | 96 | return new_chord | ||
103 | elif isinstance(other, Chord): | 97 | elif isinstance(other, Chord): | ||
104 | new_chord = copy.deepcopy(self) | 98 | new_chord = copy.deepcopy(self) | ||
105 | 99 | ||||
106 | for tone in other._tones: | 100 | for tone in other._tones: | ||
107 | if tone not in new_chord._tones: | 101 | if tone not in new_chord._tones: | ||
108 | new_chord._tones.append(tone) | 102 | new_chord._tones.append(tone) | ||
109 | 103 | ||||
110 | return new_chord | 104 | return new_chord | ||
111 | 105 | ||||
n | 112 | n | |||
113 | def __sub__(self, other): | 106 | def __sub__(self, other): | ||
114 | if isinstance(other, Tone): | 107 | if isinstance(other, Tone): | ||
n | n | 108 | new_chord = copy.deepcopy(self) | ||
109 | new_chord.remove_tone(other) | ||||
110 | |||||
115 | if len(self._tones) < 3: | 111 | if len(new_chord._tones) < 2: | ||
116 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 112 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
n | 117 | n | |||
118 | new_chord = copy.deepcopy(self) | ||||
119 | new_chord.__remove_tone(other) | ||||
120 | return new_chord | 113 | return new_chord | ||
121 | 114 | ||||
122 | def __getitem__(self, index): | 115 | def __getitem__(self, index): | ||
123 | return self._tones[index] | 116 | return self._tones[index] | ||
124 | 117 | ||||
125 | def __setitem__(self, index, value): | 118 | def __setitem__(self, index, value): | ||
126 | self._tones[index] = value | 119 | self._tones[index] = value | ||
127 | 120 | ||||
n | 128 | #Не знам дали трябва да проверяваме отляво на главния тон получава ли се минор/мажор (закоментирания код в двете функции) | n | ||
129 | def is_minor(self): | 121 | def is_minor(self): | ||
n | 130 | for tone in self._tones: | n | 122 | for tone in self._tones[1:]: |
131 | if tone == self._main_tone: | ||||
132 | continue | ||||
133 | curr_tone_idx = self.__get_tone_index(tone) | 123 | curr_tone_idx = self.__get_tone_index(tone) | ||
134 | if(abs(self._main_tone_index - curr_tone_idx) == 3): | 124 | if(abs(self._main_tone_index - curr_tone_idx) == 3): | ||
135 | return True | 125 | return True | ||
n | 136 | # if(abs(self._main_tone_index - curr_tone_idx) == 3 or abs(self._main_tone_index - curr_tone_idx) == 8): | n | ||
137 | # return True | ||||
138 | 126 | ||||
139 | return False | 127 | return False | ||
140 | 128 | ||||
141 | def is_major(self): | 129 | def is_major(self): | ||
n | 142 | for tone in self._tones: | n | 130 | for tone in self._tones[1:]: |
143 | if tone == self._main_tone: | ||||
144 | continue | ||||
145 | curr_tone_idx = self.__get_tone_index(tone) | 131 | curr_tone_idx = self.__get_tone_index(tone) | ||
146 | if(abs(self._main_tone_index - curr_tone_idx) == 4): | 132 | if(abs(self._main_tone_index - curr_tone_idx) == 4): | ||
147 | return True | 133 | return True | ||
n | 148 | # if(abs(self._main_tone_index - curr_tone_idx) == 4 or abs(self._main_tone_index - curr_tone_idx) == 7): | n | ||
149 | # return True | ||||
150 | 134 | ||||
151 | return False | 135 | return False | ||
152 | 136 | ||||
153 | def is_power_chord(self): | 137 | def is_power_chord(self): | ||
n | 154 | return self.is_major() == False and self.is_minor() == False | n | 138 | return not (self.is_major() or self.is_minor()) |
155 | 139 | ||||
156 | def transposed(self, interval): | 140 | def transposed(self, interval): | ||
n | 157 | step = interval.get_interval_index | n | 141 | step = interval.interval_index |
158 | new_chord = copy.deepcopy(self) | 142 | new_chord = copy.deepcopy(self) | ||
159 | new_chord._tones.clear() | 143 | new_chord._tones.clear() | ||
160 | 144 | ||||
161 | for tone in self._tones: | 145 | for tone in self._tones: | ||
162 | tone_index = self.__get_tone_index(tone) | 146 | tone_index = self.__get_tone_index(tone) | ||
163 | new_tone_index = (tone_index + step) % TONES_COUNT | 147 | new_tone_index = (tone_index + step) % TONES_COUNT | ||
164 | if tone == self._main_tone: | 148 | if tone == self._main_tone: | ||
165 | new_chord._main_tone = Tone(CHROMATIC_SCALE[new_tone_index]) | 149 | new_chord._main_tone = Tone(CHROMATIC_SCALE[new_tone_index]) | ||
166 | new_chord._main_tone_index = new_tone_index | 150 | new_chord._main_tone_index = new_tone_index | ||
167 | new_chord._tones.append(Tone(CHROMATIC_SCALE[new_tone_index])) | 151 | new_chord._tones.append(Tone(CHROMATIC_SCALE[new_tone_index])) | ||
168 | 152 | ||||
169 | return new_chord | 153 | return new_chord | ||
t | 170 | t | |||
171 | #CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | ||||
172 | |||||
173 | # Това условие също ми е неясно - отляво в смисъл като позиция в таблицата (CHROMATIC_SCALE) или отляво, като първи параметър (а + б // а е отляво) | ||||
174 | # Забележка: Искаме това да работи само когато тонът е отляво. Когато тонът е отдясно, го приемаме за недефинирано поведение и искаме да се възбуди TypeError с текст "Invalid operation". Семпло, но няма да ви вгорчаваме живота… Повече. | ||||
175 | |||||
176 | #Тук не трябва ли да хвърли грешка първо за това, че chord е с <3 тона? Смисъл, има ли значение коя грешка ще се хвърли първо? | ||||
177 | # Изваждането на несъществуващ тон също е недефинирано поведение, отново искаме TypeError, но този път искаме текстът да бъде "Cannot remove tone <Tone> from chord <Chord>", където <Tone> и <Chord> са съотвените стрингови репрезентации на тона и акорда: | ||||
178 | |||||
179 | # c_power_chord = Chord(Tone("C"), Tone("G")) | ||||
180 | # result_chord = c_power_chord - Tone("E") | ||||
181 | # Traceback (most recent call last): | ||||
182 | # File ... | ||||
183 | # ... | ||||
184 | # TypeError: Cannot remove tone E from chord C-G | ||||
185 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|