1DEFINED_TONES = (
  2    "C", "C#", "D", "D#", "E", "F",
  3    "F#", "G", "G#", "A", "A#", "B"
  4    )
  5DEFINED_INTERVALS = (
  6    "unison", "minor 2nd", "major 2nd",
  7    "minor 3rd", "major 3rd", "perfect 4th",
  8    "diminished 5th", "perfect 5th", "minor 6th",
  9    "major 6th", "minor 7th", "major 7th"
 10    )
 11TOTAL_TONES_COUNT = 12
 12
 13
 14class Tone:
 15
 16    def __init__(self, note):
 17        self.note = note
 18    
 19    def __str__(self):
 20        return self.note
 21       
 22    def __add__(self, other):
 23        if isinstance(other, Tone):
 24            return Chord(self, other)
 25        elif isinstance(other, Interval):
 26            note_idx = (DEFINED_TONES.index(self.note) 
 27                        + other.number_of_semitones)
 28            return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT])
 29        
 30    def __sub__(self, other):
 31        if isinstance(other, Tone):
 32            return Interval(DEFINED_TONES.index(self.note) 
 33                            - DEFINED_TONES.index(other.note))
 34        elif isinstance(other, Interval):
 35            note_idx = (DEFINED_TONES.index(self.note) 
 36                        - other.number_of_semitones)
 37            return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT])
 38
 39    def __eq__(self, value):
 40        return self.note == value.note
 41        
 42
 43class Interval:
 44
 45    def __init__(self, number_of_semitones):
 46        self.number_of_semitones = number_of_semitones % TOTAL_TONES_COUNT
 47        self.semitone = DEFINED_INTERVALS[self.number_of_semitones]
 48    
 49    def __str__(self):
 50        return self.semitone
 51    
 52    def __add__(self, other):
 53        if isinstance(other, Interval):
 54            return Interval(self.number_of_semitones 
 55                            + other.number_of_semitones)
 56        elif isinstance(other, Tone):
 57            raise TypeError("Invalid operation")
 58    
 59    def __sub__(self, other):
 60        if isinstance(other, Tone):
 61            raise TypeError("Invalid operation")
 62    
 63    def __neg__(self):
 64        return Interval(-self.number_of_semitones)
 65
 66    
 67class Chord:
 68
 69    def __init__(self, root, *tones):
 70        self.root = root
 71        self.secondary_tones = []
 72        for tone in tones:
 73            if tone not in self.secondary_tones:
 74                self.secondary_tones.append(tone)        
 75        if root in self.secondary_tones:
 76            self.secondary_tones.remove(root)
 77        if not self.secondary_tones:
 78            raise TypeError("Cannot have a chord made of only 1 unique tone")
 79        self.secondary_tones.sort(key=lambda x: DEFINED_TONES.index(x.note))
 80
 81    def __str__(self):
 82        tones_concat = "-".join(tone.note for tone in self.secondary_tones)
 83        return f"{str(self.root)}-{tones_concat}"
 84    
 85    def __add__(self, other):
 86        if isinstance(other, Tone):
 87            return Chord(self.root,
 88                         *self.secondary_tones,
 89                         other)
 90        elif isinstance(other, Chord):
 91            return Chord(self.root,
 92                         *self.secondary_tones,
 93                         other.root,
 94                         *other.secondary_tones)
 95    
 96    def __sub__(self, other):
 97        if not isinstance(other, Tone):
 98            return
 99        if (other not in self.secondary_tones and 
100            not (other == self.root)):
101            raise TypeError(
102                f"Cannot remove tone {str(other)} from chord {str(self)}"
103                )
104        if other == self.root:
105            return Chord(self.secondary_tones[0],
106                         *self.secondary_tones[1:])
107        else:
108            modified_secondary_tones = self.secondary_tones[:]
109            modified_secondary_tones.remove(other)
110            return Chord(self.root,
111                         *modified_secondary_tones)
112
113    def transposed(self, interval):
114        modified_secondary_tones = [
115            (tone + interval) for tone in self.secondary_tones
116            ]
117        return Chord(self.root + interval, *modified_secondary_tones)
118
119    def is_minor(self):
120        root_idx = DEFINED_TONES.index(self.root.note)
121        minor_3rd_diff = DEFINED_INTERVALS.index("minor 3rd")
122        for tone in self.secondary_tones:
123            tone_idx = DEFINED_TONES.index(tone.note)
124            if abs(root_idx - tone_idx) == minor_3rd_diff:
125                return True
126        return False
127
128    def is_major(self):
129        root_idx = DEFINED_TONES.index(self.root.note)
130        major_3rd_diff = DEFINED_INTERVALS.index("major 3rd")
131        for tone in self.secondary_tones:
132            tone_idx = DEFINED_TONES.index(tone.note)
133            if abs(root_idx - tone_idx) == major_3rd_diff:
134                return True
135        return False
136
137    def is_power_chord(self):
138        return not (self.is_major() or self.is_minor())
.F..FFF..F...........FFF.............
======================================================================
FAIL: test_chord_ordering (test.TestBasicChordFunctionality.test_chord_ordering)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 65, in test_chord_ordering
    self.assertEqual(str(f_sixth_ninth_chord), "F-A-C-E")
AssertionError: 'F-C-E-A' != 'F-A-C-E'
- F-C-E-A
?      --
+ F-A-C-E
?  ++
======================================================================
FAIL: test_is_major (test.TestBasicChordFunctionality.test_is_major)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 107, in test_is_major
    self.assertTrue(a_major_chord.is_major())
AssertionError: False is not true
======================================================================
FAIL: test_is_minor (test.TestBasicChordFunctionality.test_is_minor)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 90, in test_is_minor
    self.assertTrue(a_minor_chord.is_minor())
AssertionError: False is not true
======================================================================
FAIL: test_is_power_chord (test.TestBasicChordFunctionality.test_is_power_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 116, in test_is_power_chord
    self.assertFalse(a_minor_chord.is_power_chord())
AssertionError: True is not false
======================================================================
FAIL: test_transposed_negative_overflow (test.TestBasicChordFunctionality.test_transposed_negative_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 152, in test_transposed_negative_overflow
    self.assertEqual(str(result_chord), "G#-B-D#")
AssertionError: 'G#-D#-B' != 'G#-B-D#'
- G#-D#-B
?      --
+ G#-B-D#
?    ++
======================================================================
FAIL: test_add_tone_to_chord (test.TestOperations.test_add_tone_to_chord)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 254, in test_add_tone_to_chord
    self.assertEqual(str(result_chord), "F-A-C-D")
AssertionError: 'F-C-D-A' != 'F-A-C-D'
- F-C-D-A
?      --
+ F-A-C-D
?  ++
======================================================================
FAIL: test_add_tone_to_chord_existing_tone (test.TestOperations.test_add_tone_to_chord_existing_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 270, in test_add_tone_to_chord_existing_tone
    self.assertEqual(str(result_chord), "F-G#-C")
AssertionError: 'F-C-G#' != 'F-G#-C'
- F-C-G#
+ F-G#-C
======================================================================
FAIL: test_add_tone_to_chord_order (test.TestOperations.test_add_tone_to_chord_order)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 261, in test_add_tone_to_chord_order
    self.assertEqual(str(result_chord), "F-G-A-C")
AssertionError: 'F-C-G-A' != 'F-G-A-C'
- F-C-G-A
?  --
+ F-G-A-C
?      ++
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=8)
                            
    
 
    
        Георги Кунчев
         
    
04.11.2024 13:40Усещам, че се стремиш стриктно да спазваш правилото за 79 символа на ред. Това е чудесно, но имай предвид, че в днешно време повечето хора се стремят да използват 100 или 120 като лимит, защото 79 е легаси и е прекалено малко.  
Оценявам факта, че всички пренасяния спазват стриктно PEP8 правилата (или поне не видях отклонения).  
Ако решиш да си до 79 символа - супер, но ако ти е прекалено изкуствено да пренасяш само заради някакви си 4-5 символа отгоре, специално в нашия курс няма да се оплакваме.  
Като цяло имаш много чист стил и кодът се чете сам. Браво! 
                            
                            
                         | 
| f | 1 | DEFINED_TONES = ( | f | 1 | DEFINED_TONES = ( | 
| 2 | "C", "C#", "D", "D#", "E", "F", | 2 | "C", "C#", "D", "D#", "E", "F", | ||
| 3 | "F#", "G", "G#", "A", "A#", "B" | 3 | "F#", "G", "G#", "A", "A#", "B" | ||
| 4 | ) | 4 | ) | ||
| 5 | DEFINED_INTERVALS = ( | 5 | DEFINED_INTERVALS = ( | ||
| 6 | "unison", "minor 2nd", "major 2nd", | 6 | "unison", "minor 2nd", "major 2nd", | ||
| 7 | "minor 3rd", "major 3rd", "perfect 4th", | 7 | "minor 3rd", "major 3rd", "perfect 4th", | ||
| 8 | "diminished 5th", "perfect 5th", "minor 6th", | 8 | "diminished 5th", "perfect 5th", "minor 6th", | ||
| 9 | "major 6th", "minor 7th", "major 7th" | 9 | "major 6th", "minor 7th", "major 7th" | ||
| 10 | ) | 10 | ) | ||
| 11 | TOTAL_TONES_COUNT = 12 | 11 | TOTAL_TONES_COUNT = 12 | ||
| 12 | 12 | ||||
| 13 | 13 | ||||
| 14 | class Tone: | 14 | class Tone: | ||
| 15 | 15 | ||||
| 16 | def __init__(self, note): | 16 | def __init__(self, note): | ||
| 17 | self.note = note | 17 | self.note = note | ||
| 18 | 18 | ||||
| 19 | def __str__(self): | 19 | def __str__(self): | ||
| 20 | return self.note | 20 | return self.note | ||
| 21 | 21 | ||||
| 22 | def __add__(self, other): | 22 | def __add__(self, other): | ||
| 23 | if isinstance(other, Tone): | 23 | if isinstance(other, Tone): | ||
| 24 | return Chord(self, other) | 24 | return Chord(self, other) | ||
| 25 | elif isinstance(other, Interval): | 25 | elif isinstance(other, Interval): | ||
| 26 | note_idx = (DEFINED_TONES.index(self.note) | 26 | note_idx = (DEFINED_TONES.index(self.note) | ||
| 27 | + other.number_of_semitones) | 27 | + other.number_of_semitones) | ||
| 28 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | 28 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | ||
| 29 | 29 | ||||
| 30 | def __sub__(self, other): | 30 | def __sub__(self, other): | ||
| 31 | if isinstance(other, Tone): | 31 | if isinstance(other, Tone): | ||
| 32 | return Interval(DEFINED_TONES.index(self.note) | 32 | return Interval(DEFINED_TONES.index(self.note) | ||
| 33 | - DEFINED_TONES.index(other.note)) | 33 | - DEFINED_TONES.index(other.note)) | ||
| 34 | elif isinstance(other, Interval): | 34 | elif isinstance(other, Interval): | ||
| 35 | note_idx = (DEFINED_TONES.index(self.note) | 35 | note_idx = (DEFINED_TONES.index(self.note) | ||
| 36 | - other.number_of_semitones) | 36 | - other.number_of_semitones) | ||
| 37 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | 37 | return Tone(DEFINED_TONES[note_idx % TOTAL_TONES_COUNT]) | ||
| 38 | 38 | ||||
| 39 | def __eq__(self, value): | 39 | def __eq__(self, value): | ||
| 40 | return self.note == value.note | 40 | return self.note == value.note | ||
| 41 | 41 | ||||
| 42 | 42 | ||||
| 43 | class Interval: | 43 | class Interval: | ||
| 44 | 44 | ||||
| 45 | def __init__(self, number_of_semitones): | 45 | def __init__(self, number_of_semitones): | ||
| 46 | self.number_of_semitones = number_of_semitones % TOTAL_TONES_COUNT | 46 | self.number_of_semitones = number_of_semitones % TOTAL_TONES_COUNT | ||
| 47 | self.semitone = DEFINED_INTERVALS[self.number_of_semitones] | 47 | self.semitone = DEFINED_INTERVALS[self.number_of_semitones] | ||
| 48 | 48 | ||||
| 49 | def __str__(self): | 49 | def __str__(self): | ||
| 50 | return self.semitone | 50 | return self.semitone | ||
| 51 | 51 | ||||
| 52 | def __add__(self, other): | 52 | def __add__(self, other): | ||
| 53 | if isinstance(other, Interval): | 53 | if isinstance(other, Interval): | ||
| 54 | return Interval(self.number_of_semitones | 54 | return Interval(self.number_of_semitones | ||
| 55 | + other.number_of_semitones) | 55 | + other.number_of_semitones) | ||
| 56 | elif isinstance(other, Tone): | 56 | elif isinstance(other, Tone): | ||
| 57 | raise TypeError("Invalid operation") | 57 | raise TypeError("Invalid operation") | ||
| 58 | 58 | ||||
| 59 | def __sub__(self, other): | 59 | def __sub__(self, other): | ||
| 60 | if isinstance(other, Tone): | 60 | if isinstance(other, Tone): | ||
| 61 | raise TypeError("Invalid operation") | 61 | raise TypeError("Invalid operation") | ||
| 62 | 62 | ||||
| 63 | def __neg__(self): | 63 | def __neg__(self): | ||
| 64 | return Interval(-self.number_of_semitones) | 64 | return Interval(-self.number_of_semitones) | ||
| 65 | 65 | ||||
| 66 | 66 | ||||
| 67 | class Chord: | 67 | class Chord: | ||
| 68 | 68 | ||||
| n | 69 | min_tones_count = 2 | n | ||
| 70 | |||||
| 71 | def __init__(self, root, *tones): | 69 | def __init__(self, root, *tones): | ||
| 72 | self.root = root | 70 | self.root = root | ||
| 73 | self.secondary_tones = [] | 71 | self.secondary_tones = [] | ||
| 74 | for tone in tones: | 72 | for tone in tones: | ||
| 75 | if tone not in self.secondary_tones: | 73 | if tone not in self.secondary_tones: | ||
| 76 | self.secondary_tones.append(tone) | 74 | self.secondary_tones.append(tone) | ||
| 77 | if root in self.secondary_tones: | 75 | if root in self.secondary_tones: | ||
| 78 | self.secondary_tones.remove(root) | 76 | self.secondary_tones.remove(root) | ||
| 79 | if not self.secondary_tones: | 77 | if not self.secondary_tones: | ||
| 80 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 78 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
| 81 | self.secondary_tones.sort(key=lambda x: DEFINED_TONES.index(x.note)) | 79 | self.secondary_tones.sort(key=lambda x: DEFINED_TONES.index(x.note)) | ||
| 82 | 80 | ||||
| 83 | def __str__(self): | 81 | def __str__(self): | ||
| 84 | tones_concat = "-".join(tone.note for tone in self.secondary_tones) | 82 | tones_concat = "-".join(tone.note for tone in self.secondary_tones) | ||
| 85 | return f"{str(self.root)}-{tones_concat}" | 83 | return f"{str(self.root)}-{tones_concat}" | ||
| 86 | 84 | ||||
| 87 | def __add__(self, other): | 85 | def __add__(self, other): | ||
| 88 | if isinstance(other, Tone): | 86 | if isinstance(other, Tone): | ||
| 89 | return Chord(self.root, | 87 | return Chord(self.root, | ||
| n | 90 | *self.secondary_tones, | n | 88 | *self.secondary_tones, | 
| 91 | other) | 89 | other) | ||
| 92 | elif isinstance(other, Chord): | 90 | elif isinstance(other, Chord): | ||
| 93 | return Chord(self.root, | 91 | return Chord(self.root, | ||
| n | 94 | *self.secondary_tones, | n | 92 | *self.secondary_tones, | 
| 95 | other.root, | 93 | other.root, | ||
| 96 | *other.secondary_tones) | 94 | *other.secondary_tones) | ||
| 97 | 95 | ||||
| 98 | def __sub__(self, other): | 96 | def __sub__(self, other): | ||
| 99 | if not isinstance(other, Tone): | 97 | if not isinstance(other, Tone): | ||
| 100 | return | 98 | return | ||
| 101 | if (other not in self.secondary_tones and | 99 | if (other not in self.secondary_tones and | ||
| 102 | not (other == self.root)): | 100 | not (other == self.root)): | ||
| 103 | raise TypeError( | 101 | raise TypeError( | ||
| 104 | f"Cannot remove tone {str(other)} from chord {str(self)}" | 102 | f"Cannot remove tone {str(other)} from chord {str(self)}" | ||
| 105 | ) | 103 | ) | ||
| n | 106 | if (len(self.secondary_tones) < Chord.min_tones_count): | n | ||
| 107 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||||
| 108 | if other == self.root: | 104 | if other == self.root: | ||
| 109 | return Chord(self.secondary_tones[0], | 105 | return Chord(self.secondary_tones[0], | ||
| n | 110 | *self.secondary_tones[1:]) | n | 106 | *self.secondary_tones[1:]) | 
| 111 | else: | 107 | else: | ||
| 112 | modified_secondary_tones = self.secondary_tones[:] | 108 | modified_secondary_tones = self.secondary_tones[:] | ||
| 113 | modified_secondary_tones.remove(other) | 109 | modified_secondary_tones.remove(other) | ||
| 114 | return Chord(self.root, | 110 | return Chord(self.root, | ||
| t | 115 | *modified_secondary_tones) | t | 111 | *modified_secondary_tones) | 
| 116 | 112 | ||||
| 117 | def transposed(self, interval): | 113 | def transposed(self, interval): | ||
| 118 | modified_secondary_tones = [ | 114 | modified_secondary_tones = [ | ||
| 119 | (tone + interval) for tone in self.secondary_tones | 115 | (tone + interval) for tone in self.secondary_tones | ||
| 120 | ] | 116 | ] | ||
| 121 | return Chord(self.root + interval, *modified_secondary_tones) | 117 | return Chord(self.root + interval, *modified_secondary_tones) | ||
| 122 | 118 | ||||
| 123 | def is_minor(self): | 119 | def is_minor(self): | ||
| 124 | root_idx = DEFINED_TONES.index(self.root.note) | 120 | root_idx = DEFINED_TONES.index(self.root.note) | ||
| 125 | minor_3rd_diff = DEFINED_INTERVALS.index("minor 3rd") | 121 | minor_3rd_diff = DEFINED_INTERVALS.index("minor 3rd") | ||
| 126 | for tone in self.secondary_tones: | 122 | for tone in self.secondary_tones: | ||
| 127 | tone_idx = DEFINED_TONES.index(tone.note) | 123 | tone_idx = DEFINED_TONES.index(tone.note) | ||
| 128 | if abs(root_idx - tone_idx) == minor_3rd_diff: | 124 | if abs(root_idx - tone_idx) == minor_3rd_diff: | ||
| 129 | return True | 125 | return True | ||
| 130 | return False | 126 | return False | ||
| 131 | 127 | ||||
| 132 | def is_major(self): | 128 | def is_major(self): | ||
| 133 | root_idx = DEFINED_TONES.index(self.root.note) | 129 | root_idx = DEFINED_TONES.index(self.root.note) | ||
| 134 | major_3rd_diff = DEFINED_INTERVALS.index("major 3rd") | 130 | major_3rd_diff = DEFINED_INTERVALS.index("major 3rd") | ||
| 135 | for tone in self.secondary_tones: | 131 | for tone in self.secondary_tones: | ||
| 136 | tone_idx = DEFINED_TONES.index(tone.note) | 132 | tone_idx = DEFINED_TONES.index(tone.note) | ||
| 137 | if abs(root_idx - tone_idx) == major_3rd_diff: | 133 | if abs(root_idx - tone_idx) == major_3rd_diff: | ||
| 138 | return True | 134 | return True | ||
| 139 | return False | 135 | return False | ||
| 140 | 136 | ||||
| 141 | def is_power_chord(self): | 137 | def is_power_chord(self): | ||
| 142 | return not (self.is_major() or self.is_minor()) | 138 | return not (self.is_major() or self.is_minor()) | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
 
  | 
              
  |  |||||||||