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 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|