1import copy
2
3tones_and_intervals = {
4 0: ("C", "unison"),
5 1: ("C#", "minor 2nd"),
6 2: ("D", "major 2nd"),
7 3: ("D#", "minor 3rd"),
8 4: ("E", "major 3rd"),
9 5: ("F", "perfect 4th"),
10 6: ("F#", "diminished 5th"),
11 7: ("G", "perfect 5th"),
12 8: ("G#", "minor 6th"),
13 9: ("A", "major 6th"),
14 10: ("A#", "minor 7th"),
15 11: ("B", "major 7th")
16 }
17
18def get_tone(number):
19 return tones_and_intervals[number % 12][0]
20
21def get_numeric_value(tone_or_interval):
22 for key, (tone, interval) in tones_and_intervals.items():
23 if str(tone_or_interval) == tone or str(tone_or_interval) == interval:
24 return key
25
26class Tone:
27 def __init__(self, name):
28 self.name = name
29
30 def __str__(self):
31 return self.name
32
33 def __add__(self, other):
34 if isinstance(other, Tone):
35 return Chord(self, other)
36 elif isinstance(other, Interval):
37 return Tone(get_tone((get_numeric_value(self) + other.value) % 12))
38
39 def __sub__(self, other):
40 if isinstance(other, Tone):
41 return Interval(abs(get_numeric_value(self) - get_numeric_value(other)))
42 elif isinstance(other, Interval):
43 return Tone(get_tone((get_numeric_value(self) - other.value + 12) % 12))
44
45
46class Interval:
47 def __init__(self, value):
48 self.value = value % 12
49
50 def __str__(self):
51 return tones_and_intervals[abs(self.value)][1]
52
53 def __neg__(self):
54 return Interval(-self.value)
55
56 def __add__(self, other):
57 if isinstance(other, Tone):
58 raise TypeError("Invalid operation")
59
60 if isinstance(other, Interval):
61 return Interval((self.value + other.value) % 12)
62
63 def __sub__(self, other):
64 if isinstance(other, Tone):
65 raise TypeError("Invalid operation")
66
67
68class Chord:
69 def __init__(self, root, *args):
70 unique_tones = {str(root)}
71 unique_tones.update(str(arg) for arg in args)
72
73 if len(unique_tones) <= 1:
74 raise TypeError("Cannot have a chord made of only 1 unique tone")
75
76 ordered_tones = list(unique_tones)
77 ordered_tones = [get_numeric_value(name_of_tone) for name_of_tone in ordered_tones]
78 ordered_tones.sort()
79
80 index_of_root = ordered_tones.index(get_numeric_value(root))
81
82 self.tones = ordered_tones[index_of_root:] + ordered_tones[:index_of_root]
83 self.tones = [Tone(get_tone(tone)) for tone in self.tones]
84
85 def __str__(self):
86 result = "-".join(str(tone) for tone in self.tones)
87 return result
88
89 def __add__(self, other):
90 if isinstance(other, Tone):
91 temp_tones = copy.deepcopy(self.tones)
92 temp_tones.append(other)
93 return Chord(temp_tones[0], *temp_tones)
94
95 if isinstance(other, Chord):
96 temp_tones = copy.deepcopy(self.tones)
97 temp_tones_other = copy.deepcopy(other.tones)
98 temp_tones += temp_tones_other
99 return Chord(temp_tones[0], *temp_tones)
100
101 def __sub__(self, other):
102 if isinstance(other, Tone):
103 temp_tones = copy.deepcopy(self.tones)
104 for tone in temp_tones:
105 if get_numeric_value(tone) == get_numeric_value(other):
106 temp_tones.remove(tone)
107 return Chord(temp_tones[0], *temp_tones)
108 raise TypeError(f"Cannot remove tone {other} from chord {self}")
109
110 def is_minor(self):
111 root_numeric_value = get_numeric_value(self.tones[0])
112 for tone in self.tones[1:]:
113 if abs(root_numeric_value - get_numeric_value(tone)) == 3:
114 return True
115 return False
116
117 def is_major(self):
118 root_numeric_value = get_numeric_value(self.tones[0])
119 for tone in self.tones[1:]:
120 if abs(root_numeric_value - get_numeric_value(tone)) == 4:
121 return True
122 return False
123
124 def is_power_chord(self):
125 if not (self.is_minor() or self.is_major()):
126 return True
127 return False
128
129 def transposed(self, other):
130 if isinstance(other, Interval):
131 temp_tones = copy.deepcopy(self.tones)
132 temp_tones = [Tone(get_tone(get_numeric_value(tone) + other.value)) for tone in temp_tones]
133 return Chord(temp_tones[0], *temp_tones)
....FFF............................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_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=4)
07.11.2024 09:14
07.11.2024 09:14
07.11.2024 09:14
07.11.2024 09:15
07.11.2024 09:16
07.11.2024 09:18