1class Tone:
2 VALID_TONES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
3 INVALID_OPERATION_ERROR_MESSAGE = "Invalid operation"
4
5 def __init__(self, tone):
6 if tone not in Tone.VALID_TONES:
7 raise TypeError(self.INVALID_OPERATION_ERROR_MESSAGE)
8 self.tone = tone
9
10 def __str__(self):
11 return self.tone
12
13 def __add__(self, other):
14 if isinstance(other, Tone):
15 return Chord(self, other)
16 elif isinstance(other, Interval):
17 index = (Tone.VALID_TONES.index(self.tone) + other.length) % len(Tone.VALID_TONES)
18 return Tone(Tone.VALID_TONES[index])
19 else:
20 raise TypeError(self.INVALID_OPERATION_ERROR_MESSAGE)
21
22 def __sub__(self, other):
23 if isinstance(other, Tone):
24 index_diff = (Tone.VALID_TONES.index(self.tone) - Tone.VALID_TONES.index(other.tone)) % len(Tone.VALID_TONES)
25 return Interval(index_diff)
26 elif isinstance(other, Interval):
27 index = (Tone.VALID_TONES.index(self.tone) - other.length) % len(Tone.VALID_TONES)
28 return Tone(Tone.VALID_TONES[index])
29 else:
30 raise TypeError(self.INVALID_OPERATION_ERROR_MESSAGE)
31
32
33class Interval:
34 INTERVAL_NAMES = ['unison', 'minor 2nd', 'major 2nd', 'minor 3rd', 'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th', 'minor 6th', 'major 6th', 'minor 7th', 'major 7th']
35
36 def __init__(self, length):
37 self.length = length % 12
38
39 def __str__(self):
40 return Interval.INTERVAL_NAMES[self.length]
41
42 def __add__(self, other):
43 if isinstance(other, Interval):
44 return Interval(self.length + other.length)
45 else:
46 raise TypeError(Tone.INVALID_OPERATION_ERROR_MESSAGE)
47
48
49class Chord:
50 TONES_COUNT_ERROR_MESSAGE = 'Cannot have a chord made of only 1 unique tone'
51
52 def __init__(self, *tones):
53 unique_tones = {tone.tone for tone in tones}
54 if len(unique_tones) < 2:
55 raise TypeError(self.TONES_COUNT_ERROR_MESSAGE)
56 self.tones = sorted(unique_tones, key=lambda x: Tone.VALID_TONES.index(x))
57
58 def __str__(self):
59 return '-'.join(self.tones)
60
61 def is_minor(self):
62 if 'C' in self.tones and 'D#' in self.tones and 'G' in self.tones:
63 return True
64 return False
65
66 def is_major(self):
67 if 'C' in self.tones and 'E' in self.tones and 'G' in self.tones:
68 return True
69 return False
70
71 def is_power_chord(self):
72 if len(self.tones) == 2 and 'C' in self.tones and 'G' in self.tones:
73 return True
74 return False
75
76 def __add__(self, other):
77 if isinstance(other, Tone):
78 if other.tone not in self.tones:
79 self.tones.append(other.tone)
80 return Chord(*[Tone(t) for t in self.tones])
81 elif isinstance(other, Chord):
82 combined_tones = list(set(self.tones + other.tones))
83 return Chord(*[Tone(t) for t in combined_tones])
84 else:
85 raise TypeError(Tone.INVALID_OPERATION_ERROR_MESSAGE)
86
87 def __sub__(self, tone):
88 if not isinstance(tone, Tone):
89 raise TypeError(Tone.INVALID_OPERATION_ERROR_MESSAGE)
90 if tone.tone not in self.tones:
91 raise TypeError(f"Cannot remove tone {tone.tone} from chord {self}")
92 if len(self.tones) <= 2:
93 raise TypeError(self.TONES_COUNT_ERROR_MESSAGE)
94 self.tones.remove(tone.tone)
95 return Chord(*[Tone(t) for t in self.tones])
96
97 def transposed(self, interval):
98 transposed_tones = [Tone(Tone.VALID_TONES[(Tone.VALID_TONES.index(t) + interval.length) % len(Tone.VALID_TONES)]) for t in self.tones]
99 return Chord(*transposed_tones)
.FFFFFF.EEFE.........FFF...F.........
======================================================================
ERROR: test_transposed_negative (test.TestBasicChordFunctionality.test_transposed_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 136, in test_transposed_negative
result_chord = e_minor_chord.transposed(-Interval(2))
^^^^^^^^^^^^
TypeError: bad operand type for unary -: 'Interval'
======================================================================
ERROR: test_transposed_negative_overflow (test.TestBasicChordFunctionality.test_transposed_negative_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 150, in test_transposed_negative_overflow
result_chord = e_minor_chord.transposed(-Interval(8))
^^^^^^^^^^^^
TypeError: bad operand type for unary -: 'Interval'
======================================================================
ERROR: test_interval_negative (test.TestBasicIntervalFunctionality.test_interval_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 50, in test_interval_negative
minor_2nd = -major_7th
^^^^^^^^^^
TypeError: bad operand type for unary -: 'Interval'
======================================================================
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_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 124, in test_is_power_chord
self.assertTrue(a_kind_of_power_chord.is_power_chord())
AssertionError: False is not true
======================================================================
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
----------------------------------------------------------------------
Ran 37 tests in 0.003s
FAILED (failures=11, errors=3)
07.11.2024 11:46
07.11.2024 11:46
07.11.2024 11:47
07.11.2024 11:48