1class Tone:
2 TONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
3
4 def __init__(self, name):
5 if name not in self.TONES:
6 raise ValueError(f"Invalid tone: {name}")
7 self.name = name
8
9 def __str__(self):
10 return self.name
11
12 def __eq__(self, other):
13 return isinstance(other, Tone) and self.name == other.name
14
15#използва хеш стойността на обекта, за да определи къде да го постави, ако има обекти със
16# същата хеш стойност ги сравнява, за да реши дали тези обекти наистина са равни
17 def __hash__(self):
18 return hash(self.name)
19
20 def __add__(self, other):
21 if isinstance(other, Interval):
22 return self._transposed_by_interval(other)
23 elif isinstance(other, Tone):
24 return Chord(self, other)
25 else:
26 raise TypeError("Invalid operation")
27
28 def __sub__(self, other):
29 if isinstance(other, Tone):
30 return self._distance_to(other)
31
32 elif isinstance(other, Interval):
33 return self._transposed_by_interval(other, subtract = True)
34
35 else:
36 raise TypeError("Invalid operation")
37
38 def _transposed_by_interval(self, interval, subtract=False):
39 current_index = self.TONES.index(self.name)
40
41 if subtract:
42 semitone_change = -interval.semitones
43 else:
44 semitone_change = interval.semitones
45
46 new_index = (current_index + semitone_change) % 12
47
48 return Tone(self.TONES[new_index])
49
50 def _distance_to(self, other):
51 current_index = self.TONES.index(self.name)
52 other_index = self.TONES.index(other.name)
53
54 distance = (current_index - other_index) % 12
55 return Interval(distance)
56
57
58class Interval:
59 INTERVALS = ["unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",
60 "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",
61 "major 6th", "minor 7th", "major 7th"]
62
63 def __init__(self, semitones):
64 if not isinstance(semitones, int):
65 raise TypeError("Semitone distance must be an integer")
66 self.semitones = semitones % 12
67
68 def __str__(self):
69 return self.INTERVALS[self.semitones]
70
71 def __add__(self, other):
72 if isinstance(other, Interval):
73 return Interval(self.semitones + other.semitones)
74 raise TypeError("Invalid operation")
75
76 def __neg__(self):
77 return Interval(-self.semitones)
78
79
80class Chord:
81 def __init__(self, *tones):
82 if not all(isinstance(tone, Tone) for tone in tones):
83 raise ValueError("All components must be instances of Tone")
84
85 unique = list(set(tones))
86
87 if len(unique) < 2:
88 raise TypeError("A chord must have at least two unique tones")
89
90 self.tones = sorted(unique, key=lambda tone: Tone.TONES.index(tone.name))
91
92 self.root = self.tones[0]
93
94 def __str__(self):
95 return "-".join(str(tone) for tone in self.tones)
96
97 def __add__(self, other):
98 if isinstance(other, Tone):
99 if other not in self.tones:
100 return Chord(*self.tones, other)
101 else:
102 return Chord(*self.tones)
103 elif isinstance(other, Chord):
104 combined_tones = list(set(self.tones + other.tones))
105 return Chord(*combined_tones)
106 else:
107 raise TypeError("Invalid operation")
108
109 def __sub__(self, other):
110 if isinstance(other, Tone):
111 if other in self.tones:
112 new_tones = []
113
114 for tone in self.tones:
115 if tone != other:
116 new_tones.append(tone)
117
118 if len(new_tones) < 2:
119 raise TypeError("Cannot have a chord with fewer than 2 unique tones")
120
121 return Chord(*new_tones)
122 else:
123 raise TypeError(f"Tone {other} is not in this chord")
124 return None
125
126 def is_minor(self):
127 return self._has_interval(3)
128
129 def is_major(self):
130 return self._has_interval(4)
131
132 def is_power_chord(self):
133 return not self.is_minor() and not self.is_major()
134
135 def _has_interval(self, interval_semitones):
136 for tone in self.tones[1:]:
137 interval = self.root - tone
138 if interval.semitones == interval_semitones:
139 return True
140 return False
141
142 def transposed(self, interval):
143 if not isinstance(interval, Interval):
144 raise ValueError("Input must be an instance of Interval")
145
146 transposed_tones = []
147
148 for tone in self.tones:
149 transposed_tone = tone + interval
150 transposed_tones.append(transposed_tone)
151
152 return Chord(*transposed_tones)
FFFF.FF..FF..........FFF...F...F.F...
======================================================================
FAIL: test_chord_not_enough_tones (test.TestBasicChordFunctionality.test_chord_not_enough_tones)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 80, in test_chord_not_enough_tones
self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
AssertionError: 'A chord must have at least two unique tones' != 'Cannot have a chord made of only 1 unique tone'
- A chord must have at least two unique tones
+ Cannot have a chord made of only 1 unique tone
======================================================================
FAIL: test_chord_ordering (test.TestBasicChordFunctionality.test_chord_ordering)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 65, in test_chord_ordering
self.assertEqual(str(f_sixth_ninth_chord), "F-A-C-E")
AssertionError: 'C-E-F-A' != 'F-A-C-E'
- C-E-F-A
+ F-A-C-E
======================================================================
FAIL: test_chord_str (test.TestBasicChordFunctionality.test_chord_str)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 60, in test_chord_str
self.assertEqual(str(a_major_chord), "A-C#-E")
AssertionError: 'C#-E-A' != 'A-C#-E'
- C#-E-A
+ A-C#-E
======================================================================
FAIL: test_chord_tone_repetition (test.TestBasicChordFunctionality.test_chord_tone_repetition)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 74, in test_chord_tone_repetition
self.assertEqual(str(a_minor_chord), "A-C-E")
AssertionError: 'C-E-A' != 'A-C-E'
- C-E-A
+ A-C-E
======================================================================
FAIL: test_is_minor (test.TestBasicChordFunctionality.test_is_minor)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 98, in test_is_minor
self.assertFalse(a_kind_of_power_chord.is_minor())
AssertionError: True is not false
======================================================================
FAIL: test_is_power_chord (test.TestBasicChordFunctionality.test_is_power_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 124, in test_is_power_chord
self.assertTrue(a_kind_of_power_chord.is_power_chord())
AssertionError: False is not true
======================================================================
FAIL: test_transposed_negative_overflow (test.TestBasicChordFunctionality.test_transposed_negative_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 152, in test_transposed_negative_overflow
self.assertEqual(str(result_chord), "G#-B-D#")
AssertionError: 'D#-G#-B' != 'G#-B-D#'
- D#-G#-B
+ G#-B-D#
======================================================================
FAIL: test_transposed_overflow (test.TestBasicChordFunctionality.test_transposed_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 145, in test_transposed_overflow
self.assertEqual(str(result_chord), "A#-D-F")
AssertionError: 'D-F-A#' != 'A#-D-F'
- D-F-A#
+ A#-D-F
======================================================================
FAIL: test_add_tone_to_chord (test.TestOperations.test_add_tone_to_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 254, in test_add_tone_to_chord
self.assertEqual(str(result_chord), "F-A-C-D")
AssertionError: 'C-D-F-A' != 'F-A-C-D'
- C-D-F-A
+ F-A-C-D
======================================================================
FAIL: 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 270, in test_add_tone_to_chord_existing_tone
self.assertEqual(str(result_chord), "F-G#-C")
AssertionError: 'C-F-G#' != 'F-G#-C'
- C-F-G#
+ F-G#-C
======================================================================
FAIL: test_add_tone_to_chord_order (test.TestOperations.test_add_tone_to_chord_order)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 261, in test_add_tone_to_chord_order
self.assertEqual(str(result_chord), "F-G-A-C")
AssertionError: 'C-F-G-A' != 'F-G-A-C'
- C-F-G-A
? --
+ F-G-A-C
? ++
======================================================================
FAIL: test_subtract_interval_from_tone_left_side_error (test.TestOperations.test_subtract_interval_from_tone_left_side_error)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 235, in test_subtract_interval_from_tone_left_side_error
self.assertEqual(str(err.exception), INVALID_OPERATION)
AssertionError: "unsupported operand type(s) for -: 'Interval' and 'Tone'" != 'Invalid operation'
- unsupported operand type(s) for -: 'Interval' and 'Tone'
+ Invalid operation
======================================================================
FAIL: test_subtract_tone_from_chord_error (test.TestOperations.test_subtract_tone_from_chord_error)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 293, in test_subtract_tone_from_chord_error
self.assertEqual(str(err.exception), expected_message)
AssertionError: 'Tone A is not in this chord' != 'Cannot remove tone A from chord C-G'
- Tone A is not in this chord
+ Cannot remove tone A from chord C-G
======================================================================
FAIL: test_tone_addition_same_tone (test.TestOperations.test_tone_addition_same_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 168, in test_tone_addition_same_tone
self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
AssertionError: 'A chord must have at least two unique tones' != 'Cannot have a chord made of only 1 unique tone'
- A chord must have at least two unique tones
+ Cannot have a chord made of only 1 unique tone
----------------------------------------------------------------------
Ran 37 tests in 0.003s
FAILED (failures=14)
07.11.2024 09:25
07.11.2024 09:24
07.11.2024 09:27
07.11.2024 09:31