1NUMBER_OF_SEMITONES_TO_STR = {
2 0: "unison",
3 1: "minor 2nd",
4 2: "major 2nd",
5 3: "minor 3rd",
6 4: "major 3rd",
7 5: "perfect 4th",
8 6: "diminished 5th",
9 7: "perfect 5th",
10 8: "minor 6th",
11 9: "major 6th",
12 10: "minor 7th",
13 11: "major 7th"
14}
15INDEX_TO_TONE_STR = {
16 0: "C",
17 1: "C#",
18 2: "D",
19 3: "D#",
20 4: "E",
21 5: "F",
22 6: "F#",
23 7: "G",
24 8: "G#",
25 9: "A",
26 10: "A#",
27 11: "B",
28}
29TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()}
30TONES_COUNT = 12
31MINOR_THIRD_INDEX = 3
32MAJOR_THIRD_INDEX = 4
33
34
35class Tone:
36
37 def __init__(self, tone_str: str):
38 self.__tone_str = tone_str
39
40 def __str__(self):
41 return self.__tone_str
42
43 def __hash__(self):
44 return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones)
45
46 def __eq__(self, other):
47 if isinstance(other, Tone):
48 return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones)
49
50 def __sub__(self, other):
51 if isinstance(other, Tone):
52 if self.get_tone_index() > other.get_tone_index():
53 return Interval(self.get_tone_index() - other.get_tone_index())
54 return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index())
55 elif isinstance(other, Interval):
56 return self + (-other)
57
58 def __add__(self, other):
59 if isinstance(other, Tone):
60 return Chord(self, other)
61 elif isinstance(other, Interval):
62 index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT
63 return Tone(INDEX_TO_TONE_STR[index])
64
65 @property
66 def tone_str(self):
67 return self.__tone_str
68
69 def get_tone_index(self):
70 return TONE_STR_TO_INDEX[self.__tone_str]
71
72
73class Interval:
74
75 def __init__(self, number_of_semitones: int):
76 self.__number_of_semitones = number_of_semitones % TONES_COUNT
77
78 def __neg__(self):
79 return Interval(-self.__number_of_semitones)
80
81 def __str__(self):
82 return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones]
83
84 def __add__(self, other):
85 if isinstance(other, Interval):
86 return Interval(self.number_of_semitones + other.number_of_semitones)
87 elif isinstance(other, Tone):
88 raise TypeError("Invalid operation")
89
90 def __sub__(self, other):
91 if isinstance(other, Tone):
92 raise TypeError("Invalid operation")
93
94 @property
95 def number_of_semitones(self):
96 return self.__number_of_semitones
97
98
99class Chord:
100 def __init__(self, root: Tone, *args: [Tone]):
101 self.__root = root
102 self.__initialise_tones(args)
103
104 def __str__(self):
105 return "-".join(map(str, [self.__root, *self.__tones]))
106
107 def __sub__(self, other):
108 if not other.get_tone_index() in ([t.get_tone_index() for t in self.__tones] + [self.__root.get_tone_index()]):
109 raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}")
110
111 if other.get_tone_index() == self.__root.get_tone_index():
112 return Chord(self.__tones[0], *self.__tones[1:])
113 return Chord(self.__root, *list(filter(lambda cur: str(cur) != str(other), self.__tones)))
114
115 def __add__(self, other):
116 if isinstance(other, Tone):
117 return Chord(self.__root, *self.__tones, other)
118 elif isinstance(other, Chord):
119 return Chord(self.__root, *self.__tones, other.__root, *other.__tones)
120
121 def __initialise_tones(self, new_tones: [Tone]):
122 new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones)))
123 # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered
124
125 if len(new_tones) == 0:
126 raise TypeError("Cannot have a chord made of only 1 unique tone")
127
128 pivot = self.__root.get_tone_index()
129 ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index()))
130 upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones))
131 lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones))
132 self.__tones = [*upper_bound, *lower_bound]
133
134 def is_minor(self) -> bool:
135 return self.__seek_for_interval(MINOR_THIRD_INDEX)
136
137 def is_major(self) -> bool:
138 return self.__seek_for_interval(MAJOR_THIRD_INDEX)
139
140 def is_power_chord(self) -> bool:
141 return not self.is_major() and not self.is_minor()
142
143 def __seek_for_interval(self, number_of_semitones: int) -> bool:
144 for tone in self.__tones:
145 current_interval = tone - self.__root
146 if current_interval.number_of_semitones == number_of_semitones:
147 return True
148 return False
149
150 def transposed(self, interval: Interval):
151 new_root = self.__root + interval
152 new_tones = [tone + interval for tone in self.__tones]
153 return Chord(new_root, *new_tones)
....F.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_power_chord (test.TestBasicChordFunctionality.test_is_power_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 120, in test_is_power_chord
self.assertFalse(a_major_chord.is_power_chord())
AssertionError: True is not false
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=2)
Георги Кунчев
04.11.2024 14:12В Python обикновено сме по-консервативни от гледна точка на това. Правейки ги protected с една долна черта е достатъчно да "кажеш" на останалите да не го закачат.
Ако пък нещо е публично, то следва класът ти да е написан така, че да е ок със ситуация, в която се сменя извън класа. Твоят клас `Tone` например е напълно валиден, дори някой да бръкне и да смени `__tone_str` отвън. Всичко ще работи. Иначе, ако наистина имаш полета, за които не можеш да гарантираш, че външна промяна няма да доведе до проблем, но искаш да може да се достъпва, подходът с `@property` е валиден и подходящ.
|
Павел Петков
04.11.2024 14:05A гетърите съм ги написал, защото искам да не може да се променят член данните извън класа, а само вътре в него и в същото време да се взимат(ясно е ,че никой не ползва name-mangling)
|
Георги Кунчев
04.11.2024 13:46Ясно. Мога да предложа да направиш така, че pivot да е индекса, имайки предвид, че `ordered_tones` се очаква да са подредени и `ordered_tones.index(pivot)` ще ти даде индекса, по който да направиш същата операция, но не съм сигурен, че това, което очаквам в тези колекции спрямо имената им, е това, което всъщност е там, затова не искам да рискувам да те подведа.
От теб зависи дали ще си поиграеш да го опростиш.
|
Павел Петков
04.11.2024 13:08Проблема е , че pivot = self.__root.get_tone_index() не връща индекс в масива , а каква числова репрезентация има от 0 до 11, съответно това с slice-а няма как да работи
|
f | 1 | NUMBER_OF_SEMITONES_TO_STR = { | f | 1 | NUMBER_OF_SEMITONES_TO_STR = { |
2 | 0: "unison", | 2 | 0: "unison", | ||
3 | 1: "minor 2nd", | 3 | 1: "minor 2nd", | ||
4 | 2: "major 2nd", | 4 | 2: "major 2nd", | ||
5 | 3: "minor 3rd", | 5 | 3: "minor 3rd", | ||
6 | 4: "major 3rd", | 6 | 4: "major 3rd", | ||
7 | 5: "perfect 4th", | 7 | 5: "perfect 4th", | ||
8 | 6: "diminished 5th", | 8 | 6: "diminished 5th", | ||
9 | 7: "perfect 5th", | 9 | 7: "perfect 5th", | ||
10 | 8: "minor 6th", | 10 | 8: "minor 6th", | ||
11 | 9: "major 6th", | 11 | 9: "major 6th", | ||
12 | 10: "minor 7th", | 12 | 10: "minor 7th", | ||
13 | 11: "major 7th" | 13 | 11: "major 7th" | ||
14 | } | 14 | } | ||
15 | INDEX_TO_TONE_STR = { | 15 | INDEX_TO_TONE_STR = { | ||
16 | 0: "C", | 16 | 0: "C", | ||
17 | 1: "C#", | 17 | 1: "C#", | ||
18 | 2: "D", | 18 | 2: "D", | ||
19 | 3: "D#", | 19 | 3: "D#", | ||
20 | 4: "E", | 20 | 4: "E", | ||
21 | 5: "F", | 21 | 5: "F", | ||
22 | 6: "F#", | 22 | 6: "F#", | ||
23 | 7: "G", | 23 | 7: "G", | ||
24 | 8: "G#", | 24 | 8: "G#", | ||
25 | 9: "A", | 25 | 9: "A", | ||
26 | 10: "A#", | 26 | 10: "A#", | ||
27 | 11: "B", | 27 | 11: "B", | ||
28 | } | 28 | } | ||
29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | 29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | ||
30 | TONES_COUNT = 12 | 30 | TONES_COUNT = 12 | ||
31 | MINOR_THIRD_INDEX = 3 | 31 | MINOR_THIRD_INDEX = 3 | ||
32 | MAJOR_THIRD_INDEX = 4 | 32 | MAJOR_THIRD_INDEX = 4 | ||
33 | 33 | ||||
34 | 34 | ||||
35 | class Tone: | 35 | class Tone: | ||
36 | 36 | ||||
37 | def __init__(self, tone_str: str): | 37 | def __init__(self, tone_str: str): | ||
38 | self.__tone_str = tone_str | 38 | self.__tone_str = tone_str | ||
39 | 39 | ||||
40 | def __str__(self): | 40 | def __str__(self): | ||
41 | return self.__tone_str | 41 | return self.__tone_str | ||
42 | 42 | ||||
43 | def __hash__(self): | 43 | def __hash__(self): | ||
44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | 44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | ||
45 | 45 | ||||
46 | def __eq__(self, other): | 46 | def __eq__(self, other): | ||
47 | if isinstance(other, Tone): | 47 | if isinstance(other, Tone): | ||
48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | 48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | ||
49 | 49 | ||||
50 | def __sub__(self, other): | 50 | def __sub__(self, other): | ||
51 | if isinstance(other, Tone): | 51 | if isinstance(other, Tone): | ||
52 | if self.get_tone_index() > other.get_tone_index(): | 52 | if self.get_tone_index() > other.get_tone_index(): | ||
53 | return Interval(self.get_tone_index() - other.get_tone_index()) | 53 | return Interval(self.get_tone_index() - other.get_tone_index()) | ||
54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | 54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | ||
55 | elif isinstance(other, Interval): | 55 | elif isinstance(other, Interval): | ||
56 | return self + (-other) | 56 | return self + (-other) | ||
57 | 57 | ||||
58 | def __add__(self, other): | 58 | def __add__(self, other): | ||
59 | if isinstance(other, Tone): | 59 | if isinstance(other, Tone): | ||
60 | return Chord(self, other) | 60 | return Chord(self, other) | ||
61 | elif isinstance(other, Interval): | 61 | elif isinstance(other, Interval): | ||
62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | 62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | ||
63 | return Tone(INDEX_TO_TONE_STR[index]) | 63 | return Tone(INDEX_TO_TONE_STR[index]) | ||
64 | 64 | ||||
65 | @property | 65 | @property | ||
66 | def tone_str(self): | 66 | def tone_str(self): | ||
67 | return self.__tone_str | 67 | return self.__tone_str | ||
68 | 68 | ||||
69 | def get_tone_index(self): | 69 | def get_tone_index(self): | ||
70 | return TONE_STR_TO_INDEX[self.__tone_str] | 70 | return TONE_STR_TO_INDEX[self.__tone_str] | ||
71 | 71 | ||||
72 | 72 | ||||
73 | class Interval: | 73 | class Interval: | ||
74 | 74 | ||||
75 | def __init__(self, number_of_semitones: int): | 75 | def __init__(self, number_of_semitones: int): | ||
76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | 76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | ||
77 | 77 | ||||
78 | def __neg__(self): | 78 | def __neg__(self): | ||
79 | return Interval(-self.__number_of_semitones) | 79 | return Interval(-self.__number_of_semitones) | ||
80 | 80 | ||||
81 | def __str__(self): | 81 | def __str__(self): | ||
82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | 82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | ||
83 | 83 | ||||
84 | def __add__(self, other): | 84 | def __add__(self, other): | ||
85 | if isinstance(other, Interval): | 85 | if isinstance(other, Interval): | ||
86 | return Interval(self.number_of_semitones + other.number_of_semitones) | 86 | return Interval(self.number_of_semitones + other.number_of_semitones) | ||
87 | elif isinstance(other, Tone): | 87 | elif isinstance(other, Tone): | ||
88 | raise TypeError("Invalid operation") | 88 | raise TypeError("Invalid operation") | ||
89 | 89 | ||||
90 | def __sub__(self, other): | 90 | def __sub__(self, other): | ||
91 | if isinstance(other, Tone): | 91 | if isinstance(other, Tone): | ||
92 | raise TypeError("Invalid operation") | 92 | raise TypeError("Invalid operation") | ||
93 | 93 | ||||
94 | @property | 94 | @property | ||
95 | def number_of_semitones(self): | 95 | def number_of_semitones(self): | ||
96 | return self.__number_of_semitones | 96 | return self.__number_of_semitones | ||
97 | 97 | ||||
98 | 98 | ||||
99 | class Chord: | 99 | class Chord: | ||
100 | def __init__(self, root: Tone, *args: [Tone]): | 100 | def __init__(self, root: Tone, *args: [Tone]): | ||
101 | self.__root = root | 101 | self.__root = root | ||
102 | self.__initialise_tones(args) | 102 | self.__initialise_tones(args) | ||
103 | 103 | ||||
104 | def __str__(self): | 104 | def __str__(self): | ||
105 | return "-".join(map(str, [self.__root, *self.__tones])) | 105 | return "-".join(map(str, [self.__root, *self.__tones])) | ||
106 | 106 | ||||
107 | def __sub__(self, other): | 107 | def __sub__(self, other): | ||
108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.__tones] + [self.__root.get_tone_index()]): | 108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.__tones] + [self.__root.get_tone_index()]): | ||
109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | 109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | ||
110 | 110 | ||||
111 | if other.get_tone_index() == self.__root.get_tone_index(): | 111 | if other.get_tone_index() == self.__root.get_tone_index(): | ||
t | 112 | return Chord(self.__tones[0], self.__tones[1:]) | t | 112 | return Chord(self.__tones[0], *self.__tones[1:]) |
113 | return Chord(self.__root, *list(filter(lambda cur: str(cur) != str(other), self.__tones))) | 113 | return Chord(self.__root, *list(filter(lambda cur: str(cur) != str(other), self.__tones))) | ||
114 | 114 | ||||
115 | def __add__(self, other): | 115 | def __add__(self, other): | ||
116 | if isinstance(other, Tone): | 116 | if isinstance(other, Tone): | ||
117 | return Chord(self.__root, *self.__tones, other) | 117 | return Chord(self.__root, *self.__tones, other) | ||
118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
119 | return Chord(self.__root, *self.__tones, other.__root, *other.__tones) | 119 | return Chord(self.__root, *self.__tones, other.__root, *other.__tones) | ||
120 | 120 | ||||
121 | def __initialise_tones(self, new_tones: [Tone]): | 121 | def __initialise_tones(self, new_tones: [Tone]): | ||
122 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | 122 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | ||
123 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | 123 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | ||
124 | 124 | ||||
125 | if len(new_tones) == 0: | 125 | if len(new_tones) == 0: | ||
126 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 126 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
127 | 127 | ||||
128 | pivot = self.__root.get_tone_index() | 128 | pivot = self.__root.get_tone_index() | ||
129 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | 129 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | ||
130 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | 130 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | ||
131 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | 131 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | ||
132 | self.__tones = [*upper_bound, *lower_bound] | 132 | self.__tones = [*upper_bound, *lower_bound] | ||
133 | 133 | ||||
134 | def is_minor(self) -> bool: | 134 | def is_minor(self) -> bool: | ||
135 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | 135 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | ||
136 | 136 | ||||
137 | def is_major(self) -> bool: | 137 | def is_major(self) -> bool: | ||
138 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | 138 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | ||
139 | 139 | ||||
140 | def is_power_chord(self) -> bool: | 140 | def is_power_chord(self) -> bool: | ||
141 | return not self.is_major() and not self.is_minor() | 141 | return not self.is_major() and not self.is_minor() | ||
142 | 142 | ||||
143 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | 143 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | ||
144 | for tone in self.__tones: | 144 | for tone in self.__tones: | ||
145 | current_interval = tone - self.__root | 145 | current_interval = tone - self.__root | ||
146 | if current_interval.number_of_semitones == number_of_semitones: | 146 | if current_interval.number_of_semitones == number_of_semitones: | ||
147 | return True | 147 | return True | ||
148 | return False | 148 | return False | ||
149 | 149 | ||||
150 | def transposed(self, interval: Interval): | 150 | def transposed(self, interval: Interval): | ||
151 | new_root = self.__root + interval | 151 | new_root = self.__root + interval | ||
152 | new_tones = [tone + interval for tone in self.__tones] | 152 | new_tones = [tone + interval for tone in self.__tones] | ||
153 | return Chord(new_root, *new_tones) | 153 | return Chord(new_root, *new_tones) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | NUMBER_OF_SEMITONES_TO_STR = { | f | 1 | NUMBER_OF_SEMITONES_TO_STR = { |
2 | 0: "unison", | 2 | 0: "unison", | ||
3 | 1: "minor 2nd", | 3 | 1: "minor 2nd", | ||
4 | 2: "major 2nd", | 4 | 2: "major 2nd", | ||
5 | 3: "minor 3rd", | 5 | 3: "minor 3rd", | ||
6 | 4: "major 3rd", | 6 | 4: "major 3rd", | ||
7 | 5: "perfect 4th", | 7 | 5: "perfect 4th", | ||
8 | 6: "diminished 5th", | 8 | 6: "diminished 5th", | ||
9 | 7: "perfect 5th", | 9 | 7: "perfect 5th", | ||
10 | 8: "minor 6th", | 10 | 8: "minor 6th", | ||
11 | 9: "major 6th", | 11 | 9: "major 6th", | ||
12 | 10: "minor 7th", | 12 | 10: "minor 7th", | ||
13 | 11: "major 7th" | 13 | 11: "major 7th" | ||
14 | } | 14 | } | ||
15 | INDEX_TO_TONE_STR = { | 15 | INDEX_TO_TONE_STR = { | ||
16 | 0: "C", | 16 | 0: "C", | ||
17 | 1: "C#", | 17 | 1: "C#", | ||
18 | 2: "D", | 18 | 2: "D", | ||
19 | 3: "D#", | 19 | 3: "D#", | ||
20 | 4: "E", | 20 | 4: "E", | ||
21 | 5: "F", | 21 | 5: "F", | ||
22 | 6: "F#", | 22 | 6: "F#", | ||
23 | 7: "G", | 23 | 7: "G", | ||
24 | 8: "G#", | 24 | 8: "G#", | ||
25 | 9: "A", | 25 | 9: "A", | ||
26 | 10: "A#", | 26 | 10: "A#", | ||
27 | 11: "B", | 27 | 11: "B", | ||
28 | } | 28 | } | ||
29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | 29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | ||
30 | TONES_COUNT = 12 | 30 | TONES_COUNT = 12 | ||
31 | MINOR_THIRD_INDEX = 3 | 31 | MINOR_THIRD_INDEX = 3 | ||
32 | MAJOR_THIRD_INDEX = 4 | 32 | MAJOR_THIRD_INDEX = 4 | ||
33 | 33 | ||||
34 | 34 | ||||
35 | class Tone: | 35 | class Tone: | ||
36 | 36 | ||||
37 | def __init__(self, tone_str: str): | 37 | def __init__(self, tone_str: str): | ||
38 | self.__tone_str = tone_str | 38 | self.__tone_str = tone_str | ||
39 | 39 | ||||
40 | def __str__(self): | 40 | def __str__(self): | ||
41 | return self.__tone_str | 41 | return self.__tone_str | ||
42 | 42 | ||||
43 | def __hash__(self): | 43 | def __hash__(self): | ||
44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | 44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | ||
45 | 45 | ||||
46 | def __eq__(self, other): | 46 | def __eq__(self, other): | ||
47 | if isinstance(other, Tone): | 47 | if isinstance(other, Tone): | ||
48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | 48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | ||
49 | 49 | ||||
50 | def __sub__(self, other): | 50 | def __sub__(self, other): | ||
51 | if isinstance(other, Tone): | 51 | if isinstance(other, Tone): | ||
52 | if self.get_tone_index() > other.get_tone_index(): | 52 | if self.get_tone_index() > other.get_tone_index(): | ||
53 | return Interval(self.get_tone_index() - other.get_tone_index()) | 53 | return Interval(self.get_tone_index() - other.get_tone_index()) | ||
54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | 54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | ||
55 | elif isinstance(other, Interval): | 55 | elif isinstance(other, Interval): | ||
56 | return self + (-other) | 56 | return self + (-other) | ||
57 | 57 | ||||
58 | def __add__(self, other): | 58 | def __add__(self, other): | ||
59 | if isinstance(other, Tone): | 59 | if isinstance(other, Tone): | ||
60 | return Chord(self, other) | 60 | return Chord(self, other) | ||
61 | elif isinstance(other, Interval): | 61 | elif isinstance(other, Interval): | ||
62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | 62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | ||
63 | return Tone(INDEX_TO_TONE_STR[index]) | 63 | return Tone(INDEX_TO_TONE_STR[index]) | ||
64 | 64 | ||||
65 | @property | 65 | @property | ||
66 | def tone_str(self): | 66 | def tone_str(self): | ||
67 | return self.__tone_str | 67 | return self.__tone_str | ||
68 | 68 | ||||
69 | def get_tone_index(self): | 69 | def get_tone_index(self): | ||
70 | return TONE_STR_TO_INDEX[self.__tone_str] | 70 | return TONE_STR_TO_INDEX[self.__tone_str] | ||
71 | 71 | ||||
72 | 72 | ||||
73 | class Interval: | 73 | class Interval: | ||
74 | 74 | ||||
75 | def __init__(self, number_of_semitones: int): | 75 | def __init__(self, number_of_semitones: int): | ||
76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | 76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | ||
77 | 77 | ||||
78 | def __neg__(self): | 78 | def __neg__(self): | ||
79 | return Interval(-self.__number_of_semitones) | 79 | return Interval(-self.__number_of_semitones) | ||
80 | 80 | ||||
81 | def __str__(self): | 81 | def __str__(self): | ||
82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | 82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | ||
83 | 83 | ||||
84 | def __add__(self, other): | 84 | def __add__(self, other): | ||
85 | if isinstance(other, Interval): | 85 | if isinstance(other, Interval): | ||
86 | return Interval(self.number_of_semitones + other.number_of_semitones) | 86 | return Interval(self.number_of_semitones + other.number_of_semitones) | ||
87 | elif isinstance(other, Tone): | 87 | elif isinstance(other, Tone): | ||
88 | raise TypeError("Invalid operation") | 88 | raise TypeError("Invalid operation") | ||
89 | 89 | ||||
90 | def __sub__(self, other): | 90 | def __sub__(self, other): | ||
91 | if isinstance(other, Tone): | 91 | if isinstance(other, Tone): | ||
92 | raise TypeError("Invalid operation") | 92 | raise TypeError("Invalid operation") | ||
93 | 93 | ||||
94 | @property | 94 | @property | ||
95 | def number_of_semitones(self): | 95 | def number_of_semitones(self): | ||
96 | return self.__number_of_semitones | 96 | return self.__number_of_semitones | ||
97 | 97 | ||||
98 | 98 | ||||
99 | class Chord: | 99 | class Chord: | ||
100 | def __init__(self, root: Tone, *args: [Tone]): | 100 | def __init__(self, root: Tone, *args: [Tone]): | ||
101 | self.__root = root | 101 | self.__root = root | ||
102 | self.__initialise_tones(args) | 102 | self.__initialise_tones(args) | ||
103 | 103 | ||||
104 | def __str__(self): | 104 | def __str__(self): | ||
105 | return "-".join(map(str, [self.__root, *self.__tones])) | 105 | return "-".join(map(str, [self.__root, *self.__tones])) | ||
106 | 106 | ||||
107 | def __sub__(self, other): | 107 | def __sub__(self, other): | ||
108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.__tones] + [self.__root.get_tone_index()]): | 108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.__tones] + [self.__root.get_tone_index()]): | ||
109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | 109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | ||
110 | 110 | ||||
111 | if other.get_tone_index() == self.__root.get_tone_index(): | 111 | if other.get_tone_index() == self.__root.get_tone_index(): | ||
112 | return Chord(self.__tones[0], self.__tones[1:]) | 112 | return Chord(self.__tones[0], self.__tones[1:]) | ||
113 | return Chord(self.__root, *list(filter(lambda cur: str(cur) != str(other), self.__tones))) | 113 | return Chord(self.__root, *list(filter(lambda cur: str(cur) != str(other), self.__tones))) | ||
114 | 114 | ||||
115 | def __add__(self, other): | 115 | def __add__(self, other): | ||
116 | if isinstance(other, Tone): | 116 | if isinstance(other, Tone): | ||
117 | return Chord(self.__root, *self.__tones, other) | 117 | return Chord(self.__root, *self.__tones, other) | ||
118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
119 | return Chord(self.__root, *self.__tones, other.__root, *other.__tones) | 119 | return Chord(self.__root, *self.__tones, other.__root, *other.__tones) | ||
120 | 120 | ||||
121 | def __initialise_tones(self, new_tones: [Tone]): | 121 | def __initialise_tones(self, new_tones: [Tone]): | ||
122 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | 122 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | ||
123 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | 123 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | ||
124 | 124 | ||||
125 | if len(new_tones) == 0: | 125 | if len(new_tones) == 0: | ||
t | 126 | raise ValueError("Cannot have a chord made of only 1 unique tone") | t | 126 | raise TypeError("Cannot have a chord made of only 1 unique tone") |
127 | 127 | ||||
128 | pivot = self.__root.get_tone_index() | 128 | pivot = self.__root.get_tone_index() | ||
129 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | 129 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | ||
130 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | 130 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | ||
131 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | 131 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | ||
132 | self.__tones = [*upper_bound, *lower_bound] | 132 | self.__tones = [*upper_bound, *lower_bound] | ||
133 | 133 | ||||
134 | def is_minor(self) -> bool: | 134 | def is_minor(self) -> bool: | ||
135 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | 135 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | ||
136 | 136 | ||||
137 | def is_major(self) -> bool: | 137 | def is_major(self) -> bool: | ||
138 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | 138 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | ||
139 | 139 | ||||
140 | def is_power_chord(self) -> bool: | 140 | def is_power_chord(self) -> bool: | ||
141 | return not self.is_major() and not self.is_minor() | 141 | return not self.is_major() and not self.is_minor() | ||
142 | 142 | ||||
143 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | 143 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | ||
144 | for tone in self.__tones: | 144 | for tone in self.__tones: | ||
145 | current_interval = tone - self.__root | 145 | current_interval = tone - self.__root | ||
146 | if current_interval.number_of_semitones == number_of_semitones: | 146 | if current_interval.number_of_semitones == number_of_semitones: | ||
147 | return True | 147 | return True | ||
148 | return False | 148 | return False | ||
149 | 149 | ||||
150 | def transposed(self, interval: Interval): | 150 | def transposed(self, interval: Interval): | ||
151 | new_root = self.__root + interval | 151 | new_root = self.__root + interval | ||
152 | new_tones = [tone + interval for tone in self.__tones] | 152 | new_tones = [tone + interval for tone in self.__tones] | ||
153 | return Chord(new_root, *new_tones) | 153 | return Chord(new_root, *new_tones) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | NUMBER_OF_SEMITONES_TO_STR = { | f | 1 | NUMBER_OF_SEMITONES_TO_STR = { |
2 | 0: "unison", | 2 | 0: "unison", | ||
3 | 1: "minor 2nd", | 3 | 1: "minor 2nd", | ||
4 | 2: "major 2nd", | 4 | 2: "major 2nd", | ||
5 | 3: "minor 3rd", | 5 | 3: "minor 3rd", | ||
6 | 4: "major 3rd", | 6 | 4: "major 3rd", | ||
7 | 5: "perfect 4th", | 7 | 5: "perfect 4th", | ||
8 | 6: "diminished 5th", | 8 | 6: "diminished 5th", | ||
9 | 7: "perfect 5th", | 9 | 7: "perfect 5th", | ||
10 | 8: "minor 6th", | 10 | 8: "minor 6th", | ||
11 | 9: "major 6th", | 11 | 9: "major 6th", | ||
12 | 10: "minor 7th", | 12 | 10: "minor 7th", | ||
13 | 11: "major 7th" | 13 | 11: "major 7th" | ||
14 | } | 14 | } | ||
15 | INDEX_TO_TONE_STR = { | 15 | INDEX_TO_TONE_STR = { | ||
16 | 0: "C", | 16 | 0: "C", | ||
17 | 1: "C#", | 17 | 1: "C#", | ||
18 | 2: "D", | 18 | 2: "D", | ||
19 | 3: "D#", | 19 | 3: "D#", | ||
20 | 4: "E", | 20 | 4: "E", | ||
21 | 5: "F", | 21 | 5: "F", | ||
22 | 6: "F#", | 22 | 6: "F#", | ||
23 | 7: "G", | 23 | 7: "G", | ||
24 | 8: "G#", | 24 | 8: "G#", | ||
25 | 9: "A", | 25 | 9: "A", | ||
26 | 10: "A#", | 26 | 10: "A#", | ||
27 | 11: "B", | 27 | 11: "B", | ||
28 | } | 28 | } | ||
29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | 29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | ||
30 | TONES_COUNT = 12 | 30 | TONES_COUNT = 12 | ||
31 | MINOR_THIRD_INDEX = 3 | 31 | MINOR_THIRD_INDEX = 3 | ||
32 | MAJOR_THIRD_INDEX = 4 | 32 | MAJOR_THIRD_INDEX = 4 | ||
33 | 33 | ||||
34 | 34 | ||||
35 | class Tone: | 35 | class Tone: | ||
36 | 36 | ||||
37 | def __init__(self, tone_str: str): | 37 | def __init__(self, tone_str: str): | ||
38 | self.__tone_str = tone_str | 38 | self.__tone_str = tone_str | ||
39 | 39 | ||||
40 | def __str__(self): | 40 | def __str__(self): | ||
41 | return self.__tone_str | 41 | return self.__tone_str | ||
42 | 42 | ||||
43 | def __hash__(self): | 43 | def __hash__(self): | ||
44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | 44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | ||
45 | 45 | ||||
46 | def __eq__(self, other): | 46 | def __eq__(self, other): | ||
47 | if isinstance(other, Tone): | 47 | if isinstance(other, Tone): | ||
48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | 48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | ||
49 | 49 | ||||
50 | def __sub__(self, other): | 50 | def __sub__(self, other): | ||
51 | if isinstance(other, Tone): | 51 | if isinstance(other, Tone): | ||
52 | if self.get_tone_index() > other.get_tone_index(): | 52 | if self.get_tone_index() > other.get_tone_index(): | ||
53 | return Interval(self.get_tone_index() - other.get_tone_index()) | 53 | return Interval(self.get_tone_index() - other.get_tone_index()) | ||
54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | 54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | ||
55 | elif isinstance(other, Interval): | 55 | elif isinstance(other, Interval): | ||
56 | return self + (-other) | 56 | return self + (-other) | ||
57 | 57 | ||||
58 | def __add__(self, other): | 58 | def __add__(self, other): | ||
59 | if isinstance(other, Tone): | 59 | if isinstance(other, Tone): | ||
60 | return Chord(self, other) | 60 | return Chord(self, other) | ||
61 | elif isinstance(other, Interval): | 61 | elif isinstance(other, Interval): | ||
62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | 62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | ||
63 | return Tone(INDEX_TO_TONE_STR[index]) | 63 | return Tone(INDEX_TO_TONE_STR[index]) | ||
64 | 64 | ||||
65 | @property | 65 | @property | ||
66 | def tone_str(self): | 66 | def tone_str(self): | ||
67 | return self.__tone_str | 67 | return self.__tone_str | ||
68 | 68 | ||||
69 | def get_tone_index(self): | 69 | def get_tone_index(self): | ||
70 | return TONE_STR_TO_INDEX[self.__tone_str] | 70 | return TONE_STR_TO_INDEX[self.__tone_str] | ||
71 | 71 | ||||
72 | 72 | ||||
73 | class Interval: | 73 | class Interval: | ||
74 | 74 | ||||
75 | def __init__(self, number_of_semitones: int): | 75 | def __init__(self, number_of_semitones: int): | ||
76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | 76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | ||
77 | 77 | ||||
78 | def __neg__(self): | 78 | def __neg__(self): | ||
79 | return Interval(-self.__number_of_semitones) | 79 | return Interval(-self.__number_of_semitones) | ||
80 | 80 | ||||
81 | def __str__(self): | 81 | def __str__(self): | ||
82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | 82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | ||
83 | 83 | ||||
84 | def __add__(self, other): | 84 | def __add__(self, other): | ||
85 | if isinstance(other, Interval): | 85 | if isinstance(other, Interval): | ||
86 | return Interval(self.number_of_semitones + other.number_of_semitones) | 86 | return Interval(self.number_of_semitones + other.number_of_semitones) | ||
87 | elif isinstance(other, Tone): | 87 | elif isinstance(other, Tone): | ||
88 | raise TypeError("Invalid operation") | 88 | raise TypeError("Invalid operation") | ||
89 | 89 | ||||
90 | def __sub__(self, other): | 90 | def __sub__(self, other): | ||
91 | if isinstance(other, Tone): | 91 | if isinstance(other, Tone): | ||
92 | raise TypeError("Invalid operation") | 92 | raise TypeError("Invalid operation") | ||
93 | 93 | ||||
94 | @property | 94 | @property | ||
95 | def number_of_semitones(self): | 95 | def number_of_semitones(self): | ||
96 | return self.__number_of_semitones | 96 | return self.__number_of_semitones | ||
97 | 97 | ||||
98 | 98 | ||||
99 | class Chord: | 99 | class Chord: | ||
100 | def __init__(self, root: Tone, *args: [Tone]): | 100 | def __init__(self, root: Tone, *args: [Tone]): | ||
101 | self.__root = root | 101 | self.__root = root | ||
102 | self.__initialise_tones(args) | 102 | self.__initialise_tones(args) | ||
103 | 103 | ||||
104 | def __str__(self): | 104 | def __str__(self): | ||
n | 105 | return "-".join(map(str, [self.root, *self.tones])) | n | 105 | return "-".join(map(str, [self.__root, *self.__tones])) |
106 | 106 | ||||
107 | def __sub__(self, other): | 107 | def __sub__(self, other): | ||
n | 108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.tones] + [self.root.get_tone_index()]): | n | 108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.__tones] + [self.__root.get_tone_index()]): |
109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | 109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | ||
110 | 110 | ||||
n | 111 | if other.get_tone_index() == self.root.get_tone_index(): | n | 111 | if other.get_tone_index() == self.__root.get_tone_index(): |
112 | return Chord(self.tones[0], self.tones[1:]) | 112 | return Chord(self.__tones[0], self.__tones[1:]) | ||
113 | return Chord(self.root, *list(filter(lambda cur: str(cur) != str(other), self.tones))) | 113 | return Chord(self.__root, *list(filter(lambda cur: str(cur) != str(other), self.__tones))) | ||
114 | 114 | ||||
115 | def __add__(self, other): | 115 | def __add__(self, other): | ||
116 | if isinstance(other, Tone): | 116 | if isinstance(other, Tone): | ||
n | 117 | return Chord(self.root, *self.tones, other) | n | 117 | return Chord(self.__root, *self.__tones, other) |
118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
n | 119 | return Chord(self.root, *self.tones, other.root, *other.tones) | n | 119 | return Chord(self.__root, *self.__tones, other.__root, *other.__tones) |
120 | |||||
121 | @property | ||||
122 | def tones(self): | ||||
123 | return self.__tones.copy() | ||||
124 | |||||
125 | @property | ||||
126 | def root(self): | ||||
127 | return self.__root | ||||
128 | 120 | ||||
129 | def __initialise_tones(self, new_tones: [Tone]): | 121 | def __initialise_tones(self, new_tones: [Tone]): | ||
130 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | 122 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | ||
131 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | 123 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | ||
132 | 124 | ||||
n | 133 | if len(new_tones) == 0 or (len(new_tones) == 1 and str(new_tones[0]) == self.__root): | n | 125 | if len(new_tones) == 0: |
134 | raise ValueError("Cannot have a chord made of only 1 unique tone") | 126 | raise ValueError("Cannot have a chord made of only 1 unique tone") | ||
135 | 127 | ||||
136 | pivot = self.__root.get_tone_index() | 128 | pivot = self.__root.get_tone_index() | ||
137 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | 129 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | ||
138 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | 130 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | ||
139 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | 131 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | ||
140 | self.__tones = [*upper_bound, *lower_bound] | 132 | self.__tones = [*upper_bound, *lower_bound] | ||
141 | 133 | ||||
142 | def is_minor(self) -> bool: | 134 | def is_minor(self) -> bool: | ||
143 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | 135 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | ||
144 | 136 | ||||
145 | def is_major(self) -> bool: | 137 | def is_major(self) -> bool: | ||
146 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | 138 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | ||
147 | 139 | ||||
148 | def is_power_chord(self) -> bool: | 140 | def is_power_chord(self) -> bool: | ||
149 | return not self.is_major() and not self.is_minor() | 141 | return not self.is_major() and not self.is_minor() | ||
150 | 142 | ||||
151 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | 143 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | ||
n | 152 | for tone in self.tones: | n | 144 | for tone in self.__tones: |
153 | current_interval = tone - self.__root | 145 | current_interval = tone - self.__root | ||
154 | if current_interval.number_of_semitones == number_of_semitones: | 146 | if current_interval.number_of_semitones == number_of_semitones: | ||
155 | return True | 147 | return True | ||
156 | return False | 148 | return False | ||
157 | 149 | ||||
158 | def transposed(self, interval: Interval): | 150 | def transposed(self, interval: Interval): | ||
159 | new_root = self.__root + interval | 151 | new_root = self.__root + interval | ||
n | 160 | new_tones = [tone + interval for tone in self.tones] | n | 152 | new_tones = [tone + interval for tone in self.__tones] |
161 | return Chord(new_root, *new_tones) | 153 | return Chord(new_root, *new_tones) | ||
t | 162 | t | |||
163 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | NUMBER_OF_SEMITONES_TO_STR = { | f | 1 | NUMBER_OF_SEMITONES_TO_STR = { |
2 | 0: "unison", | 2 | 0: "unison", | ||
3 | 1: "minor 2nd", | 3 | 1: "minor 2nd", | ||
4 | 2: "major 2nd", | 4 | 2: "major 2nd", | ||
5 | 3: "minor 3rd", | 5 | 3: "minor 3rd", | ||
6 | 4: "major 3rd", | 6 | 4: "major 3rd", | ||
7 | 5: "perfect 4th", | 7 | 5: "perfect 4th", | ||
8 | 6: "diminished 5th", | 8 | 6: "diminished 5th", | ||
9 | 7: "perfect 5th", | 9 | 7: "perfect 5th", | ||
10 | 8: "minor 6th", | 10 | 8: "minor 6th", | ||
11 | 9: "major 6th", | 11 | 9: "major 6th", | ||
12 | 10: "minor 7th", | 12 | 10: "minor 7th", | ||
13 | 11: "major 7th" | 13 | 11: "major 7th" | ||
14 | } | 14 | } | ||
15 | INDEX_TO_TONE_STR = { | 15 | INDEX_TO_TONE_STR = { | ||
16 | 0: "C", | 16 | 0: "C", | ||
17 | 1: "C#", | 17 | 1: "C#", | ||
18 | 2: "D", | 18 | 2: "D", | ||
19 | 3: "D#", | 19 | 3: "D#", | ||
20 | 4: "E", | 20 | 4: "E", | ||
21 | 5: "F", | 21 | 5: "F", | ||
22 | 6: "F#", | 22 | 6: "F#", | ||
23 | 7: "G", | 23 | 7: "G", | ||
24 | 8: "G#", | 24 | 8: "G#", | ||
25 | 9: "A", | 25 | 9: "A", | ||
26 | 10: "A#", | 26 | 10: "A#", | ||
27 | 11: "B", | 27 | 11: "B", | ||
28 | } | 28 | } | ||
29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | 29 | TONE_STR_TO_INDEX = {item[1]: item[0] for item in INDEX_TO_TONE_STR.items()} | ||
30 | TONES_COUNT = 12 | 30 | TONES_COUNT = 12 | ||
31 | MINOR_THIRD_INDEX = 3 | 31 | MINOR_THIRD_INDEX = 3 | ||
32 | MAJOR_THIRD_INDEX = 4 | 32 | MAJOR_THIRD_INDEX = 4 | ||
33 | 33 | ||||
34 | 34 | ||||
35 | class Tone: | 35 | class Tone: | ||
36 | 36 | ||||
37 | def __init__(self, tone_str: str): | 37 | def __init__(self, tone_str: str): | ||
38 | self.__tone_str = tone_str | 38 | self.__tone_str = tone_str | ||
39 | 39 | ||||
40 | def __str__(self): | 40 | def __str__(self): | ||
41 | return self.__tone_str | 41 | return self.__tone_str | ||
42 | 42 | ||||
43 | def __hash__(self): | 43 | def __hash__(self): | ||
44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | 44 | return hash(self.__tone_str) # това го правя, за да кажа dict.fromkeys(tones) | ||
45 | 45 | ||||
46 | def __eq__(self, other): | 46 | def __eq__(self, other): | ||
47 | if isinstance(other, Tone): | 47 | if isinstance(other, Tone): | ||
48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | 48 | return self.__tone_str == other.tone_str # това го правя, за да кажа dict.fromkeys(tones) | ||
49 | 49 | ||||
50 | def __sub__(self, other): | 50 | def __sub__(self, other): | ||
51 | if isinstance(other, Tone): | 51 | if isinstance(other, Tone): | ||
52 | if self.get_tone_index() > other.get_tone_index(): | 52 | if self.get_tone_index() > other.get_tone_index(): | ||
53 | return Interval(self.get_tone_index() - other.get_tone_index()) | 53 | return Interval(self.get_tone_index() - other.get_tone_index()) | ||
54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | 54 | return Interval(TONES_COUNT - other.get_tone_index() - self.get_tone_index()) | ||
55 | elif isinstance(other, Interval): | 55 | elif isinstance(other, Interval): | ||
56 | return self + (-other) | 56 | return self + (-other) | ||
57 | 57 | ||||
58 | def __add__(self, other): | 58 | def __add__(self, other): | ||
59 | if isinstance(other, Tone): | 59 | if isinstance(other, Tone): | ||
60 | return Chord(self, other) | 60 | return Chord(self, other) | ||
61 | elif isinstance(other, Interval): | 61 | elif isinstance(other, Interval): | ||
62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | 62 | index = (self.get_tone_index() + other.number_of_semitones) % TONES_COUNT | ||
63 | return Tone(INDEX_TO_TONE_STR[index]) | 63 | return Tone(INDEX_TO_TONE_STR[index]) | ||
64 | 64 | ||||
65 | @property | 65 | @property | ||
66 | def tone_str(self): | 66 | def tone_str(self): | ||
67 | return self.__tone_str | 67 | return self.__tone_str | ||
68 | 68 | ||||
69 | def get_tone_index(self): | 69 | def get_tone_index(self): | ||
70 | return TONE_STR_TO_INDEX[self.__tone_str] | 70 | return TONE_STR_TO_INDEX[self.__tone_str] | ||
71 | 71 | ||||
72 | 72 | ||||
73 | class Interval: | 73 | class Interval: | ||
74 | 74 | ||||
75 | def __init__(self, number_of_semitones: int): | 75 | def __init__(self, number_of_semitones: int): | ||
76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | 76 | self.__number_of_semitones = number_of_semitones % TONES_COUNT | ||
77 | 77 | ||||
78 | def __neg__(self): | 78 | def __neg__(self): | ||
79 | return Interval(-self.__number_of_semitones) | 79 | return Interval(-self.__number_of_semitones) | ||
80 | 80 | ||||
81 | def __str__(self): | 81 | def __str__(self): | ||
82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | 82 | return NUMBER_OF_SEMITONES_TO_STR[self.number_of_semitones] | ||
83 | 83 | ||||
84 | def __add__(self, other): | 84 | def __add__(self, other): | ||
85 | if isinstance(other, Interval): | 85 | if isinstance(other, Interval): | ||
86 | return Interval(self.number_of_semitones + other.number_of_semitones) | 86 | return Interval(self.number_of_semitones + other.number_of_semitones) | ||
87 | elif isinstance(other, Tone): | 87 | elif isinstance(other, Tone): | ||
88 | raise TypeError("Invalid operation") | 88 | raise TypeError("Invalid operation") | ||
89 | 89 | ||||
90 | def __sub__(self, other): | 90 | def __sub__(self, other): | ||
91 | if isinstance(other, Tone): | 91 | if isinstance(other, Tone): | ||
92 | raise TypeError("Invalid operation") | 92 | raise TypeError("Invalid operation") | ||
93 | 93 | ||||
94 | @property | 94 | @property | ||
95 | def number_of_semitones(self): | 95 | def number_of_semitones(self): | ||
96 | return self.__number_of_semitones | 96 | return self.__number_of_semitones | ||
97 | 97 | ||||
98 | 98 | ||||
99 | class Chord: | 99 | class Chord: | ||
100 | def __init__(self, root: Tone, *args: [Tone]): | 100 | def __init__(self, root: Tone, *args: [Tone]): | ||
101 | self.__root = root | 101 | self.__root = root | ||
102 | self.__initialise_tones(args) | 102 | self.__initialise_tones(args) | ||
103 | 103 | ||||
104 | def __str__(self): | 104 | def __str__(self): | ||
105 | return "-".join(map(str, [self.root, *self.tones])) | 105 | return "-".join(map(str, [self.root, *self.tones])) | ||
106 | 106 | ||||
107 | def __sub__(self, other): | 107 | def __sub__(self, other): | ||
108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.tones] + [self.root.get_tone_index()]): | 108 | if not other.get_tone_index() in ([t.get_tone_index() for t in self.tones] + [self.root.get_tone_index()]): | ||
109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | 109 | raise TypeError(f"Cannot remove tone {str(other)} from chord {self.__str__()}") | ||
110 | 110 | ||||
111 | if other.get_tone_index() == self.root.get_tone_index(): | 111 | if other.get_tone_index() == self.root.get_tone_index(): | ||
112 | return Chord(self.tones[0], self.tones[1:]) | 112 | return Chord(self.tones[0], self.tones[1:]) | ||
113 | return Chord(self.root, *list(filter(lambda cur: str(cur) != str(other), self.tones))) | 113 | return Chord(self.root, *list(filter(lambda cur: str(cur) != str(other), self.tones))) | ||
114 | 114 | ||||
115 | def __add__(self, other): | 115 | def __add__(self, other): | ||
116 | if isinstance(other, Tone): | 116 | if isinstance(other, Tone): | ||
117 | return Chord(self.root, *self.tones, other) | 117 | return Chord(self.root, *self.tones, other) | ||
118 | elif isinstance(other, Chord): | 118 | elif isinstance(other, Chord): | ||
119 | return Chord(self.root, *self.tones, other.root, *other.tones) | 119 | return Chord(self.root, *self.tones, other.root, *other.tones) | ||
120 | 120 | ||||
121 | @property | 121 | @property | ||
122 | def tones(self): | 122 | def tones(self): | ||
n | 123 | return self.__tones | n | 123 | return self.__tones.copy() |
124 | 124 | ||||
125 | @property | 125 | @property | ||
126 | def root(self): | 126 | def root(self): | ||
127 | return self.__root | 127 | return self.__root | ||
128 | 128 | ||||
129 | def __initialise_tones(self, new_tones: [Tone]): | 129 | def __initialise_tones(self, new_tones: [Tone]): | ||
130 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | 130 | new_tones = list(dict.fromkeys(filter(lambda cur: str(cur) != str(self.__root), new_tones))) | ||
131 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | 131 | # това ми прави все едно set ,защото пак ползва hash ,но пази реда ,защото dict след python 3.7 e ordered | ||
132 | 132 | ||||
133 | if len(new_tones) == 0 or (len(new_tones) == 1 and str(new_tones[0]) == self.__root): | 133 | if len(new_tones) == 0 or (len(new_tones) == 1 and str(new_tones[0]) == self.__root): | ||
134 | raise ValueError("Cannot have a chord made of only 1 unique tone") | 134 | raise ValueError("Cannot have a chord made of only 1 unique tone") | ||
135 | 135 | ||||
136 | pivot = self.__root.get_tone_index() | 136 | pivot = self.__root.get_tone_index() | ||
137 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | 137 | ordered_tones = list(sorted(new_tones, key=lambda cur: cur.get_tone_index())) | ||
138 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | 138 | upper_bound = list(filter(lambda cur: cur.get_tone_index() > pivot, ordered_tones)) | ||
139 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | 139 | lower_bound = list(filter(lambda cur: cur.get_tone_index() < pivot, ordered_tones)) | ||
140 | self.__tones = [*upper_bound, *lower_bound] | 140 | self.__tones = [*upper_bound, *lower_bound] | ||
141 | 141 | ||||
142 | def is_minor(self) -> bool: | 142 | def is_minor(self) -> bool: | ||
143 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | 143 | return self.__seek_for_interval(MINOR_THIRD_INDEX) | ||
144 | 144 | ||||
145 | def is_major(self) -> bool: | 145 | def is_major(self) -> bool: | ||
146 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | 146 | return self.__seek_for_interval(MAJOR_THIRD_INDEX) | ||
147 | 147 | ||||
148 | def is_power_chord(self) -> bool: | 148 | def is_power_chord(self) -> bool: | ||
149 | return not self.is_major() and not self.is_minor() | 149 | return not self.is_major() and not self.is_minor() | ||
150 | 150 | ||||
151 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | 151 | def __seek_for_interval(self, number_of_semitones: int) -> bool: | ||
152 | for tone in self.tones: | 152 | for tone in self.tones: | ||
153 | current_interval = tone - self.__root | 153 | current_interval = tone - self.__root | ||
154 | if current_interval.number_of_semitones == number_of_semitones: | 154 | if current_interval.number_of_semitones == number_of_semitones: | ||
155 | return True | 155 | return True | ||
156 | return False | 156 | return False | ||
157 | 157 | ||||
158 | def transposed(self, interval: Interval): | 158 | def transposed(self, interval: Interval): | ||
159 | new_root = self.__root + interval | 159 | new_root = self.__root + interval | ||
160 | new_tones = [tone + interval for tone in self.tones] | 160 | new_tones = [tone + interval for tone in self.tones] | ||
161 | return Chord(new_root, *new_tones) | 161 | return Chord(new_root, *new_tones) | ||
t | t | 162 | |||
163 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
04.11.2024 12:47
04.11.2024 12:47
04.11.2024 12:48
04.11.2024 12:48
04.11.2024 12:51
04.11.2024 12:50
04.11.2024 12:52
04.11.2024 12:54
04.11.2024 12:55