1class Tone:
2 _notes_index = {"C": 0, "C#": 1, "D": 2, "D#": 3, "E":4, "F":5, "F#":6, "G":7, "G#":8, "A":9, "A#":10, "B":11}
3 _index_notes = {idx: note for note, idx in _notes_index.items()}
4
5 def __init__(self, note):
6 if note not in self._notes_index:
7 raise ValueError("Invalid note")
8 self.note = note
9 self.index = self._notes_index[note]
10
11 def __str__(self):
12 return self.note
13
14 def __add__(self, other):
15 if isinstance(other, Tone):
16 return Chord(self, other)
17 elif isinstance(other, Interval):
18 newIndex = (self.index + other.semitones) % 12
19 return Tone(self._index_notes[newIndex])
20 else:
21 raise TypeError("Invalid argument type")
22
23 def __sub__(self, other):
24 if isinstance(other, Tone):
25 semitones = (self.index - other.index) % 12
26 if semitones < 0:
27 semitones += 12
28 return Interval(semitones)
29 elif isinstance(other, Interval):
30 new_index = (self.index - other.semitones) % 12
31 return Tone(self._index_notes[new_index])
32 else:
33 raise TypeError("Invalid argument type")
34
35 def __eq__(self, other):
36 if isinstance(other, Tone):
37 return self.index == other.index
38 return False
39
40
41class Interval:
42 _intervals = { 0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd", 4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th", 7: "perfect 5th", 8: "minor 6th", 9: "major 6th", 10: "minor 7th", 11: "major 7th"}
43
44 def __init__(self, semitones):
45 self.semitones = semitones % 12
46
47 def __str__(self):
48 return self._intervals[self.semitones]
49
50 def __add__(self, other):
51 if isinstance(other, Interval):
52 newSemitone = (self.semitones + other.semitones) % 12
53 return Interval(newSemitone)
54 elif isinstance(other, Tone):
55 raise TypeError("Invalid operation")
56 else:
57 raise TypeError("Invalid argument type")
58
59 def __sub__(self, other):
60 if isinstance(other, Tone):
61 raise TypeError("Invalid operation")
62
63 def __neg__(self):
64 return Interval(-self.semitones)
65
66
67class Chord:
68 def __init__(self, root, *tones):
69 unique_tones = []
70 for tone in tones:
71 if tone not in unique_tones and tone != root:
72 unique_tones.append(tone)
73 if len(unique_tones) < 1:
74 raise TypeError("Cannot have a chord made of only 1 unique tone")
75 self.root = root
76 self.chords = sorted(unique_tones, key=lambda tone:(tone.index - self.root.index) % 12)
77
78 def __str__(self):
79 return str(self.root) + "-" + "-".join(str(tone) for tone in self.chords)
80
81 def is_minor(self):
82 intervals = [abs(self.root.index - tone.index) % 12 for tone in self.chords]
83 return 3 in intervals
84
85 def is_major(self):
86 intervals = [abs(self.root.index - tone.index) % 12 for tone in self.chords]
87 return 4 in intervals
88
89 def is_power_chord(self):
90 return not self.is_minor() and not self.is_major()
91
92
93 def __add__(self, other):
94 if isinstance(other, Tone):
95 return Chord(self.root, *(self.chords + [other]))
96 elif isinstance(other, Chord):
97 return Chord(self.root, *(self.chords + [other.root] + other.chords))
98 elif isinstance(other, Interval):
99 transposed_tones = [tone + other for tone in [self.root] + self.chords]
100 return Chord(*transposed_tones)
101 else:
102 raise TypeError("Invalid argument type")
103
104 def __sub__(self, other):
105 if isinstance(other, Tone):
106 if other not in self.chords:
107 raise ValueError(f"Cannot remove tone {str(other)} from chord {str(self)}")
108 if len(self.chords) + 1 < 3:
109 raise TypeError("Cannot have a chord made of only 1 unique tone")
110 if other not in self.chords:
111 raise TypeError(f"Cannot remove tone {str(other)} from chord {str(self)}")
112 if other == self.root:
113 self.root = self.chords[0]
114 new_chords = [tone for tone in self.chords if tone != other]
115 return Chord(self.root, *new_chords)
116 if isinstance(other, Interval):
117 return Chord(self + -other)
118 else:
119 raise TypeError("Invalid argument type")
120
121 def transposed(self, interval):
122 if isinstance(interval, Interval):
123 transposed_tones = [tone + interval for tone in self.chords]
124 return Chord(self.root + interval, *transposed_tones)
125 else:
126 raise TypeError("Invalid argument type")
127
128
129# =========================================================================================================================
130# =========================================================================================================================
131# ---------------------------------------------... ТЕСТВАНЕ ...------------------------------------------------------------
132# =========================================================================================================================
133# =========================================================================================================================
....FFF........................E.....
======================================================================
ERROR: test_subtract_tone_from_chord_error (test.TestOperations.test_subtract_tone_from_chord_error)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 292, in test_subtract_tone_from_chord_error
c5_chord - a
~~~~~~~~~^~~
File "/tmp/solution.py", line 107, in __sub__
raise ValueError(f"Cannot remove tone {str(other)} from chord {str(self)}")
ValueError: Cannot remove tone A from chord C-G
======================================================================
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
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=3, errors=1)
07.11.2024 10:54
07.11.2024 10:55
07.11.2024 10:56
07.11.2024 10:56
07.11.2024 10:57
07.11.2024 10:58
07.11.2024 10:59
07.11.2024 10:58