1class Tone:
  2    CHROMATIC_SCALE = {
  3        "C": 0,
  4        "C#": 1,
  5        "D": 2,
  6        "D#": 3,
  7        "E": 4,
  8        "F": 5,
  9        "F#": 6,
 10        "G": 7,
 11        "G#": 8,
 12        "A": 9,
 13        "A#": 10,
 14        "B": 11,
 15    }
 16
 17    def __init__(self, value):
 18        self.value = value
 19
 20    def __str__(self):
 21        return str(self.value)
 22
 23    # Need this functions because of the unique_tones set.
 24    def __eq__(self, other):
 25        return self.value == other.value
 26
 27    def __hash__(self):
 28        return hash(self.value)
 29
 30    @property
 31    def index(self):
 32        return Tone.CHROMATIC_SCALE[self.value]
 33
 34    @staticmethod
 35    def get_tone_by_index(index):
 36        """ Helper method to get tone name by index."""
 37        for tone, tone_index in Tone.CHROMATIC_SCALE.items():
 38            if tone_index == index:
 39                return tone
 40
 41    def __add__(self, other):
 42        if isinstance(other, Tone):
 43            """Add two tones."""
 44            return Chord(self, other)
 45        elif isinstance(other, Interval):
 46            """Add tone and interval."""
 47            new_index = (self.index + other.interval) % 12
 48            new_value = Tone.get_tone_by_index(new_index)
 49            return Tone(new_value)
 50
 51    def __sub__(self, other):
 52        if isinstance(other, Tone):
 53            """Subtract two tones."""
 54            semitone_diff = (self.index - other.index) % 12
 55            return Interval(semitone_diff)
 56        else:
 57            """Subtract tone and interval."""
 58            new_index = (self.index - other.interval) % 12
 59            new_value = Tone.get_tone_by_index(new_index)
 60            return Tone(new_value)
 61
 62    def __radd__(self, other):
 63        raise TypeError("Invalid operation")
 64
 65
 66class Interval:
 67    interval_names = {
 68        0: "unison",
 69        1: "minor 2nd",
 70        2: "major 2nd",
 71        3: "minor 3rd",
 72        4: "major 3rd",
 73        5: "perfect 4th",
 74        6: "diminished 5th",
 75        7: "perfect 5th",
 76        8: "minor 6th",
 77        9: "major 6th",
 78        10: "minor 7th",
 79        11: "major 7th"
 80    }
 81
 82    def __init__(self, interval):
 83        self.interval = interval % 12
 84        # Divide the integer by 12 because I want to have a valid output also by 13 for example as given in the rule.
 85        self.name = Interval.interval_names[self.interval]
 86
 87    def __str__(self):
 88        return str(self.name)
 89
 90    def __add__(self, other):
 91        new_interval = (self.interval + other.interval) % 12
 92        return Interval(new_interval)
 93
 94    def __neg__(self):
 95        """Enable unary negation of Interval."""
 96        return Interval(-self.interval)
 97
 98
 99class Chord:
100    minor_third_interval = 3
101    major_third_interval = 4
102
103    def __init__(self, root, *tones):
104        self.root = root
105        unique_tones = {tone for tone in tones if tone != root}
106        " So we solve the problem when one of the tones is equal to root."
107
108        if len(unique_tones) < 1:
109            raise TypeError("Cannot have a chord made of only 1 unique tone")
110
111        self.tones = [self.root] + sorted(unique_tones, key=lambda tone: (tone.index - self.root.index) % 12)
112
113    def __str__(self):
114        return "-".join(str(tone) for tone in self.tones)
115
116    def is_minor(self):
117        root_index = self.root.index
118        for tone in self.tones[1:]:  # Skip the root
119            if (tone.index - root_index) % 12 == self.minor_third_interval:
120                return True
121        return False
122
123    def is_major(self):
124        root_index = self.root.index
125        for tone in self.tones[1:]:
126            if (tone.index - root_index) % 12 == self.major_third_interval:
127                return True
128        return False
129
130    def is_power_chord(self):
131        return not self.is_minor() and not self.is_major()
132
133    def __add__(self, other):
134        if isinstance(other, Tone):
135            new_tones = list(self.tones)
136            new_tones.append(other)
137            return Chord(self.root, *new_tones)
138        else:
139            combined_tones = {tone for tone in self.tones}
140            combined_tones.update(other.tones)
141            return Chord(self.root, *combined_tones)
142
143    def __sub__(self, other):
144        if other not in self.tones:
145            raise TypeError(f"Cannot remove tone {other} from chord {self}")
146
147        new_unique_tones = {tone for tone in self.tones if tone != other}
148
149        if len(new_unique_tones) < 2:
150            raise TypeError("Cannot have a chord made of only 1 unique tone")
151
152        return Chord(self.root, *new_unique_tones)
153
154    def transposed(self, interval):
155        transposed_tones = [tone + interval for tone in self.tones]
156        return Chord(transposed_tones[0], *transposed_tones[1:])
..................E........F.........
======================================================================
ERROR: test_add_interval_to_tone_left_side_error (test.TestOperations.test_add_interval_to_tone_left_side_error)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 210, in test_add_interval_to_tone_left_side_error
    Interval(2) + g
    ~~~~~~~~~~~~^~~
  File "/tmp/solution.py", line 91, in __add__
    new_interval = (self.interval + other.interval) % 12
                                    ^^^^^^^^^^^^^^
AttributeError: 'Tone' object has no attribute 'interval'
======================================================================
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
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=1, errors=1)
                            
    
 
    
        Валентина Петрова
         
    
04.11.2024 15:03Ясно! Благодаря! 😁 
                            
                            
                         | 
                            
    
 
    
        Виктор Бечев
         
    
04.11.2024 14:07Използва се да "маскира" достъпът до дадени данни, които изискват изчисляване, така че за външния свят да изглеждат като атрибут.  
Пример:
```
class Vector:
    ...
    def get_length(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    @property
    def length(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
v = Vector(1, 2)
v.get_length() # 2.23606797749979
v.length # 2.23606797749979
```
Второто е за предпочитане, защото прави дължината, въпреки, че трябва да се изчисли динамично, да изглежда като данни, като атрибут, а не като функция.
Не е нужно да го променяш, пак ще го обясним. 
                            
                            
                         | 
                            
    
 
    
        Валентина Петрова
         
    
04.11.2024 13:21Тъй като по време на лекцията не успях да разбера много добре, кога по-точно се използва декораторът @property и защо?
П.С. Ако ще е част от темите на лекцията на 07.11, мога да изчакам до тогова :) 
                            
                            
                         | 
| f | 1 | class Tone: | f | 1 | class Tone: | 
| n | 2 | chromatic_scale = { | n | 2 | CHROMATIC_SCALE = { | 
| 3 | "C": 0, | 3 | "C": 0, | ||
| 4 | "C#": 1, | 4 | "C#": 1, | ||
| 5 | "D": 2, | 5 | "D": 2, | ||
| 6 | "D#": 3, | 6 | "D#": 3, | ||
| 7 | "E": 4, | 7 | "E": 4, | ||
| 8 | "F": 5, | 8 | "F": 5, | ||
| 9 | "F#": 6, | 9 | "F#": 6, | ||
| 10 | "G": 7, | 10 | "G": 7, | ||
| 11 | "G#": 8, | 11 | "G#": 8, | ||
| 12 | "A": 9, | 12 | "A": 9, | ||
| 13 | "A#": 10, | 13 | "A#": 10, | ||
| 14 | "B": 11, | 14 | "B": 11, | ||
| 15 | } | 15 | } | ||
| 16 | 16 | ||||
| 17 | def __init__(self, value): | 17 | def __init__(self, value): | ||
| 18 | self.value = value | 18 | self.value = value | ||
| 19 | 19 | ||||
| 20 | def __str__(self): | 20 | def __str__(self): | ||
| 21 | return str(self.value) | 21 | return str(self.value) | ||
| 22 | 22 | ||||
| n | 23 | "Need this functions because of the unique_tones set." | n | 23 | # Need this functions because of the unique_tones set. | 
| 24 | def __eq__(self, other): | 24 | def __eq__(self, other): | ||
| n | 25 | return isinstance(other, Tone) and self.value == other.value | n | 25 | return self.value == other.value | 
| 26 | 26 | ||||
| 27 | def __hash__(self): | 27 | def __hash__(self): | ||
| 28 | return hash(self.value) | 28 | return hash(self.value) | ||
| 29 | 29 | ||||
| n | n | 30 | @property | ||
| 30 | def get_index(self): | 31 | def index(self): | ||
| 31 | return Tone.chromatic_scale[self.value] | 32 | return Tone.CHROMATIC_SCALE[self.value] | ||
| 32 | 33 | ||||
| 33 | @staticmethod | 34 | @staticmethod | ||
| 34 | def get_tone_by_index(index): | 35 | def get_tone_by_index(index): | ||
| 35 | """ Helper method to get tone name by index.""" | 36 | """ Helper method to get tone name by index.""" | ||
| n | 36 | for tone, tone_index in Tone.chromatic_scale.items(): | n | 37 | for tone, tone_index in Tone.CHROMATIC_SCALE.items(): | 
| 37 | if tone_index == index: | 38 | if tone_index == index: | ||
| 38 | return tone | 39 | return tone | ||
| 39 | 40 | ||||
| 40 | def __add__(self, other): | 41 | def __add__(self, other): | ||
| 41 | if isinstance(other, Tone): | 42 | if isinstance(other, Tone): | ||
| 42 | """Add two tones.""" | 43 | """Add two tones.""" | ||
| 43 | return Chord(self, other) | 44 | return Chord(self, other) | ||
| 44 | elif isinstance(other, Interval): | 45 | elif isinstance(other, Interval): | ||
| 45 | """Add tone and interval.""" | 46 | """Add tone and interval.""" | ||
| n | 46 | new_index = (self.get_index() + other.interval) % 12 | n | 47 | new_index = (self.index + other.interval) % 12 | 
| 47 | new_value = Tone.get_tone_by_index(new_index) | 48 | new_value = Tone.get_tone_by_index(new_index) | ||
| 48 | return Tone(new_value) | 49 | return Tone(new_value) | ||
| 49 | 50 | ||||
| 50 | def __sub__(self, other): | 51 | def __sub__(self, other): | ||
| 51 | if isinstance(other, Tone): | 52 | if isinstance(other, Tone): | ||
| 52 | """Subtract two tones.""" | 53 | """Subtract two tones.""" | ||
| n | 53 | semitone_diff = (self.get_index() - other.get_index()) % 12 | n | 54 | semitone_diff = (self.index - other.index) % 12 | 
| 54 | return Interval(semitone_diff) | 55 | return Interval(semitone_diff) | ||
| 55 | else: | 56 | else: | ||
| 56 | """Subtract tone and interval.""" | 57 | """Subtract tone and interval.""" | ||
| n | 57 | new_index = (self.get_index() - other.interval) % 12 | n | 58 | new_index = (self.index - other.interval) % 12 | 
| 58 | new_value = Tone.get_tone_by_index(new_index) | 59 | new_value = Tone.get_tone_by_index(new_index) | ||
| 59 | return Tone(new_value) | 60 | return Tone(new_value) | ||
| 60 | 61 | ||||
| 61 | def __radd__(self, other): | 62 | def __radd__(self, other): | ||
| 62 | raise TypeError("Invalid operation") | 63 | raise TypeError("Invalid operation") | ||
| 63 | 64 | ||||
| 64 | 65 | ||||
| 65 | class Interval: | 66 | class Interval: | ||
| 66 | interval_names = { | 67 | interval_names = { | ||
| 67 | 0: "unison", | 68 | 0: "unison", | ||
| 68 | 1: "minor 2nd", | 69 | 1: "minor 2nd", | ||
| 69 | 2: "major 2nd", | 70 | 2: "major 2nd", | ||
| 70 | 3: "minor 3rd", | 71 | 3: "minor 3rd", | ||
| 71 | 4: "major 3rd", | 72 | 4: "major 3rd", | ||
| 72 | 5: "perfect 4th", | 73 | 5: "perfect 4th", | ||
| 73 | 6: "diminished 5th", | 74 | 6: "diminished 5th", | ||
| 74 | 7: "perfect 5th", | 75 | 7: "perfect 5th", | ||
| 75 | 8: "minor 6th", | 76 | 8: "minor 6th", | ||
| 76 | 9: "major 6th", | 77 | 9: "major 6th", | ||
| 77 | 10: "minor 7th", | 78 | 10: "minor 7th", | ||
| 78 | 11: "major 7th" | 79 | 11: "major 7th" | ||
| 79 | } | 80 | } | ||
| 80 | 81 | ||||
| 81 | def __init__(self, interval): | 82 | def __init__(self, interval): | ||
| 82 | self.interval = interval % 12 | 83 | self.interval = interval % 12 | ||
| n | 83 | " Divide the integer by 12 because I want to have a valid output also by 13 for example as given in the rule." | n | 84 | # Divide the integer by 12 because I want to have a valid output also by 13 for example as given in the rule. | 
| 84 | self.name = Interval.interval_names.get(self.interval, "unknown") | 85 | self.name = Interval.interval_names[self.interval] | ||
| 85 | 86 | ||||
| 86 | def __str__(self): | 87 | def __str__(self): | ||
| 87 | return str(self.name) | 88 | return str(self.name) | ||
| 88 | 89 | ||||
| 89 | def __add__(self, other): | 90 | def __add__(self, other): | ||
| 90 | new_interval = (self.interval + other.interval) % 12 | 91 | new_interval = (self.interval + other.interval) % 12 | ||
| 91 | return Interval(new_interval) | 92 | return Interval(new_interval) | ||
| 92 | 93 | ||||
| 93 | def __neg__(self): | 94 | def __neg__(self): | ||
| 94 | """Enable unary negation of Interval.""" | 95 | """Enable unary negation of Interval.""" | ||
| 95 | return Interval(-self.interval) | 96 | return Interval(-self.interval) | ||
| 96 | 97 | ||||
| 97 | 98 | ||||
| 98 | class Chord: | 99 | class Chord: | ||
| n | n | 100 | minor_third_interval = 3 | ||
| 101 | major_third_interval = 4 | ||||
| 102 | |||||
| 99 | def __init__(self, root, *tones): | 103 | def __init__(self, root, *tones): | ||
| 100 | self.root = root | 104 | self.root = root | ||
| 101 | unique_tones = {tone for tone in tones if tone != root} | 105 | unique_tones = {tone for tone in tones if tone != root} | ||
| 102 | " So we solve the problem when one of the tones is equal to root." | 106 | " So we solve the problem when one of the tones is equal to root." | ||
| 103 | 107 | ||||
| 104 | if len(unique_tones) < 1: | 108 | if len(unique_tones) < 1: | ||
| 105 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 109 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
| 106 | 110 | ||||
| n | 107 | self.tones = [self.root] + sorted(unique_tones, key=lambda tone: (tone.get_index() - self.root.get_index()) % 12) | n | 111 | self.tones = [self.root] + sorted(unique_tones, key=lambda tone: (tone.index - self.root.index) % 12) | 
| 108 | 112 | ||||
| 109 | def __str__(self): | 113 | def __str__(self): | ||
| 110 | return "-".join(str(tone) for tone in self.tones) | 114 | return "-".join(str(tone) for tone in self.tones) | ||
| 111 | 115 | ||||
| 112 | def is_minor(self): | 116 | def is_minor(self): | ||
| n | 113 | minor_third_interval = 3 | n | ||
| 114 | root_index = self.root.get_index() | 117 | root_index = self.root.index | ||
| 115 | for tone in self.tones[1:]: # Skip the root | 118 | for tone in self.tones[1:]: # Skip the root | ||
| n | 116 | if (tone.get_index() - root_index) % 12 == minor_third_interval: | n | 119 | if (tone.index - root_index) % 12 == self.minor_third_interval: | 
| 117 | return True | 120 | return True | ||
| 118 | return False | 121 | return False | ||
| 119 | 122 | ||||
| 120 | def is_major(self): | 123 | def is_major(self): | ||
| n | 121 | major_third_interval = 4 | n | ||
| 122 | root_index = self.root.get_index() | 124 | root_index = self.root.index | ||
| 123 | for tone in self.tones[1:]: | 125 | for tone in self.tones[1:]: | ||
| n | 124 | if (tone.get_index() - root_index) % 12 == major_third_interval: | n | 126 | if (tone.index - root_index) % 12 == self.major_third_interval: | 
| 125 | return True | 127 | return True | ||
| 126 | return False | 128 | return False | ||
| 127 | 129 | ||||
| 128 | def is_power_chord(self): | 130 | def is_power_chord(self): | ||
| 129 | return not self.is_minor() and not self.is_major() | 131 | return not self.is_minor() and not self.is_major() | ||
| 130 | 132 | ||||
| 131 | def __add__(self, other): | 133 | def __add__(self, other): | ||
| 132 | if isinstance(other, Tone): | 134 | if isinstance(other, Tone): | ||
| 133 | new_tones = list(self.tones) | 135 | new_tones = list(self.tones) | ||
| 134 | new_tones.append(other) | 136 | new_tones.append(other) | ||
| 135 | return Chord(self.root, *new_tones) | 137 | return Chord(self.root, *new_tones) | ||
| 136 | else: | 138 | else: | ||
| 137 | combined_tones = {tone for tone in self.tones} | 139 | combined_tones = {tone for tone in self.tones} | ||
| 138 | combined_tones.update(other.tones) | 140 | combined_tones.update(other.tones) | ||
| 139 | return Chord(self.root, *combined_tones) | 141 | return Chord(self.root, *combined_tones) | ||
| 140 | 142 | ||||
| 141 | def __sub__(self, other): | 143 | def __sub__(self, other): | ||
| 142 | if other not in self.tones: | 144 | if other not in self.tones: | ||
| 143 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | 145 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||
| 144 | 146 | ||||
| 145 | new_unique_tones = {tone for tone in self.tones if tone != other} | 147 | new_unique_tones = {tone for tone in self.tones if tone != other} | ||
| 146 | 148 | ||||
| 147 | if len(new_unique_tones) < 2: | 149 | if len(new_unique_tones) < 2: | ||
| 148 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 150 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
| 149 | 151 | ||||
| 150 | return Chord(self.root, *new_unique_tones) | 152 | return Chord(self.root, *new_unique_tones) | ||
| 151 | 153 | ||||
| 152 | def transposed(self, interval): | 154 | def transposed(self, interval): | ||
| 153 | transposed_tones = [tone + interval for tone in self.tones] | 155 | transposed_tones = [tone + interval for tone in self.tones] | ||
| 154 | return Chord(transposed_tones[0], *transposed_tones[1:]) | 156 | return Chord(transposed_tones[0], *transposed_tones[1:]) | ||
| 155 | 157 | ||||
| t | 156 | t | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
 
  | 
              
  |  |||||||||