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