1class Tone:
2 tones = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
3
4 def __init__(self, tone):
5 if not tone in self.tones:
6 raise TypeError("Invalid tone type.")
7 self.tone = tone
8
9 def __str__(self):
10 return self.tone
11
12 def __eq__(self, other):
13 if isinstance(other, Tone):
14 return self.tone == other.tone
15 return False
16
17 def __hash__(self):
18 return hash(self.tone)
19
20 def __add__(self, other):
21 if isinstance(other, Interval):
22 return Tone(Tone.tones[(Tone.tones.index(self.tone) + other.interval) % len(Tone.tones)])
23
24 if isinstance(other, Tone):
25 return Chord(self, other)
26
27 raise TypeError("Invalid operation.")
28
29
30 def __sub__(self, other):
31 if isinstance(other, Interval):
32 return Tone(Tone.tones[(Tone.tones.index(self.tone) - other.interval) % len(Tone.tones)])
33
34 if isinstance(other, Tone):
35 return Interval(abs(Tone.tones.index(self.tone) - Tone.tones.index(other.tone)))
36
37 raise TypeError("Invalid operation.")
38
39
40
41class Interval:
42 intervals = ("unison", "minor 2nd", "major 2nd",
43 "minor 3rd", "major 3rd", "perfect 4th",
44 "diminished 5th", "perfect 5th", "minor 6th",
45 "major 6th", "minor 7th", "major 7th")
46
47 def __init__(self, number_of_semitones):
48 self.interval = number_of_semitones % len(Interval.intervals)
49
50 def __str__(self):
51 return self.intervals[self.interval]
52
53 def __add__(self, other):
54 if isinstance(other, Interval):
55 return Interval(self.interval + other.interval)
56
57 raise TypeError("Invald operation")
58
59 def __sub__(self, other):
60 raise TypeError("Invald operation")
61
62 def __neg__(self):
63 return Interval(12 - self.interval)
64
65
66class Chord:
67 def __init__(self, first_tone, *other_tones):
68 tones = set(other_tones)
69 tones.add(first_tone)
70
71 if len(tones) == 1:
72 raise TypeError("Cannot have a chord made of only 1 unique tone")
73
74 self.tones = list()
75 for tone in Tone.tones:
76 curr_tone = Tone(tone)
77 if curr_tone in tones and curr_tone not in self.tones:
78 self.tones.append(Tone(tone))
79
80 for idx, tone in enumerate(self.tones):
81 if tone == first_tone:
82 new_tones = self.tones[idx:]
83 new_tones.extend(self.tones[:idx])
84 self.tones = new_tones
85 break
86
87 self.root = first_tone
88
89 def __str__(self):
90 return "-".join(str(tone) for tone in self.tones)
91
92 def _is_type(self, chord_type):
93 for tone in self.tones:
94 if tone == self.root:
95 continue
96
97 if str(tone - self.root) == chord_type:
98 return True
99
100 return False
101
102 def is_minor(self):
103 return self._is_type("minor 3rd")
104
105 def is_major(self):
106 return self._is_type("major 3rd")
107
108 def is_power_chord(self):
109 return not self._is_type("minor 3rd") and not self._is_type("major 3rd")
110
111 def __add__(self, other):
112 if isinstance(other, Tone):
113 return Chord(self.root, other, *self.tones)
114
115 if isinstance(other, Chord):
116 new_tones = self.tones[::]
117 new_tones.extend(other.tones)
118 return Chord(self.root, *new_tones)
119
120 raise TypeError("Invald operation")
121
122
123 def __sub__(self, other):
124 if isinstance(other, Tone):
125 if other not in self.tones:
126 raise TypeError(f"Cannot remove tone {other} from chord {self}")
127
128 new_tones = self.tones[::]
129 new_tones.remove(other)
130 return Chord(new_tones[0], *new_tones)
131
132 raise TypeError("Invald operation")
133
134 def transposed(self, interval):
135 if isinstance(interval, Interval):
136 new_tones = self.tones[::]
137 new_tones = list(map(lambda tone: tone + interval, new_tones))
138 return Chord(new_tones[0], *new_tones)
139
140 raise TypeError("Invald operation")
....FFF...........F........F.......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_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 211, in test_add_interval_to_tone_left_side_error
self.assertEqual(str(err.exception), INVALID_OPERATION)
AssertionError: 'Invald operation' != 'Invalid operation'
- Invald operation
+ Invalid operation
? +
======================================================================
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: 'Invald operation' != 'Invalid operation'
- Invald operation
+ Invalid operation
? +
======================================================================
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.002s
FAILED (failures=6)
06.11.2024 14:44
06.11.2024 14:45
06.11.2024 14:46
06.11.2024 14:47
06.11.2024 14:54
06.11.2024 14:49
06.11.2024 14:55
06.11.2024 14:55
06.11.2024 14:56