Домашни > Pitches love the D > Решения > Решението на Даниел Стефанов

Резултати
9 точки от тестове
0 точки от учител

9 точки общо

32 успешни теста
5 неуспешни теста
Код

  1INPUT_ERROR_MESSAGE = 'Invalid input type: Expected'
  2INVALID_OPERATION_ERROR_MESSAGE = 'Invalid operation'
  3VALID_TONES = (
  4    'C', 'C#', 'D', 'D#', 'E', 'F',
  5    'F#', 'G', 'G#', 'A', 'A#', 'B'
  6)
  7
  8
  9class Tone:
 10
 11    def __init__(self, tone):
 12        self.tone = tone
 13
 14    @property
 15    def tone(self):
 16        return self._tone
 17
 18    @tone.setter
 19    def tone(self, value):
 20        if value not in VALID_TONES:
 21            raise ValueError(f'{INPUT_ERROR_MESSAGE} one of {VALID_TONES}')
 22        self._tone = value
 23
 24    def __str__(self):
 25        return self.tone
 26
 27    def __add__(self, other):
 28        if isinstance(other, Tone):
 29            return Chord(self, other)
 30        elif isinstance(other, Interval):
 31            starting_index = VALID_TONES.index(self.tone)
 32            shifted_tone = VALID_TONES[(starting_index + other.length) % INTERVALS_COUNT]
 33            return Tone(shifted_tone)
 34
 35        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)
 36
 37    def __sub__(self, other):
 38        if isinstance(other, Tone):
 39            first_tone_index = VALID_TONES.index(self.tone)
 40            second_tone_index = VALID_TONES.index(other.tone)
 41            interval_len = first_tone_index - second_tone_index
 42
 43            if first_tone_index <= second_tone_index:
 44                interval_len += INTERVALS_COUNT
 45
 46            return Interval(interval_len)
 47        elif isinstance(other, Interval):
 48            starting_index = VALID_TONES.index(self.tone)
 49            shifted_tone = VALID_TONES[starting_index - other.length]
 50            return Tone(shifted_tone)
 51
 52        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)
 53
 54
 55# The indices correspond to the interval length
 56INTERVAL_NAMES = (
 57    'unison', 'minor 2nd', 'major 2nd', 'minor 3rd',
 58    'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th',
 59    'minor 6th', 'major 6th', 'minor 7th', 'major 7th'
 60)
 61
 62SHORTEST_INTERVAL = 0
 63LONGEST_INTERVAL = 11
 64INTERVALS_COUNT = 12
 65
 66
 67class Interval:
 68
 69    def __init__(self, length, is_direction_forward=True):
 70        self._set_length(length)
 71        self._is_direction_forward = is_direction_forward
 72
 73    def __neg__(self):
 74        return Interval(self.length, False)
 75
 76    @property
 77    def is_direction_forward(self):
 78        return self._is_direction_forward
 79
 80    @property
 81    def length(self):
 82        return self._length
 83
 84    def _set_length(self, value):
 85        if not isinstance(value, int):
 86            raise TypeError(f'{INPUT_ERROR_MESSAGE} {str(int)}')
 87
 88        if value < SHORTEST_INTERVAL:
 89            raise ValueError(f'{INPUT_ERROR_MESSAGE} a non-negative integer')
 90
 91        self._length = value % INTERVALS_COUNT
 92
 93    def __str__(self):
 94        return INTERVAL_NAMES[self.length]
 95
 96    def __add__(self, other):
 97        if isinstance(other, Interval):
 98            return Interval(self.length + other.length)
 99        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)
100
101
102MIN_TONES_COUNT_IN_CHORD = 2
103TONES_COUNT_ERROR_MESSAGE = 'Cannot have a chord made of only 1 unique tone'
104
105
106def rearrange_tuple(input_tuple, skip_index):
107    start_index = skip_index + 1
108    end_index = skip_index - 1
109
110
111    if start_index <= end_index:
112        result_tuple = input_tuple[start_index:end_index + 1]
113    else:
114        result_tuple = input_tuple[start_index:] + input_tuple[:end_index + 1]
115
116    if skip_index is not None:
117        skip_tone = input_tuple[skip_index]
118        result_tuple = [tone for tone in result_tuple if tone != skip_tone]
119
120    return result_tuple
121
122
123class Chord:
124    def __init__(self, main_tone, *args, **kwargs):
125        secondary_tones = list(args) + list(kwargs.values())
126        self.tones = Chord._extract_and_sort_tones(main_tone, secondary_tones)
127
128    @property
129    def tones(self):
130        return self._tones
131
132    @property
133    def main_tone(self):
134        return self._tones[0]
135
136    @property
137    def secondary_tones(self):
138        return self._tones[1:]
139
140    @tones.setter
141    def tones(self, value):
142        if len(value) < MIN_TONES_COUNT_IN_CHORD:
143            raise TypeError(TONES_COUNT_ERROR_MESSAGE)
144
145        self._tones = value
146
147    @staticmethod
148    def _extract_and_sort_tones(main_tone, secondary_tones=None):
149
150        if secondary_tones is None:
151            return list(main_tone)
152
153        unique_secondary_tones = set(filter(lambda tone: str(tone) != str(main_tone), secondary_tones))
154        unique_secondary_tones_strs = [str(tone) for tone in unique_secondary_tones]
155
156        main_tone_index = VALID_TONES.index(str(main_tone))
157        rearranged_tones = rearrange_tuple(VALID_TONES, main_tone_index)
158
159        sorted_tones = [main_tone]
160
161        for tone in rearranged_tones:
162            if tone in unique_secondary_tones_strs:
163                sorted_tones.append(next((t for t in unique_secondary_tones if str(t) == tone)))
164
165        return sorted_tones
166
167    def __str__(self):
168        return '-'.join([str(tone) for tone in self.tones])
169
170    def _contains_interval(self, interval):
171        first_possible_tone = VALID_TONES[self.tones.index(self.main_tone) - interval.length]
172        second_possible_tone = VALID_TONES[self.tones.index(self.main_tone) + interval.length]
173
174        if any(str(tone) in [first_possible_tone, second_possible_tone] for tone in self.tones):
175            return True
176
177        return False
178
179    def is_minor(self):
180        return self._contains_interval(Interval(3))
181
182    def is_major(self):
183        return self._contains_interval(Interval(4))
184
185    def is_power_chord(self):
186        return not self.is_minor() and not self.is_major()
187
188    def __add__(self, other):
189        if isinstance(other, Tone):
190            return Chord(self.main_tone, *self.secondary_tones, other)
191        elif isinstance(other, Chord):
192            return Chord(self.main_tone, *self.secondary_tones, *other.tones)
193
194    def __sub__(self, tone_to_remove):
195        if str(tone_to_remove) not in [str(tone) for tone in self.tones]:
196            raise TypeError(f'Cannot remove tone {str(tone_to_remove)} from chord {str(self)}')
197
198        if len(self.tones) <= MIN_TONES_COUNT_IN_CHORD:
199            raise TypeError(TONES_COUNT_ERROR_MESSAGE)
200
201        if str(self.main_tone) == str(tone_to_remove):
202            return Chord(self.tones[1], *self.secondary_tones)
203
204        updated_tones = [tone for tone in self.secondary_tones if str(tone) != str(tone_to_remove)]
205        return Chord(self.main_tone, *updated_tones)
206
207    def transposed(self, interval):
208        if interval.is_direction_forward:
209            shifted_secondary_tones = [tone + interval for tone in self.secondary_tones]
210            return Chord(self.main_tone + interval, *shifted_secondary_tones)
211
212        shifted_secondary_tones = [tone - interval for tone in self.secondary_tones]
213        return Chord(self.main_tone - interval, *shifted_secondary_tones)

....FFF....F...............F.........
======================================================================
FAIL: test_is_major (test.TestBasicChordFunctionality.test_is_major)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 103, in test_is_major
self.assertFalse(a_minor_chord.is_major())
AssertionError: True is not false

======================================================================
FAIL: test_is_minor (test.TestBasicChordFunctionality.test_is_minor)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 94, in test_is_minor
self.assertFalse(a_major_chord.is_minor())
AssertionError: True is not false

======================================================================
FAIL: test_is_power_chord (test.TestBasicChordFunctionality.test_is_power_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 124, in test_is_power_chord
self.assertTrue(a_kind_of_power_chord.is_power_chord())
AssertionError: False is not true

======================================================================
FAIL: test_interval_negative (test.TestBasicIntervalFunctionality.test_interval_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 51, in test_interval_negative
self.assertEqual(str(minor_2nd), "minor 2nd")
AssertionError: 'major 7th' != 'minor 2nd'
- major 7th
+ minor 2nd

======================================================================
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=5)

Дискусия
Виктор Бечев
04.11.2024 14:18

Точно това имахме предвид и да, вариантите за имплементиране са няколко, тъй като Python минава по определен ред през дъндърите на двата обекта от двете страни на операторът.
Даниел Стефанов
04.11.2024 11:50

Добре а тук след описанието на събиране на тон с интервал има следната забележка: Забележка: Искаме това да работи само когато тонът е отляво на знакът за събиране. Когато тонът е отдясно, го приемаме за недефинирано поведение и искаме да се възбуди TypeError с текст "Invalid operation". Семпло, но няма да ви вгорчаваме живота… Повече. На мен идеята ми беше да покрия тази забележка чрез __radd__ в Tone. Тоест едиснтвеното друго решение което се сещам e в __add__ на Interval да хвърля тази грешка. Това ли сте имали предвид в условието?
Виктор Бечев
04.11.2024 11:42

Добре, аз пък разбрах и твоята идея след двата коментара. Принципно при операция, при която имаме обекта отдясно - `__radd__` и производните се викат с приоритет, така че не съм сигурен, че е необходимо, but if it works - it works.
Даниел Стефанов
04.11.2024 10:34

Сега седнах да пиша unit test-ове и май разбрах какво имате предвид. NotImplemented е някакъв сингълтън клас и когато тествам конкретната функция __аdd__ тя не хвърля грешка а връща NotImplemented. По време на изптлнение на програмата ще се пробва да извика __radd__ и ще си върне правилната грешка, но когато тестваме това поведение не се активира и при тестване на __аdd__ ни се връща NonImplemented и тестове нямаше да ми минават като тествате дали __add__ хвърля грешка XD
Даниел Стефанов
04.11.2024 09:42

Идеята за NotImplemented беше че ако Тone("C#") + 5 не може да се хелдълне от __add__ ще се пробва да изпълни 5 + Tone("C#") чрез __radd__ функцията. Там вече raise-вам правилната грешка. Просто ми се видя като по-добър дизайн. В случая няма някаква полза, но мислех, че е по-добре така да се направи.
История

f1INPUT_ERROR_MESSAGE = 'Invalid input type: Expected'f1INPUT_ERROR_MESSAGE = 'Invalid input type: Expected'
2INVALID_OPERATION_ERROR_MESSAGE = 'Invalid operation'2INVALID_OPERATION_ERROR_MESSAGE = 'Invalid operation'
3VALID_TONES = (3VALID_TONES = (
4    'C', 'C#', 'D', 'D#', 'E', 'F',4    'C', 'C#', 'D', 'D#', 'E', 'F',
5    'F#', 'G', 'G#', 'A', 'A#', 'B'5    'F#', 'G', 'G#', 'A', 'A#', 'B'
6)6)
nn7 
78
8class Tone:9class Tone:
910
10    def __init__(self, tone):11    def __init__(self, tone):
11        self.tone = tone12        self.tone = tone
1213
13    @property14    @property
14    def tone(self):15    def tone(self):
15        return self._tone16        return self._tone
1617
17    @tone.setter18    @tone.setter
18    def tone(self, value):19    def tone(self, value):
19        if value not in VALID_TONES:20        if value not in VALID_TONES:
20            raise ValueError(f'{INPUT_ERROR_MESSAGE} one of {VALID_TONES}')21            raise ValueError(f'{INPUT_ERROR_MESSAGE} one of {VALID_TONES}')
21        self._tone = value22        self._tone = value
2223
23    def __str__(self):24    def __str__(self):
24        return self.tone25        return self.tone
2526
26    def __add__(self, other):27    def __add__(self, other):
27        if isinstance(other, Tone):28        if isinstance(other, Tone):
28            return Chord(self, other)29            return Chord(self, other)
29        elif isinstance(other, Interval):30        elif isinstance(other, Interval):
30            starting_index = VALID_TONES.index(self.tone)31            starting_index = VALID_TONES.index(self.tone)
31            shifted_tone = VALID_TONES[(starting_index + other.length) % INTERVALS_COUNT]32            shifted_tone = VALID_TONES[(starting_index + other.length) % INTERVALS_COUNT]
32            return Tone(shifted_tone)33            return Tone(shifted_tone)
3334
n34        return NotImplementedn
35 
36    def __radd__(self, other):
37        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)35        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)
3836
39    def __sub__(self, other):37    def __sub__(self, other):
40        if isinstance(other, Tone):38        if isinstance(other, Tone):
41            first_tone_index = VALID_TONES.index(self.tone)39            first_tone_index = VALID_TONES.index(self.tone)
42            second_tone_index = VALID_TONES.index(other.tone)40            second_tone_index = VALID_TONES.index(other.tone)
43            interval_len = first_tone_index - second_tone_index41            interval_len = first_tone_index - second_tone_index
4442
45            if first_tone_index <= second_tone_index:43            if first_tone_index <= second_tone_index:
46                interval_len += INTERVALS_COUNT44                interval_len += INTERVALS_COUNT
4745
48            return Interval(interval_len)46            return Interval(interval_len)
49        elif isinstance(other, Interval):47        elif isinstance(other, Interval):
50            starting_index = VALID_TONES.index(self.tone)48            starting_index = VALID_TONES.index(self.tone)
51            shifted_tone = VALID_TONES[starting_index - other.length]49            shifted_tone = VALID_TONES[starting_index - other.length]
52            return Tone(shifted_tone)50            return Tone(shifted_tone)
5351
n54        return NotImplementedn
55 
56    def __rsub__(self, other):
57        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)52        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)
5853
5954
60# The indices correspond to the interval length55# The indices correspond to the interval length
61INTERVAL_NAMES = (56INTERVAL_NAMES = (
62    'unison', 'minor 2nd', 'major 2nd', 'minor 3rd',57    'unison', 'minor 2nd', 'major 2nd', 'minor 3rd',
63    'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th',58    'major 3rd', 'perfect 4th', 'diminished 5th', 'perfect 5th',
64    'minor 6th', 'major 6th', 'minor 7th', 'major 7th'59    'minor 6th', 'major 6th', 'minor 7th', 'major 7th'
65)60)
6661
67SHORTEST_INTERVAL = 062SHORTEST_INTERVAL = 0
68LONGEST_INTERVAL = 1163LONGEST_INTERVAL = 11
69INTERVALS_COUNT = 1264INTERVALS_COUNT = 12
7065
nn66 
71class Interval:67class Interval:
7268
73    def __init__(self, length, is_direction_forward=True):69    def __init__(self, length, is_direction_forward=True):
n74        self.length = lengthn70        self._set_length(length)
75        self.is_direction_forward = is_direction_forward71        self._is_direction_forward = is_direction_forward
7672
77    def __neg__(self):73    def __neg__(self):
78        return Interval(self.length, False)74        return Interval(self.length, False)
7975
80    @property76    @property
81    def is_direction_forward(self):77    def is_direction_forward(self):
82        return self._is_direction_forward78        return self._is_direction_forward
8379
n84    @is_direction_forward.settern
85    def is_direction_forward(self, value):
86        self._is_direction_forward = value
87 
88    @property80    @property
89    def length(self):81    def length(self):
90        return self._length82        return self._length
9183
n92    @length.settern
93    def length(self, value):84    def _set_length(self, value):
94        if not isinstance(value, int):85        if not isinstance(value, int):
95            raise TypeError(f'{INPUT_ERROR_MESSAGE} {str(int)}')86            raise TypeError(f'{INPUT_ERROR_MESSAGE} {str(int)}')
9687
97        if value < SHORTEST_INTERVAL:88        if value < SHORTEST_INTERVAL:
98            raise ValueError(f'{INPUT_ERROR_MESSAGE} a non-negative integer')89            raise ValueError(f'{INPUT_ERROR_MESSAGE} a non-negative integer')
9990
100        self._length = value % INTERVALS_COUNT91        self._length = value % INTERVALS_COUNT
10192
102    def __str__(self):93    def __str__(self):
103        return INTERVAL_NAMES[self.length]94        return INTERVAL_NAMES[self.length]
10495
105    def __add__(self, other):96    def __add__(self, other):
nn97        if isinstance(other, Interval):
106        return Interval(self.length + other.length)98            return Interval(self.length + other.length)
99        raise TypeError(INVALID_OPERATION_ERROR_MESSAGE)
107100
108101
109MIN_TONES_COUNT_IN_CHORD = 2102MIN_TONES_COUNT_IN_CHORD = 2
110TONES_COUNT_ERROR_MESSAGE = 'Cannot have a chord made of only 1 unique tone'103TONES_COUNT_ERROR_MESSAGE = 'Cannot have a chord made of only 1 unique tone'
111104
112105
113def rearrange_tuple(input_tuple, skip_index):106def rearrange_tuple(input_tuple, skip_index):
114    start_index = skip_index + 1107    start_index = skip_index + 1
115    end_index = skip_index - 1108    end_index = skip_index - 1
nn109 
110 
116    if start_index <= end_index:111    if start_index <= end_index:
n117        result = input_tuple[start_index:end_index + 1]n112        result_tuple = input_tuple[start_index:end_index + 1]
118    else:113    else:
n119        result = input_tuple[start_index:] + input_tuple[:end_index + 1]n114        result_tuple = input_tuple[start_index:] + input_tuple[:end_index + 1]
120115
121    if skip_index is not None:116    if skip_index is not None:
122        skip_tone = input_tuple[skip_index]117        skip_tone = input_tuple[skip_index]
n123        result = [tone for tone in result if tone != skip_tone]n118        result_tuple = [tone for tone in result_tuple if tone != skip_tone]
124119
t125    return resultt120    return result_tuple
126121
127122
128class Chord:123class Chord:
129    def __init__(self, main_tone, *args, **kwargs):124    def __init__(self, main_tone, *args, **kwargs):
130        secondary_tones = list(args) + list(kwargs.values())125        secondary_tones = list(args) + list(kwargs.values())
131        self.tones = Chord._extract_and_sort_tones(main_tone, secondary_tones)126        self.tones = Chord._extract_and_sort_tones(main_tone, secondary_tones)
132127
133    @property128    @property
134    def tones(self):129    def tones(self):
135        return self._tones130        return self._tones
136131
137    @property132    @property
138    def main_tone(self):133    def main_tone(self):
139        return self._tones[0]134        return self._tones[0]
140135
141    @property136    @property
142    def secondary_tones(self):137    def secondary_tones(self):
143        return self._tones[1:]138        return self._tones[1:]
144139
145    @tones.setter140    @tones.setter
146    def tones(self, value):141    def tones(self, value):
147        if len(value) < MIN_TONES_COUNT_IN_CHORD:142        if len(value) < MIN_TONES_COUNT_IN_CHORD:
148            raise TypeError(TONES_COUNT_ERROR_MESSAGE)143            raise TypeError(TONES_COUNT_ERROR_MESSAGE)
149144
150        self._tones = value145        self._tones = value
151146
152    @staticmethod147    @staticmethod
153    def _extract_and_sort_tones(main_tone, secondary_tones=None):148    def _extract_and_sort_tones(main_tone, secondary_tones=None):
154149
155        if secondary_tones is None:150        if secondary_tones is None:
156            return list(main_tone)151            return list(main_tone)
157152
158        unique_secondary_tones = set(filter(lambda tone: str(tone) != str(main_tone), secondary_tones))153        unique_secondary_tones = set(filter(lambda tone: str(tone) != str(main_tone), secondary_tones))
159        unique_secondary_tones_strs = [str(tone) for tone in unique_secondary_tones]154        unique_secondary_tones_strs = [str(tone) for tone in unique_secondary_tones]
160155
161        main_tone_index = VALID_TONES.index(str(main_tone))156        main_tone_index = VALID_TONES.index(str(main_tone))
162        rearranged_tones = rearrange_tuple(VALID_TONES, main_tone_index)157        rearranged_tones = rearrange_tuple(VALID_TONES, main_tone_index)
163158
164        sorted_tones = [main_tone]159        sorted_tones = [main_tone]
165160
166        for tone in rearranged_tones:161        for tone in rearranged_tones:
167            if tone in unique_secondary_tones_strs:162            if tone in unique_secondary_tones_strs:
168                sorted_tones.append(next((t for t in unique_secondary_tones if str(t) == tone)))163                sorted_tones.append(next((t for t in unique_secondary_tones if str(t) == tone)))
169164
170        return sorted_tones165        return sorted_tones
171166
172    def __str__(self):167    def __str__(self):
173        return '-'.join([str(tone) for tone in self.tones])168        return '-'.join([str(tone) for tone in self.tones])
174169
175    def _contains_interval(self, interval):170    def _contains_interval(self, interval):
176        first_possible_tone = VALID_TONES[self.tones.index(self.main_tone) - interval.length]171        first_possible_tone = VALID_TONES[self.tones.index(self.main_tone) - interval.length]
177        second_possible_tone = VALID_TONES[self.tones.index(self.main_tone) + interval.length]172        second_possible_tone = VALID_TONES[self.tones.index(self.main_tone) + interval.length]
178173
179        if any(str(tone) in [first_possible_tone, second_possible_tone] for tone in self.tones):174        if any(str(tone) in [first_possible_tone, second_possible_tone] for tone in self.tones):
180            return True175            return True
181176
182        return False177        return False
183178
184    def is_minor(self):179    def is_minor(self):
185        return self._contains_interval(Interval(3))180        return self._contains_interval(Interval(3))
186181
187    def is_major(self):182    def is_major(self):
188        return self._contains_interval(Interval(4))183        return self._contains_interval(Interval(4))
189184
190    def is_power_chord(self):185    def is_power_chord(self):
191        return not self.is_minor() and not self.is_major()186        return not self.is_minor() and not self.is_major()
192187
193    def __add__(self, other):188    def __add__(self, other):
194        if isinstance(other, Tone):189        if isinstance(other, Tone):
195            return Chord(self.main_tone, *self.secondary_tones, other)190            return Chord(self.main_tone, *self.secondary_tones, other)
196        elif isinstance(other, Chord):191        elif isinstance(other, Chord):
197            return Chord(self.main_tone, *self.secondary_tones, *other.tones)192            return Chord(self.main_tone, *self.secondary_tones, *other.tones)
198193
199    def __sub__(self, tone_to_remove):194    def __sub__(self, tone_to_remove):
200        if str(tone_to_remove) not in [str(tone) for tone in self.tones]:195        if str(tone_to_remove) not in [str(tone) for tone in self.tones]:
201            raise TypeError(f'Cannot remove tone {str(tone_to_remove)} from chord {str(self)}')196            raise TypeError(f'Cannot remove tone {str(tone_to_remove)} from chord {str(self)}')
202197
203        if len(self.tones) <= MIN_TONES_COUNT_IN_CHORD:198        if len(self.tones) <= MIN_TONES_COUNT_IN_CHORD:
204            raise TypeError(TONES_COUNT_ERROR_MESSAGE)199            raise TypeError(TONES_COUNT_ERROR_MESSAGE)
205200
206        if str(self.main_tone) == str(tone_to_remove):201        if str(self.main_tone) == str(tone_to_remove):
207            return Chord(self.tones[1], *self.secondary_tones)202            return Chord(self.tones[1], *self.secondary_tones)
208203
209        updated_tones = [tone for tone in self.secondary_tones if str(tone) != str(tone_to_remove)]204        updated_tones = [tone for tone in self.secondary_tones if str(tone) != str(tone_to_remove)]
210        return Chord(self.main_tone, *updated_tones)205        return Chord(self.main_tone, *updated_tones)
211206
212    def transposed(self, interval):207    def transposed(self, interval):
213        if interval.is_direction_forward:208        if interval.is_direction_forward:
214            shifted_secondary_tones = [tone + interval for tone in self.secondary_tones]209            shifted_secondary_tones = [tone + interval for tone in self.secondary_tones]
215            return Chord(self.main_tone + interval, *shifted_secondary_tones)210            return Chord(self.main_tone + interval, *shifted_secondary_tones)
216211
217        shifted_secondary_tones = [tone - interval for tone in self.secondary_tones]212        shifted_secondary_tones = [tone - interval for tone in self.secondary_tones]
218        return Chord(self.main_tone - interval, *shifted_secondary_tones)213        return Chord(self.main_tone - interval, *shifted_secondary_tones)
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op