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 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||