1TONES_ORDER = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
2
3
4class Tone:
5 """Represents a musical tone."""
6
7 def __init__(self, name):
8 self.name = name
9
10 def __str__(self):
11 """Return the name of the tone."""
12 return f"{self.name}"
13
14 def __add__(self, other):
15 """Add a Tone or Interval to this Tone and return a new Tone or Chord."""
16 if isinstance(other, Tone):
17 return Chord(self, other)
18
19 if isinstance(other, Interval):
20 index = (TONES_ORDER.index(self.name) + other.semitones_count) % len(TONES_ORDER)
21 return Tone(TONES_ORDER[index])
22
23 def __sub__(self, other):
24 """Subtract a Tone or Interval from this Tone, returning an Interval or a new Tone."""
25 if isinstance(other, Tone):
26 index = TONES_ORDER.index(self.name) - TONES_ORDER.index(other.name)
27 return Interval(index % len(TONES_ORDER))
28
29 if isinstance(other, Interval):
30 index = (TONES_ORDER.index(self.name) - other.semitones_count) % len(TONES_ORDER)
31 return Tone(TONES_ORDER[index])
32
33 def __hash__(self):
34 return hash(self.name)
35
36 def __eq__(self, other):
37 if isinstance(other, Tone):
38 return self.name == other.name
39 return False
40
41
42class Interval:
43 """Represents a musical interval defined by the number of semitones."""
44
45 _INTERVAL_NAMES = {
46 0: "unison",
47 1: "minor 2nd",
48 2: "major 2nd",
49 3: "minor 3rd",
50 4: "major 3rd",
51 5: "perfect 4th",
52 6: "diminished 5th",
53 7: "perfect 5th",
54 8: "minor 6th",
55 9: "major 6th",
56 10: "minor 7th",
57 11: "major 7th"
58 }
59
60 def __init__(self, semitones_count):
61 self.semitones_count = semitones_count
62
63 def __str__(self):
64 """Return the name of the interval."""
65 return Interval._INTERVAL_NAMES[self.semitones_count % len(Interval._INTERVAL_NAMES)]
66
67 def __add__(self, other):
68 """Add two intervals."""
69 if isinstance(other, Tone):
70 raise TypeError("Invalid operation")
71
72 if isinstance(other, Interval):
73 return Interval(self.semitones_count + other.semitones_count)
74
75 def __sub__(self, other):
76 if isinstance(other, Tone):
77 raise TypeError("Invalid operation")
78
79 def __neg__(self):
80 """Return the interval in opposite direction."""
81 return Interval(-self.semitones_count)
82
83
84class Chord:
85 """Represents a musical chord built from a root tone and additional tones."""
86
87 def __init__(self, root_tone, *other_tones):
88 self.root = root_tone
89 self.other_tones = list(filter(lambda tone: tone != self.root, other_tones))
90 if len(self.other_tones) == 0:
91 raise TypeError("Cannot have a chord made of only 1 unique tone")
92 self._sort_other_tones_relative_to_root()
93
94 def __str__(self):
95 """Return a representation of the chord's tones."""
96 return (
97 f"{self.root.name}-{"-".join(map(str, self.other_tones))}"
98 if len(self.other_tones) != 0 else self.root.name
99 )
100
101 def _sort_other_tones_relative_to_root(self):
102 root_index = TONES_ORDER.index(self.root.name)
103 self.other_tones.sort(
104 key=lambda tone: (TONES_ORDER.index(tone.name) - root_index) % len(TONES_ORDER)
105 )
106
107 def __add__(self, other):
108 """Return a Chord with the added tones."""
109 if isinstance(other, Tone):
110 tones = set(self.other_tones) | {other}
111 return Chord(self.root, *tones)
112
113 if isinstance(other, Chord):
114 tones = set(self.other_tones) | {other.root} | set(other.other_tones)
115 return Chord(self.root, *tones)
116
117 def __sub__(self, other):
118 """Return a new Chord with the tone removed."""
119 if isinstance(other, Tone):
120 if self.root != other and other not in self.other_tones:
121 raise TypeError(f"Cannot remove tone {str(other)} from chord {str(self)}")
122
123 if self.root == other:
124 tones = filter(lambda tone: tone != other, self.other_tones)
125 return Chord(self.other_tones[0], *tones)
126
127 tones = filter(lambda tone: tone != other, self.other_tones)
128 return Chord(self.root, *tones)
129
130 def _check_if_tones_form_interval(self, interval_name):
131 for tone in self.other_tones:
132 if str(tone - self.root) == interval_name:
133 return True
134 return False
135
136 def is_minor(self):
137 """Check if chord is minor."""
138 return self._check_if_tones_form_interval("minor 3rd")
139
140 def is_major(self):
141 """Check if chord is major."""
142 return self._check_if_tones_form_interval("major 3rd")
143
144 def is_power_chord(self):
145 """Check if chord is a power chord."""
146 return not self.is_minor() and not self.is_major()
147
148 def transposed(self, interval):
149 """Return a new Chord transposed by a given interval."""
150 transposed_tones = map(lambda tone: tone + interval, self.other_tones)
151 return Chord(self.root + interval, *transposed_tones)
...F.................................
======================================================================
FAIL: test_chord_tone_repetition (test.TestBasicChordFunctionality.test_chord_tone_repetition)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 74, in test_chord_tone_repetition
self.assertEqual(str(a_minor_chord), "A-C-E")
AssertionError: 'A-C-C-C-C-C-C-C-C-C-C-C-C-C-C-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E' != 'A-C-E'
- A-C-C-C-C-C-C-C-C-C-C-C-C-C-C-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E-E
+ A-C-E
----------------------------------------------------------------------
Ran 37 tests in 0.001s
FAILED (failures=1)
Виктор Бечев
06.11.2024 17:23Няма, и на това домашно го казваме, всичко до 103 символа е окей.
|
Стефан Шиваров
06.11.2024 17:19На миналото предизвикателство ми бяхте върнали като коментар, че можем да приемем, че лимита за дължина на редовете е 100 символа, понеже 79 ми идва малко досадно :D... Надявам се не е проблем
|
07.11.2024 11:26
07.11.2024 11:27
07.11.2024 11:29