1class Tone:
2 def __init__(self, note):
3 self.note = note
4
5 def __str__(self):
6 return str(self.note)
7
8 def __add__(self, other):
9 if isinstance(other, Interval):
10 new_index = (self.get_index() + other.number) % 12
11 return Tone(self.get_note_by_index(new_index))
12 elif isinstance(other, Tone):
13 return Chord(self, other)
14 else:
15 raise TypeError("Invalid operation")
16
17 def __sub__(self, other):
18 if isinstance(other, Interval):
19 new_index = (self.get_index() - other.number) % 12
20 return Tone(self.get_note_by_index(new_index))
21 elif isinstance(other, Tone):
22 interval_number = (self.get_index()-other.get_index()) % 12
23 return Interval(interval_number)
24 else:
25 raise TypeError("Invalid operation")
26
27 def get_index(self):
28 chromatic_scale = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
29 return chromatic_scale.index(self.note)
30
31 @staticmethod
32 def get_note_by_index(index):
33 chromatic_scale = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
34 return chromatic_scale[index]
35
36class Interval:
37 interval_names = {
38 0: "unison",
39 1: "minor 2nd",
40 2: "major 2nd",
41 3: "minor 3rd",
42 4: "major 3rd",
43 5: "perfect 4th",
44 6: "diminished 5th",
45 7: "perfect 5th",
46 8: "minor 6th",
47 9: "major 6th",
48 10: "minor 7th",
49 11: "major 7th",
50 }
51
52 def __init__(self, number):
53 self.number = number
54
55 def __str__(self):
56 return self.interval_names[self.number % 12]
57
58 def __neg__(self):
59 return Interval(-self.number % 12)
60
61 def __add__(self, other):
62 if isinstance(other, Interval):
63 return Interval((self.number + other.number) % 12)
64 else:
65 raise TypeError("Invalid operation")
66
67
68class Chord:
69 def __init__(self, root: Tone, *tones: Tone):
70 self.root = root
71 self.tones = {root.note}
72 for tone in tones:
73 self.tones.add(tone.note)
74
75 if len(self.tones) <= 1:
76 raise TypeError("Cannot have a chord made of only 1 unique tone")
77
78 def __str__(self):
79 sorted_tones = sorted(self.tones, key=lambda x: self.calculate_interval(self.root.note, x))
80 return '-'.join(sorted_tones)
81
82 def is_minor(self):
83 minor_third = Interval(3)
84 for tone in self.tones:
85 if self.calculate_interval(self.root.note, tone) == minor_third.number:
86 return True
87 return False
88
89 def is_major(self):
90 major_third = Interval(4)
91 for tone in self.tones:
92 if self.calculate_interval(self.root.note, tone) == major_third.number:
93 return True
94 return False
95
96 def is_power_chord(self):
97 minor_third = Interval(3)
98 major_third = Interval(4)
99 for tone in self.tones:
100 if (self.calculate_interval(self.root.note, tone) == minor_third.number or
101 self.calculate_interval(self.root.note, tone) == major_third.number):
102 return False
103 return True
104
105 def calculate_interval(self, note1, note2):
106 chromatic_scale = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
107 index1 = chromatic_scale.index(note1)
108 index2 = chromatic_scale.index(note2)
109 return (index2 - index1) % 12
110
111 def __add__(self, other):
112 if isinstance(other, Tone):
113 return Chord(self.root, *[Tone(t) for t in self.tones], other)
114 elif isinstance(other, Chord):
115 return Chord(self.root, *(Tone(t) for t in (self.tones | other.tones)))
116 else:
117 raise TypeError("Invalid operation")
118
119 def __sub__(self, other):
120 if isinstance(other, Tone):
121 if other.note not in self.tones:
122 raise TypeError(f"Cannot remove tone {other} from chord {self}")
123 new_tones = {Tone(t) for t in self.tones if t != other.note}
124 if len(new_tones) <= 1:
125 raise TypeError("Cannot have a chord made of only 1 unique tone")
126 return Chord(self.root, *new_tones)
127 else:
128 raise TypeError("Invalid operation")
129
130 def transposed(self, interval):
131 transposed_tones = []
132 for tone in self.tones:
133 original_tone = Tone(tone)
134 new_tone = original_tone + interval
135 transposed_tones.append(new_tone)
136 new_root = self.root + interval
137 return Chord(new_root, *transposed_tones)
138
...........................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
----------------------------------------------------------------------
Ran 37 tests in 0.001s
FAILED (failures=1)
Виктор Бечев
06.11.2024 14:21Отвъд дребните забележки - кодът е чист и четим.
Едно концептуално вмятане - изборът ти да пазиш в `Chord` стринговите репрезентации на тоновете, вместо обектите `Tone`, работи в този случай, но това е защото работим с изключително прост модел. Ако се нуждаеш от много по-големи парчета функционалност, които се съдържат в класа `Tone` или самите инстанции имат state и някаква форма на идентитет, която е важна - ще е хубаво да пазиш обектите, а не стринговете _(заради което всъщност ти се налага да ги създаваш наново при някои от операциите)_.
Не те съветвам да рискуваш да го оправяш с няколко часа до крайния срок, но просто помисли по темата.
|
06.11.2024 14:13
06.11.2024 14:14
06.11.2024 14:15