1NUM_OF_TONES = 12
2
3
4class Tone:
5 def __init__(self, tone):
6 self.valid_tones = ['C', 'C#', 'D', 'D#', 'E', 'F',
7 'F#', 'G', 'G#', 'A', 'A#', 'B']
8 self.tone = tone
9
10 def __str__(self):
11 return f'{self.tone}'
12
13 def __add__(self, other):
14 if isinstance(other, Tone):
15 return Chord(self, other)
16 elif isinstance(other, Interval):
17 tone_index = self.valid_tones.index(str(self))
18 interval_index = other.valid_intervals.index(str(other))
19 new_tone = (tone_index + interval_index) % NUM_OF_TONES
20 return Tone(self.valid_tones[new_tone])
21
22 def __sub__(self, other):
23 if isinstance(other, Tone):
24 start_index = self.valid_tones.index(str(other))
25 end_index = self.valid_tones.index(str(self))
26 new_interval = end_index - start_index
27 return Interval(new_interval)
28 elif isinstance(other, Interval):
29 tone_index = self.valid_tones.index(str(self))
30 interval_index = other.valid_intervals.index(str(other))
31 new_tone = tone_index - interval_index
32 return Tone(self.valid_tones[new_tone])
33
34
35class Interval:
36 def __init__(self, interval):
37 self.valid_intervals = ['unison', 'minor 2nd', 'major 2nd', 'minor 3rd',
38 'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th',
39 'minor 6th', 'major 6th', 'minor 7th', 'major 7th']
40
41 all_intervals = interval % NUM_OF_TONES
42 self.interval = self.valid_intervals[all_intervals]
43
44 def __str__(self):
45 return f'{self.interval}'
46
47 def __add__(self, other):
48 if isinstance(other, Interval):
49 first_index = self.valid_intervals.index(str(self))
50 second_index = self.valid_intervals.index(str(other))
51 new_interval = first_index + second_index
52 return Interval(new_interval)
53 else:
54 raise TypeError('Invalid operation')
55
56 def __neg__(self):
57 current_index = self.valid_intervals.index(str(self))
58 new_index = (NUM_OF_TONES - current_index) % NUM_OF_TONES
59 return Interval(new_index)
60
61
62class Chord(Tone, Interval):
63 def __init__(self, root, *args):
64 super().__init__(args)
65 unique_tones = set()
66 unique_tones.add(str(root))
67 for tone in args:
68 unique_tones.add(str(tone))
69 if len(unique_tones) < 2:
70 raise TypeError('Cannot have a chord made of only 1 unique tone')
71 root_index = self.valid_tones.index(str(root))
72 new_order = []
73 for i in range(0, NUM_OF_TONES):
74 new_order.append(self.valid_tones[(root_index + i) % NUM_OF_TONES])
75 self.tones = sorted(unique_tones, key=lambda tone: new_order.index(str(tone)))
76
77 def __str__(self):
78 return f'{'-'.join(str(tone) for tone in self.tones)}'
79
80 def is_minor(self):
81 root_index = self.valid_tones.index(str(self.tones[0]))
82 minor_third = self.valid_tones[root_index + 3]
83 for tone in self.tones:
84 if str(tone) == minor_third:
85 return True
86 return False
87
88 def is_major(self):
89 root_index = self.valid_tones.index(str(self.tones[0]))
90 major_third = self.valid_tones[root_index + 4]
91 for tone in self.tones:
92 if str(tone) == major_third:
93 return True
94 return False
95
96 def is_power_chord(self):
97 if self.is_minor() or self.is_major():
98 return False
99 return True
100
101 def __add__(self, other):
102 if isinstance(other, Chord):
103 tones = other.tones
104 else:
105 tones = other.tone
106 new_chord = self.tones
107 for tone in tones:
108 if str(tone) not in new_chord:
109 new_chord.append(tone)
110 return Chord(*new_chord)
111
112 def __sub__(self, other):
113 if isinstance(other, Tone):
114 if str(other) in self.tones:
115 new_chord = []
116 for tone in self.tones:
117 if str(tone) != str(other):
118 new_chord.append(tone)
119 return Chord(*new_chord)
120 else:
121 raise TypeError(f'Cannot remove tone {other} from chord {self}')
122
123 def transposed(self, other):
124 new_chord = []
125 for tone in self.tones:
126 other_interval = other.valid_intervals.index(str(other))
127 tone_index = self.valid_tones.index(str(tone)) + other_interval
128 tone_index %= NUM_OF_TONES
129 new_chord.append(self.valid_tones[tone_index])
130 return Chord(*new_chord)
....EEE...............E....F.........
======================================================================
ERROR: test_is_major (test.TestBasicChordFunctionality.test_is_major)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 103, in test_is_major
self.assertFalse(a_minor_chord.is_major())
^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 90, in is_major
major_third = self.valid_tones[root_index + 4]
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
IndexError: list index out of range
======================================================================
ERROR: 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())
^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 82, in is_minor
minor_third = self.valid_tones[root_index + 3]
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
IndexError: list index out of range
======================================================================
ERROR: 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())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 97, in is_power_chord
if self.is_minor() or self.is_major():
^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 82, in is_minor
minor_third = self.valid_tones[root_index + 3]
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
IndexError: list index out of range
======================================================================
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 110, in __add__
return Chord(*new_chord)
^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 75, in __init__
self.tones = sorted(unique_tones, key=lambda tone: new_order.index(str(tone)))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 75, in <lambda>
self.tones = sorted(unique_tones, key=lambda tone: new_order.index(str(tone)))
^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: '#' is not in list
======================================================================
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
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=1, errors=4)
06.11.2024 14:32
06.11.2024 14:34
06.11.2024 14:37
06.11.2024 14:39
06.11.2024 14:38
06.11.2024 14:44