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