1CHROMATIC_SCALE = {
2 "C": 0,
3 "C#": 1,
4 "D": 2,
5 "D#": 3,
6 "E": 4,
7 "F": 5,
8 "F#": 6,
9 "G": 7,
10 "G#": 8,
11 "A": 9,
12 "A#": 10,
13 "B": 11,
14}
15
16
17INTERVALS = {
18 "0": "unison",
19 "1": "minor 2nd",
20 "2": "major 2nd",
21 "3": "minor 3rd",
22 "4": "major 3rd",
23 "5": "perfect 4th",
24 "6": "diminished 5th",
25 "7": "perfect 5th",
26 "8": "minor 6th",
27 "9": "major 6th",
28 "10": "minor 7th",
29 "11": "major 7th",
30}
31
32
33class Tone:
34 def __init__(self, name):
35 self.name = name
36
37 def __str__(self):
38 return self.name
39
40 def __add__(self, other):
41 if isinstance(other, Tone):
42 return Chord(self, other)
43 elif isinstance(other, Interval):
44 new_index = (CHROMATIC_SCALE[self.name] + other.interval) % 12
45 key = next((k for k, v in CHROMATIC_SCALE.items() if v == new_index), None)
46 new_tone = Tone(key)
47 return new_tone
48
49 def __sub__(self, other):
50 if isinstance(other, Tone):
51 interval = Interval(
52 abs(CHROMATIC_SCALE[self.name] - CHROMATIC_SCALE[other.name]))
53 return interval
54 elif isinstance(other, Interval):
55 new_index = abs(CHROMATIC_SCALE[self.name] + 12 - other.interval) % 12
56 key = next((k for k, v in CHROMATIC_SCALE.items() if v == new_index), None)
57 new_tone = Tone(key)
58 return new_tone
59
60 # Извиква се ако __add__ не успее да извърши операцията +
61 def __radd__(self, other):
62 raise TypeError("Invalid operation")
63
64 # Извиква се ако __sub__ не успее да извърши операцията -
65 def __rsub__(self, other):
66 raise TypeError("Invalid operation")
67
68
69class Interval:
70 def __init__(self, interval):
71 self.interval = interval % 12
72
73 def __str__(self):
74 return INTERVALS[str(self.interval)]
75
76 def __add__(self, other):
77 new_interval = Interval(self.interval + other.interval)
78 return new_interval
79
80 # Предефинира унарния оператор -
81 def __neg__(self):
82 return Interval(-self.interval)
83
84
85class Chord:
86 def __init__(self, *args):
87 self.unique_tones = []
88 for item in args:
89 if self.__tone_is_unique__(item):
90 self.unique_tones.append(item)
91 if len(self.unique_tones) <= 1:
92 raise TypeError("Cannot have a chord made of only 1 unique tone")
93 self.unique_tones = self.sort_relative_to_root(self.unique_tones[0])
94
95 def __tone_is_unique__(self, tone):
96 # Проверка дали тонът вече съществува в уникалните тонове
97 for item in self.unique_tones:
98 if str(item) is str(tone):
99 return False
100 return True
101
102 def sort_relative_to_root(self, root):
103 # Получаване на индекса на основния тон (root) в хроматичната скала
104 root_index = CHROMATIC_SCALE[str(root)]
105
106 # Подреждане на тоновете спрямо root-а
107 sorted_tones = sorted(self.unique_tones, key=lambda tone: (CHROMATIC_SCALE[str(tone)] -
108 CHROMATIC_SCALE[str(root)])% 12,)
109
110 # Връщане на подредените тонове като нов списък
111 return sorted_tones
112
113 def __str__(self):
114 return "-".join(str(tone) for tone in self.unique_tones)
115
116 def __add__(self, other):
117 # Ако other е тон
118 if isinstance(other, Tone):
119 # Добавяме тона към акорда
120 if self.__tone_is_unique__(other):
121 combined_tones = self.unique_tones + [other]
122 new_chord = Chord(*combined_tones)
123 new_chord.unique_tones = new_chord.sort_relative_to_root(self.unique_tones[0])
124 return new_chord
125 # Ако other е акорд
126 elif isinstance(other, Chord):
127 # Обединяване на уникалните тонове от двата акорда
128 combined_tones = self.unique_tones + [
129 tone for tone in other.unique_tones if self.__tone_is_unique__(tone)]
130 new_chord = Chord(*combined_tones)
131 new_chord.unique_tones = new_chord.sort_relative_to_root(self.unique_tones[0])
132 return new_chord
133
134 def __sub__(self, tone):
135 if str(tone) in str(self):
136 # Създаваме нов списък без посочения тон
137 new_tones = [t for t in self.unique_tones if str(t) != str(tone)]
138
139 # Проверяваме, че новият акорд има поне два уникални тона
140 if len(new_tones) >= 2:
141 return Chord(*new_tones)
142 else:
143 # Ако след премахването има по-малко от два тона, хвърляме грешка
144 raise TypeError("Cannot have a chord made of only 1 unique tone")
145 else:
146 raise TypeError(f"Cannot remove tone {str(tone)} from chord {str(self)}")
147
148 # На всеки от тоновете добавяме стойността на интервала
149 def transposed(self, interval):
150 transposed_tones = [
151 tone + interval if interval.interval >= 0 else tone - abs(interval) for tone in self.unique_tones]
152 new_chord = Chord(*transposed_tones)
153 return new_chord
154
155 def is_minor(self):
156 root = self.unique_tones[0]
157 for item in self.unique_tones:
158 interval = abs(CHROMATIC_SCALE[str(root)] - CHROMATIC_SCALE[str(item)])
159 if INTERVALS[str(interval)] is "minor 3rd":
160 return True
161 return False
162
163 def is_major(self):
164 root = self.unique_tones[0]
165 for item in self.unique_tones:
166 interval = abs(CHROMATIC_SCALE[str(root)] - CHROMATIC_SCALE[str(item)])
167 if INTERVALS[str(interval)] is "major 3rd":
168 return True
169 return False
170
171 def is_power_chord(self):
172 return not self.is_major() and not self.is_minor()
/tmp/solution.py:159: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if INTERVALS[str(interval)] is "minor 3rd":
/tmp/solution.py:167: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if INTERVALS[str(interval)] is "major 3rd":
....FFF...........E...E............F.
======================================================================
ERROR: test_add_interval_to_tone_left_side_error (test.TestOperations.test_add_interval_to_tone_left_side_error)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 210, in test_add_interval_to_tone_left_side_error
Interval(2) + g
~~~~~~~~~~~~^~~
File "/tmp/solution.py", line 77, in __add__
new_interval = Interval(self.interval + other.interval)
^^^^^^^^^^^^^^
AttributeError: 'Tone' object has no attribute 'interval'
======================================================================
ERROR: test_add_tone_to_chord_existing_tone (test.TestOperations.test_add_tone_to_chord_existing_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 267, in test_add_tone_to_chord_existing_tone
result_chord = result_chord + Tone("G#")
~~~~~~~~~~~~~^~~~~~~~~~~~
File "/tmp/solution.py", line 62, in __radd__
raise TypeError("Invalid operation")
TypeError: Invalid operation
======================================================================
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_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.003s
FAILED (failures=4, errors=2)
06.11.2024 15:46
06.11.2024 15:51
06.11.2024 15:53
06.11.2024 15:55
06.11.2024 15:56