1class Tone:
2 tones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
3
4 def __init__(self, name):
5 self.name = name
6
7 def __str__(self):
8 return self.name
9
10 def __add__(self, other):
11 if isinstance(other, Tone):
12 return Chord(self, other)
13 elif isinstance(other, Interval):
14 curr_index = (Tone.tones.index(self.name) + other.semitones) % 12
15 return Tone(Tone.tones[curr_index])
16
17 def __sub__(self, other):
18 if isinstance(other, Tone):
19 distance = (Tone.tones.index(self.name) - Tone.tones.index(other.name)) % 12
20 return Interval(distance)
21
22 elif isinstance(other, Interval):
23 curr_index = (Tone.tones.index(self.name) - other.semitones) % 12
24 return Tone(Tone.tones[curr_index])
25
26
27class Interval:
28 INTERVAL_NAMES = {
29 0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd",
30 4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th",
31 7: "perfect 5th", 8: "minor 6th", 9: "major 6th",
32 10: "minor 7th", 11: "major 7th"
33 }
34
35 def __init__(self, semitones):
36 self.semitones = semitones % 12
37
38 def __str__(self):
39 return self.INTERVAL_NAMES[self.semitones]
40
41 def __add__(self, other):
42 if isinstance(other, Interval):
43 new_semitones = (self.semitones + other.semitones) % 12
44 return Interval(new_semitones)
45 else:
46 raise TypeError("Invalid operation")
47
48
49class Chord:
50 def __init__(self, root_tone, *tones):
51 unique_tones = {root_tone, *tones}
52
53 if len(unique_tones) < 2:
54 raise TypeError("Cannot have a chord made of only 1 unique tone")
55
56 self.root_tone = root_tone
57 self.tones = unique_tones
58
59 start_index = Tone.tones.index(root_tone.name)
60 sorted_tones = sorted(unique_tones, key=lambda t: (Tone.tones * 2).index(t.name, start_index))
61
62 self.tones = [Tone(t.name) for t in sorted_tones]
63
64 def __str__(self):
65 return "-".join(str(tone) for tone in self.tones)
66
67 #Find distance between two tones
68 def semitone_distance(self, tone1, tone2):
69 return (Tone.tones.index(tone2.name) - Tone.tones.index(tone1.name)) % 12
70
71 def is_minor(self):
72 for tone in self.tones:
73 if tone != self.root_tone:
74 distance = self.semitone_distance(self.root_tone, tone)
75 if Interval (distance).__str__() == "minor 3rd":
76 return True
77 return False
78
79 def is_major(self):
80 for tone in self.tones:
81 if tone != self.root_tone:
82 distance = self.semitone_distance(self.root_tone, tone)
83 if Interval(distance).__str__() == "major 3rd":
84 return True
85 return False
86
87 def is_power_chord(self):
88 if not self.is_major and not self.is_minor:
89 return True
90 return False
91
92 def __sub__(self, other):
93 if isinstance(other, Tone):
94 if other.name not in [tone.name for tone in self.tones]:
95 raise TypeError(f"Cannot remove tone {other} from chord {self}")
96
97 new_tones = [tone for tone in self.tones if tone.name != other.name]
98
99 if len(new_tones) == 1:
100 raise TypeError("Cannot have a chord made of only 1 unique tone")
101
102 new_root_tone = new_tones[0]
103
104 return Chord(new_root_tone, *new_tones)
105
106 def __add__(self, other):
107 if isinstance(other, Chord):
108 combined_tones = list(self.tones)
109 for tone in other.tones:
110 if tone not in combined_tones:
111 combined_tones.append(tone)
112 new_root_tone = combined_tones[0]
113 unique_tones = []
114 for tone in combined_tones:
115 if tone not in unique_tones:
116 unique_tones.append(tone)
117 return Chord(new_root_tone, *unique_tones)
118 elif isinstance(other, Tone):
119 combined_tones = list(self.tones)
120 if other not in combined_tones:
121 combined_tones.append(other)
122 new_root_tone = combined_tones[0]
123 return Chord(new_root_tone, *combined_tones)
124
125 def transposed(self, interval):
126 if not isinstance(interval, Interval):
127 raise TypeError("Interval is expected")
128
129 transposed_tones = [
130 Tone(Tone.tones[(Tone.tones.index(tone.name) + interval.semitones) % 12])
131 for tone in self.tones
132 ]
133 return Chord(transposed_tones[0], *transposed_tones[1:])
F..F..F.EE.E....F.....F....F.....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_not_enough_tones (test.TestBasicChordFunctionality.test_chord_not_enough_tones)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 83, in test_chord_not_enough_tones
with self.assertRaises(TypeError) as err:
AssertionError: TypeError not raised
======================================================================
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: 'A-A-A-A-A-A-A-C-C-C-C-C-C-C-C-C-C-C-C-C-C-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E' != 'A-C-E'
- A-A-A-A-A-A-A-C-C-C-C-C-C-C-C-C-C-C-C-C-C-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E
+ A-C-E
======================================================================
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_add_chords_repeating_notes (test.TestOperations.test_add_chords_repeating_notes)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 305, in test_add_chords_repeating_notes
self.assertEqual(str(result_chord), "C-G")
AssertionError: 'C-C-G-G' != 'C-G'
- C-C-G-G
+ C-G
======================================================================
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: 'F-F-G#-G#-C-C' != 'F-G#-C'
- F-F-G#-G#-C-C
+ F-G#-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
======================================================================
FAIL: test_tone_addition_same_tone (test.TestOperations.test_tone_addition_same_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 166, in test_tone_addition_same_tone
with self.assertRaises(TypeError) as err:
AssertionError: TypeError not raised
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=7, errors=3)
f | 1 | class Tone: | f | 1 | class Tone: |
2 | tones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | 2 | tones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | ||
3 | 3 | ||||
4 | def __init__(self, name): | 4 | def __init__(self, name): | ||
5 | self.name = name | 5 | self.name = name | ||
6 | 6 | ||||
7 | def __str__(self): | 7 | def __str__(self): | ||
8 | return self.name | 8 | return self.name | ||
9 | 9 | ||||
10 | def __add__(self, other): | 10 | def __add__(self, other): | ||
11 | if isinstance(other, Tone): | 11 | if isinstance(other, Tone): | ||
12 | return Chord(self, other) | 12 | return Chord(self, other) | ||
13 | elif isinstance(other, Interval): | 13 | elif isinstance(other, Interval): | ||
14 | curr_index = (Tone.tones.index(self.name) + other.semitones) % 12 | 14 | curr_index = (Tone.tones.index(self.name) + other.semitones) % 12 | ||
15 | return Tone(Tone.tones[curr_index]) | 15 | return Tone(Tone.tones[curr_index]) | ||
16 | 16 | ||||
17 | def __sub__(self, other): | 17 | def __sub__(self, other): | ||
18 | if isinstance(other, Tone): | 18 | if isinstance(other, Tone): | ||
19 | distance = (Tone.tones.index(self.name) - Tone.tones.index(other.name)) % 12 | 19 | distance = (Tone.tones.index(self.name) - Tone.tones.index(other.name)) % 12 | ||
20 | return Interval(distance) | 20 | return Interval(distance) | ||
21 | 21 | ||||
22 | elif isinstance(other, Interval): | 22 | elif isinstance(other, Interval): | ||
23 | curr_index = (Tone.tones.index(self.name) - other.semitones) % 12 | 23 | curr_index = (Tone.tones.index(self.name) - other.semitones) % 12 | ||
24 | return Tone(Tone.tones[curr_index]) | 24 | return Tone(Tone.tones[curr_index]) | ||
25 | 25 | ||||
26 | 26 | ||||
27 | class Interval: | 27 | class Interval: | ||
28 | INTERVAL_NAMES = { | 28 | INTERVAL_NAMES = { | ||
29 | 0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd", | 29 | 0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd", | ||
30 | 4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th", | 30 | 4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th", | ||
31 | 7: "perfect 5th", 8: "minor 6th", 9: "major 6th", | 31 | 7: "perfect 5th", 8: "minor 6th", 9: "major 6th", | ||
32 | 10: "minor 7th", 11: "major 7th" | 32 | 10: "minor 7th", 11: "major 7th" | ||
33 | } | 33 | } | ||
34 | 34 | ||||
35 | def __init__(self, semitones): | 35 | def __init__(self, semitones): | ||
36 | self.semitones = semitones % 12 | 36 | self.semitones = semitones % 12 | ||
37 | 37 | ||||
38 | def __str__(self): | 38 | def __str__(self): | ||
39 | return self.INTERVAL_NAMES[self.semitones] | 39 | return self.INTERVAL_NAMES[self.semitones] | ||
40 | 40 | ||||
41 | def __add__(self, other): | 41 | def __add__(self, other): | ||
42 | if isinstance(other, Interval): | 42 | if isinstance(other, Interval): | ||
43 | new_semitones = (self.semitones + other.semitones) % 12 | 43 | new_semitones = (self.semitones + other.semitones) % 12 | ||
44 | return Interval(new_semitones) | 44 | return Interval(new_semitones) | ||
45 | else: | 45 | else: | ||
46 | raise TypeError("Invalid operation") | 46 | raise TypeError("Invalid operation") | ||
n | 47 | n | 47 | ||
48 | |||||
48 | class Chord: | 49 | class Chord: | ||
49 | def __init__(self, root_tone, *tones): | 50 | def __init__(self, root_tone, *tones): | ||
50 | unique_tones = {root_tone, *tones} | 51 | unique_tones = {root_tone, *tones} | ||
51 | 52 | ||||
52 | if len(unique_tones) < 2: | 53 | if len(unique_tones) < 2: | ||
53 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 54 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
54 | 55 | ||||
55 | self.root_tone = root_tone | 56 | self.root_tone = root_tone | ||
56 | self.tones = unique_tones | 57 | self.tones = unique_tones | ||
57 | 58 | ||||
58 | start_index = Tone.tones.index(root_tone.name) | 59 | start_index = Tone.tones.index(root_tone.name) | ||
59 | sorted_tones = sorted(unique_tones, key=lambda t: (Tone.tones * 2).index(t.name, start_index)) | 60 | sorted_tones = sorted(unique_tones, key=lambda t: (Tone.tones * 2).index(t.name, start_index)) | ||
60 | 61 | ||||
61 | self.tones = [Tone(t.name) for t in sorted_tones] | 62 | self.tones = [Tone(t.name) for t in sorted_tones] | ||
62 | 63 | ||||
63 | def __str__(self): | 64 | def __str__(self): | ||
64 | return "-".join(str(tone) for tone in self.tones) | 65 | return "-".join(str(tone) for tone in self.tones) | ||
65 | 66 | ||||
66 | #Find distance between two tones | 67 | #Find distance between two tones | ||
67 | def semitone_distance(self, tone1, tone2): | 68 | def semitone_distance(self, tone1, tone2): | ||
68 | return (Tone.tones.index(tone2.name) - Tone.tones.index(tone1.name)) % 12 | 69 | return (Tone.tones.index(tone2.name) - Tone.tones.index(tone1.name)) % 12 | ||
69 | 70 | ||||
70 | def is_minor(self): | 71 | def is_minor(self): | ||
71 | for tone in self.tones: | 72 | for tone in self.tones: | ||
72 | if tone != self.root_tone: | 73 | if tone != self.root_tone: | ||
73 | distance = self.semitone_distance(self.root_tone, tone) | 74 | distance = self.semitone_distance(self.root_tone, tone) | ||
74 | if Interval (distance).__str__() == "minor 3rd": | 75 | if Interval (distance).__str__() == "minor 3rd": | ||
75 | return True | 76 | return True | ||
76 | return False | 77 | return False | ||
77 | 78 | ||||
78 | def is_major(self): | 79 | def is_major(self): | ||
79 | for tone in self.tones: | 80 | for tone in self.tones: | ||
80 | if tone != self.root_tone: | 81 | if tone != self.root_tone: | ||
81 | distance = self.semitone_distance(self.root_tone, tone) | 82 | distance = self.semitone_distance(self.root_tone, tone) | ||
82 | if Interval(distance).__str__() == "major 3rd": | 83 | if Interval(distance).__str__() == "major 3rd": | ||
83 | return True | 84 | return True | ||
84 | return False | 85 | return False | ||
85 | 86 | ||||
86 | def is_power_chord(self): | 87 | def is_power_chord(self): | ||
87 | if not self.is_major and not self.is_minor: | 88 | if not self.is_major and not self.is_minor: | ||
88 | return True | 89 | return True | ||
89 | return False | 90 | return False | ||
90 | 91 | ||||
91 | def __sub__(self, other): | 92 | def __sub__(self, other): | ||
92 | if isinstance(other, Tone): | 93 | if isinstance(other, Tone): | ||
n | 93 | # if other not in self.tones: | n | 94 | if other.name not in [tone.name for tone in self.tones]: |
94 | # raise TypeError(f"Cannot remove tone {other} from chord {self}") | 95 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||
95 | |||||
96 | # Проверка дали тонът съществува в акорда | ||||
97 | if other.name not in [tone.name for tone in self.tones]: | ||||
98 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||||
99 | 96 | ||||
n | 100 | n | |||
101 | new_tones = [tone for tone in self.tones if tone.name != other.name] | 97 | new_tones = [tone for tone in self.tones if tone.name != other.name] | ||
102 | 98 | ||||
103 | if len(new_tones) == 1: | 99 | if len(new_tones) == 1: | ||
104 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 100 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
105 | 101 | ||||
106 | new_root_tone = new_tones[0] | 102 | new_root_tone = new_tones[0] | ||
107 | 103 | ||||
108 | return Chord(new_root_tone, *new_tones) | 104 | return Chord(new_root_tone, *new_tones) | ||
109 | 105 | ||||
110 | def __add__(self, other): | 106 | def __add__(self, other): | ||
111 | if isinstance(other, Chord): | 107 | if isinstance(other, Chord): | ||
112 | combined_tones = list(self.tones) | 108 | combined_tones = list(self.tones) | ||
113 | for tone in other.tones: | 109 | for tone in other.tones: | ||
114 | if tone not in combined_tones: | 110 | if tone not in combined_tones: | ||
115 | combined_tones.append(tone) | 111 | combined_tones.append(tone) | ||
116 | new_root_tone = combined_tones[0] | 112 | new_root_tone = combined_tones[0] | ||
117 | unique_tones = [] | 113 | unique_tones = [] | ||
118 | for tone in combined_tones: | 114 | for tone in combined_tones: | ||
119 | if tone not in unique_tones: | 115 | if tone not in unique_tones: | ||
120 | unique_tones.append(tone) | 116 | unique_tones.append(tone) | ||
121 | return Chord(new_root_tone, *unique_tones) | 117 | return Chord(new_root_tone, *unique_tones) | ||
122 | elif isinstance(other, Tone): | 118 | elif isinstance(other, Tone): | ||
123 | combined_tones = list(self.tones) | 119 | combined_tones = list(self.tones) | ||
124 | if other not in combined_tones: | 120 | if other not in combined_tones: | ||
125 | combined_tones.append(other) | 121 | combined_tones.append(other) | ||
126 | new_root_tone = combined_tones[0] | 122 | new_root_tone = combined_tones[0] | ||
127 | return Chord(new_root_tone, *combined_tones) | 123 | return Chord(new_root_tone, *combined_tones) | ||
128 | 124 | ||||
129 | def transposed(self, interval): | 125 | def transposed(self, interval): | ||
130 | if not isinstance(interval, Interval): | 126 | if not isinstance(interval, Interval): | ||
131 | raise TypeError("Interval is expected") | 127 | raise TypeError("Interval is expected") | ||
132 | 128 | ||||
133 | transposed_tones = [ | 129 | transposed_tones = [ | ||
134 | Tone(Tone.tones[(Tone.tones.index(tone.name) + interval.semitones) % 12]) | 130 | Tone(Tone.tones[(Tone.tones.index(tone.name) + interval.semitones) % 12]) | ||
135 | for tone in self.tones | 131 | for tone in self.tones | ||
136 | ] | 132 | ] | ||
137 | return Chord(transposed_tones[0], *transposed_tones[1:]) | 133 | return Chord(transposed_tones[0], *transposed_tones[1:]) | ||
t | 138 | t | |||
139 | |||||
140 | # c_minor_chord = Chord(Tone("C"), Tone("D#"), Tone("G")) | ||||
141 | # print(str(c_minor_chord)) # "C-D#-G" | ||||
142 | |||||
143 | # d_minor_chord = c_minor_chord.transposed(Interval(2)) | ||||
144 | # print(str(d_minor_chord)) # "D-F-A" | ||||
145 | |||||
146 | # a_sharp_minor_chord = d_minor_chord.transposed(Interval(-4)) | ||||
147 | # print(str(a_sharp_minor_chord)) # "A#-C#-F" | ||||
148 | |||||
149 | # c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G")) | ||||
150 | # result_chord = c_major_chord - Tone("E") | ||||
151 | # print(result_chord) | ||||
152 | |||||
153 | # c_power_chord = Chord(Tone("C"), Tone("G")) | ||||
154 | # result_chord = c_power_chord - Tone("G") | ||||
155 | |||||
156 | # c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G")) | ||||
157 | # result_chord = c_major_chord - Tone("E") | ||||
158 | # print(result_chord) | ||||
159 | c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G")) | ||||
160 | result_chord = c_major_chord - Tone("E") | ||||
161 | print(result_chord) | ||||
162 | |||||
163 | |||||
164 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
07.11.2024 12:05
07.11.2024 12:05
07.11.2024 12:06
07.11.2024 12:07
07.11.2024 12:10
07.11.2024 12:09
07.11.2024 12:11
07.11.2024 12:11
07.11.2024 12:12
07.11.2024 12:13