1class Tone:
  2    NAME_DOMAIN = ("A", "A#", "B", "C", "C#", "D",
  3                   "D#", "E", "F", "F#", "G", "G#")
  4
  5    def __init__(self, tone_name):
  6        self.name_index = Tone.NAME_DOMAIN.index(tone_name)
  7
  8    def __str__(self):
  9        return Tone.NAME_DOMAIN[self.name_index]
 10
 11    def __eq__(self, other):
 12        if isinstance(other, Tone):
 13            return self.name_index == other.name_index
 14        raise TypeError("Invalid operation")
 15
 16    def __hash__(self):
 17        return hash(self.name_index)
 18
 19    def __add__(self, other):
 20        if isinstance(other, Tone):
 21            return Chord(self, other)
 22        elif isinstance(other, Interval):
 23            return Tone(Tone.NAME_DOMAIN[(self.name_index +
 24                                          other.interval_name_index)
 25                                         % len(Tone.NAME_DOMAIN)])
 26        raise TypeError("Invalid operation")
 27
 28    def __sub__(self, other):
 29        if isinstance(other, Tone):
 30            return Interval(abs(self.name_index - other.name_index))
 31        elif isinstance(other, Interval):
 32            return Tone(Tone.NAME_DOMAIN[(self.name_index -
 33                                          other.interval_name_index)
 34                                         % len(Tone.NAME_DOMAIN)])
 35        raise TypeError("Invalid operation")
 36
 37
 38class Interval:
 39    INTERVAL_NAME_DOMAIN = ("unison", "minor 2nd", "major 2nd", "minor 3rd",
 40                            "major 3rd", "perfect 4th", "diminished 5th",
 41                            "perfect 5th", "minor 6th", "major 6th",
 42                            "minor 7th", "major 7th")
 43
 44    def __init__(self, number_of_semitones):
 45        self.interval_name_index = (number_of_semitones %
 46                                    len(Interval.INTERVAL_NAME_DOMAIN))
 47
 48    def __str__(self):
 49        return Interval.INTERVAL_NAME_DOMAIN[self.interval_name_index]
 50
 51    def __eq__(self, other):
 52        if isinstance(other, Interval):
 53            return self.interval_name_index == other.interval_name_index
 54        raise TypeError("Invalid operation")
 55
 56    def __neg__(self):
 57        return Interval(-self.interval_name_index)
 58
 59    def __add__(self, other):
 60        if isinstance(other, Interval):
 61            return Interval(self.interval_name_index +
 62                            other.interval_name_index)
 63        raise TypeError("Invalid operation")
 64
 65
 66class Chord:
 67    major_chord_interval = Interval(4)
 68    minor_chord_interval = Interval(3)
 69
 70    def __init__(self, root_tone: Tone, *additional_tones: Tone):
 71        self.tones = []
 72        self.tones.append(root_tone)
 73
 74        additional_tones = {tone for tone in additional_tones
 75                            if tone != root_tone}
 76
 77        if len(additional_tones) == 0:
 78            raise TypeError("Cannot have a chord made of only 1 unique tone")
 79
 80        temp_tones = []
 81        current_tone = self.tones[0] + Interval(1)
 82
 83        while current_tone != self.tones[0]:
 84            if current_tone in additional_tones:
 85                temp_tones.append(current_tone)
 86            current_tone = current_tone + Interval(1)
 87
 88        self.tones.extend(temp_tones)
 89
 90    def __str__(self):
 91        return "-".join([str(tone) for tone in self.tones])
 92
 93    def __add__(self, other):
 94        if isinstance(other, Tone):
 95            return Chord(*self.tones, other)
 96        elif isinstance(other, Chord):
 97            return Chord(*self.tones, *other.tones)
 98        raise TypeError("Invalid operation")
 99
100    def __sub__(self, other):
101        if isinstance(other, Tone):
102            if other not in self.tones:
103                raise TypeError(f"Cannot remove tone {other} from chord "
104                                f"{self}")
105            elif len(self.tones) == 2:
106                raise TypeError("Cannot have a chord made of only 1 unique "
107                                "tone")
108
109            return Chord(*[tone for tone in self.tones if tone != other])
110        raise TypeError("Invalid operation")
111
112    def is_major(self) -> bool:
113        root_tone_index = self.tones[0].name_index
114
115        for secondary_tone in self.tones[1:]:
116            temp_interval = Interval(abs(root_tone_index -
117                                         secondary_tone.name_index))
118
119            if temp_interval == Chord.major_chord_interval:
120                return True
121
122        return False
123
124    def is_minor(self) -> bool:
125        root_tone_index = self.tones[0].name_index
126
127        for secondary_tone in self.tones[1:]:
128            temp_interval = Interval(abs(root_tone_index -
129                                         secondary_tone.name_index))
130
131            if temp_interval == Chord.minor_chord_interval:
132                return True
133
134        return False
135
136    def is_power_chord(self) -> bool:
137        root_tone_index = self.tones[0].name_index
138
139        for secondary_tone in self.tones[1:]:
140            temp_interval = Interval(abs(root_tone_index -
141                                         secondary_tone.name_index))
142
143            if (temp_interval == Chord.major_chord_interval
144                    or temp_interval == Chord.minor_chord_interval):
145                return False
146
147        return True
148
149    def transposed(self, interval: Interval):
150        return Chord(*[tone + interval for tone in self.tones])
...........................F.......F.
======================================================================
FAIL: test_subtract_interval_from_tone_left_side_error (test.TestOperations.test_subtract_interval_from_tone_left_side_error)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 235, in test_subtract_interval_from_tone_left_side_error
    self.assertEqual(str(err.exception), INVALID_OPERATION)
AssertionError: "unsupported operand type(s) for -: 'Interval' and 'Tone'" != 'Invalid operation'
- unsupported operand type(s) for -: 'Interval' and 'Tone'
+ Invalid operation
======================================================================
FAIL: test_tone_subtraction_inverse (test.TestOperations.test_tone_subtraction_inverse)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 178, in test_tone_subtraction_inverse
    self.assertEqual(str(perfect_4th), "perfect 4th")
AssertionError: 'perfect 5th' != 'perfect 4th'
- perfect 5th
?         ^
+ perfect 4th
?         ^
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=2)
|   
        Виктор Бечев
         04.11.2024 11:46PyCharm нещо не е наред, ако списъците не дефинират `__getitem__` - не знам какво.  
Нищо от това, което си споменал не променя този факт. Just PyCharm being drunk, I guess. | 
|   
        Димитър Танков
         04.11.2024 00:37На 81 ред PyCharm изписва "Class 'list' does not define '__getitem__', so the '[]' operator cannot be used on its instances", което ме обърка. Възможно ли е да е обвързано с факта, че използвам елемент от self.tones list-а в условието на цикъла и след това променям temp_tones, което може да е нещо като shallow copy на self.tones, поради оптимизация. | 
|   
        Димитър Танков
         04.11.2024 00:32Да кажем, че имам бегло понятие за теория на музиката, понеже съм свирил на китара😁 | 
| f | 1 | class Tone: | f | 1 | class Tone: | 
| n | 2 | name_domain = ("A", "A#", "B", "C", "C#", "D", | n | 2 | NAME_DOMAIN = ("A", "A#", "B", "C", "C#", "D", | 
| 3 | "D#", "E", "F", "F#", "G", "G#") | 3 | "D#", "E", "F", "F#", "G", "G#") | ||
| 4 | 4 | ||||
| 5 | def __init__(self, tone_name): | 5 | def __init__(self, tone_name): | ||
| n | 6 | self.name_index = Tone.name_domain.index(tone_name) | n | 6 | self.name_index = Tone.NAME_DOMAIN.index(tone_name) | 
| 7 | 7 | ||||
| 8 | def __str__(self): | 8 | def __str__(self): | ||
| n | 9 | return Tone.name_domain[self.name_index] | n | 9 | return Tone.NAME_DOMAIN[self.name_index] | 
| 10 | 10 | ||||
| 11 | def __eq__(self, other): | 11 | def __eq__(self, other): | ||
| 12 | if isinstance(other, Tone): | 12 | if isinstance(other, Tone): | ||
| 13 | return self.name_index == other.name_index | 13 | return self.name_index == other.name_index | ||
| n | 14 | return False | n | 14 | raise TypeError("Invalid operation") | 
| 15 | 15 | ||||
| 16 | def __hash__(self): | 16 | def __hash__(self): | ||
| 17 | return hash(self.name_index) | 17 | return hash(self.name_index) | ||
| 18 | 18 | ||||
| 19 | def __add__(self, other): | 19 | def __add__(self, other): | ||
| 20 | if isinstance(other, Tone): | 20 | if isinstance(other, Tone): | ||
| 21 | return Chord(self, other) | 21 | return Chord(self, other) | ||
| 22 | elif isinstance(other, Interval): | 22 | elif isinstance(other, Interval): | ||
| n | 23 | return Tone(Tone.name_domain[(self.name_index + | n | 23 | return Tone(Tone.NAME_DOMAIN[(self.name_index + | 
| 24 | other.interval_name_index) | 24 | other.interval_name_index) | ||
| n | 25 | % len(Tone.name_domain)]) | n | 25 | % len(Tone.NAME_DOMAIN)]) | 
| 26 | raise TypeError("Invalid operation") | 26 | raise TypeError("Invalid operation") | ||
| 27 | 27 | ||||
| 28 | def __sub__(self, other): | 28 | def __sub__(self, other): | ||
| 29 | if isinstance(other, Tone): | 29 | if isinstance(other, Tone): | ||
| 30 | return Interval(abs(self.name_index - other.name_index)) | 30 | return Interval(abs(self.name_index - other.name_index)) | ||
| 31 | elif isinstance(other, Interval): | 31 | elif isinstance(other, Interval): | ||
| n | 32 | return Tone(Tone.name_domain[(self.name_index - | n | 32 | return Tone(Tone.NAME_DOMAIN[(self.name_index - | 
| 33 | other.interval_name_index) | 33 | other.interval_name_index) | ||
| n | 34 | % len(Tone.name_domain)]) | n | 34 | % len(Tone.NAME_DOMAIN)]) | 
| 35 | raise TypeError("Invalid operation") | 35 | raise TypeError("Invalid operation") | ||
| 36 | 36 | ||||
| 37 | 37 | ||||
| 38 | class Interval: | 38 | class Interval: | ||
| n | 39 | interval_name_domain = ("unison", "minor 2nd", "major 2nd", "minor 3rd", | n | 39 | INTERVAL_NAME_DOMAIN = ("unison", "minor 2nd", "major 2nd", "minor 3rd", | 
| 40 | "major 3rd", "perfect 4th", "diminished 5th", | 40 | "major 3rd", "perfect 4th", "diminished 5th", | ||
| 41 | "perfect 5th", "minor 6th", "major 6th", | 41 | "perfect 5th", "minor 6th", "major 6th", | ||
| 42 | "minor 7th", "major 7th") | 42 | "minor 7th", "major 7th") | ||
| 43 | 43 | ||||
| 44 | def __init__(self, number_of_semitones): | 44 | def __init__(self, number_of_semitones): | ||
| 45 | self.interval_name_index = (number_of_semitones % | 45 | self.interval_name_index = (number_of_semitones % | ||
| n | 46 | len(Interval.interval_name_domain)) | n | 46 | len(Interval.INTERVAL_NAME_DOMAIN)) | 
| 47 | 47 | ||||
| 48 | def __str__(self): | 48 | def __str__(self): | ||
| n | 49 | return Interval.interval_name_domain[self.interval_name_index] | n | 49 | return Interval.INTERVAL_NAME_DOMAIN[self.interval_name_index] | 
| 50 | 50 | ||||
| 51 | def __eq__(self, other): | 51 | def __eq__(self, other): | ||
| 52 | if isinstance(other, Interval): | 52 | if isinstance(other, Interval): | ||
| 53 | return self.interval_name_index == other.interval_name_index | 53 | return self.interval_name_index == other.interval_name_index | ||
| t | 54 | return False | t | 54 | raise TypeError("Invalid operation") | 
| 55 | 55 | ||||
| 56 | def __neg__(self): | 56 | def __neg__(self): | ||
| 57 | return Interval(-self.interval_name_index) | 57 | return Interval(-self.interval_name_index) | ||
| 58 | 58 | ||||
| 59 | def __add__(self, other): | 59 | def __add__(self, other): | ||
| 60 | if isinstance(other, Interval): | 60 | if isinstance(other, Interval): | ||
| 61 | return Interval(self.interval_name_index + | 61 | return Interval(self.interval_name_index + | ||
| 62 | other.interval_name_index) | 62 | other.interval_name_index) | ||
| 63 | raise TypeError("Invalid operation") | 63 | raise TypeError("Invalid operation") | ||
| 64 | 64 | ||||
| 65 | 65 | ||||
| 66 | class Chord: | 66 | class Chord: | ||
| 67 | major_chord_interval = Interval(4) | 67 | major_chord_interval = Interval(4) | ||
| 68 | minor_chord_interval = Interval(3) | 68 | minor_chord_interval = Interval(3) | ||
| 69 | 69 | ||||
| 70 | def __init__(self, root_tone: Tone, *additional_tones: Tone): | 70 | def __init__(self, root_tone: Tone, *additional_tones: Tone): | ||
| 71 | self.tones = [] | 71 | self.tones = [] | ||
| 72 | self.tones.append(root_tone) | 72 | self.tones.append(root_tone) | ||
| 73 | 73 | ||||
| 74 | additional_tones = {tone for tone in additional_tones | 74 | additional_tones = {tone for tone in additional_tones | ||
| 75 | if tone != root_tone} | 75 | if tone != root_tone} | ||
| 76 | 76 | ||||
| 77 | if len(additional_tones) == 0: | 77 | if len(additional_tones) == 0: | ||
| 78 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 78 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
| 79 | 79 | ||||
| 80 | temp_tones = [] | 80 | temp_tones = [] | ||
| 81 | current_tone = self.tones[0] + Interval(1) | 81 | current_tone = self.tones[0] + Interval(1) | ||
| 82 | 82 | ||||
| 83 | while current_tone != self.tones[0]: | 83 | while current_tone != self.tones[0]: | ||
| 84 | if current_tone in additional_tones: | 84 | if current_tone in additional_tones: | ||
| 85 | temp_tones.append(current_tone) | 85 | temp_tones.append(current_tone) | ||
| 86 | current_tone = current_tone + Interval(1) | 86 | current_tone = current_tone + Interval(1) | ||
| 87 | 87 | ||||
| 88 | self.tones.extend(temp_tones) | 88 | self.tones.extend(temp_tones) | ||
| 89 | 89 | ||||
| 90 | def __str__(self): | 90 | def __str__(self): | ||
| 91 | return "-".join([str(tone) for tone in self.tones]) | 91 | return "-".join([str(tone) for tone in self.tones]) | ||
| 92 | 92 | ||||
| 93 | def __add__(self, other): | 93 | def __add__(self, other): | ||
| 94 | if isinstance(other, Tone): | 94 | if isinstance(other, Tone): | ||
| 95 | return Chord(*self.tones, other) | 95 | return Chord(*self.tones, other) | ||
| 96 | elif isinstance(other, Chord): | 96 | elif isinstance(other, Chord): | ||
| 97 | return Chord(*self.tones, *other.tones) | 97 | return Chord(*self.tones, *other.tones) | ||
| 98 | raise TypeError("Invalid operation") | 98 | raise TypeError("Invalid operation") | ||
| 99 | 99 | ||||
| 100 | def __sub__(self, other): | 100 | def __sub__(self, other): | ||
| 101 | if isinstance(other, Tone): | 101 | if isinstance(other, Tone): | ||
| 102 | if other not in self.tones: | 102 | if other not in self.tones: | ||
| 103 | raise TypeError(f"Cannot remove tone {other} from chord " | 103 | raise TypeError(f"Cannot remove tone {other} from chord " | ||
| 104 | f"{self}") | 104 | f"{self}") | ||
| 105 | elif len(self.tones) == 2: | 105 | elif len(self.tones) == 2: | ||
| 106 | raise TypeError("Cannot have a chord made of only 1 unique " | 106 | raise TypeError("Cannot have a chord made of only 1 unique " | ||
| 107 | "tone") | 107 | "tone") | ||
| 108 | 108 | ||||
| 109 | return Chord(*[tone for tone in self.tones if tone != other]) | 109 | return Chord(*[tone for tone in self.tones if tone != other]) | ||
| 110 | raise TypeError("Invalid operation") | 110 | raise TypeError("Invalid operation") | ||
| 111 | 111 | ||||
| 112 | def is_major(self) -> bool: | 112 | def is_major(self) -> bool: | ||
| 113 | root_tone_index = self.tones[0].name_index | 113 | root_tone_index = self.tones[0].name_index | ||
| 114 | 114 | ||||
| 115 | for secondary_tone in self.tones[1:]: | 115 | for secondary_tone in self.tones[1:]: | ||
| 116 | temp_interval = Interval(abs(root_tone_index - | 116 | temp_interval = Interval(abs(root_tone_index - | ||
| 117 | secondary_tone.name_index)) | 117 | secondary_tone.name_index)) | ||
| 118 | 118 | ||||
| 119 | if temp_interval == Chord.major_chord_interval: | 119 | if temp_interval == Chord.major_chord_interval: | ||
| 120 | return True | 120 | return True | ||
| 121 | 121 | ||||
| 122 | return False | 122 | return False | ||
| 123 | 123 | ||||
| 124 | def is_minor(self) -> bool: | 124 | def is_minor(self) -> bool: | ||
| 125 | root_tone_index = self.tones[0].name_index | 125 | root_tone_index = self.tones[0].name_index | ||
| 126 | 126 | ||||
| 127 | for secondary_tone in self.tones[1:]: | 127 | for secondary_tone in self.tones[1:]: | ||
| 128 | temp_interval = Interval(abs(root_tone_index - | 128 | temp_interval = Interval(abs(root_tone_index - | ||
| 129 | secondary_tone.name_index)) | 129 | secondary_tone.name_index)) | ||
| 130 | 130 | ||||
| 131 | if temp_interval == Chord.minor_chord_interval: | 131 | if temp_interval == Chord.minor_chord_interval: | ||
| 132 | return True | 132 | return True | ||
| 133 | 133 | ||||
| 134 | return False | 134 | return False | ||
| 135 | 135 | ||||
| 136 | def is_power_chord(self) -> bool: | 136 | def is_power_chord(self) -> bool: | ||
| 137 | root_tone_index = self.tones[0].name_index | 137 | root_tone_index = self.tones[0].name_index | ||
| 138 | 138 | ||||
| 139 | for secondary_tone in self.tones[1:]: | 139 | for secondary_tone in self.tones[1:]: | ||
| 140 | temp_interval = Interval(abs(root_tone_index - | 140 | temp_interval = Interval(abs(root_tone_index - | ||
| 141 | secondary_tone.name_index)) | 141 | secondary_tone.name_index)) | ||
| 142 | 142 | ||||
| 143 | if (temp_interval == Chord.major_chord_interval | 143 | if (temp_interval == Chord.major_chord_interval | ||
| 144 | or temp_interval == Chord.minor_chord_interval): | 144 | or temp_interval == Chord.minor_chord_interval): | ||
| 145 | return False | 145 | return False | ||
| 146 | 146 | ||||
| 147 | return True | 147 | return True | ||
| 148 | 148 | ||||
| 149 | def transposed(self, interval: Interval): | 149 | def transposed(self, interval: Interval): | ||
| 150 | return Chord(*[tone + interval for tone in self.tones]) | 150 | return Chord(*[tone + interval for tone in self.tones]) | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 
 | 
 | |||||||||