1class Tone:
  2    tones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
  3
  4    def __init__(self, name):
  5        self.name = name
  6
  7    def __str__(self):
  8        return self.name
  9
 10    def __add__(self, other):
 11        if isinstance(other, Tone):
 12            return Chord(self, other)
 13        elif isinstance(other, Interval):
 14            curr_index = (Tone.tones.index(self.name) + other.semitones) % 12
 15            return Tone(Tone.tones[curr_index])
 16        
 17    def __sub__(self, other):
 18        if isinstance(other, Tone):
 19            distance = (Tone.tones.index(self.name) - Tone.tones.index(other.name)) % 12
 20            return Interval(distance)
 21        
 22        elif isinstance(other, Interval):
 23            curr_index = (Tone.tones.index(self.name) - other.semitones) % 12
 24            return Tone(Tone.tones[curr_index])
 25
 26
 27class Interval:
 28    INTERVAL_NAMES = {
 29        0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd",
 30        4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th",
 31        7: "perfect 5th", 8: "minor 6th", 9: "major 6th",
 32        10: "minor 7th", 11: "major 7th"
 33    }
 34
 35    def __init__(self, semitones):
 36        self.semitones = semitones % 12
 37
 38    def __str__(self):
 39        return self.INTERVAL_NAMES[self.semitones]
 40
 41    def __add__(self, other):
 42        if isinstance(other, Interval):
 43            new_semitones = (self.semitones + other.semitones) % 12
 44            return Interval(new_semitones)
 45        else:
 46            raise TypeError("Invalid operation")
 47
 48
 49class Chord:
 50    def __init__(self, root_tone, *tones):
 51        unique_tones = {root_tone, *tones}
 52
 53        if len(unique_tones) < 2:
 54            raise TypeError("Cannot have a chord made of only 1 unique tone")
 55        
 56        self.root_tone = root_tone
 57        self.tones = unique_tones
 58
 59        start_index = Tone.tones.index(root_tone.name)
 60        sorted_tones = sorted(unique_tones, key=lambda t: (Tone.tones * 2).index(t.name, start_index))
 61        
 62        self.tones = [Tone(t.name) for t in sorted_tones]
 63
 64    def __str__(self):
 65        return "-".join(str(tone) for tone in self.tones)
 66    
 67    #Find distance between two tones
 68    def semitone_distance(self, tone1, tone2):
 69        return (Tone.tones.index(tone2.name) - Tone.tones.index(tone1.name)) % 12
 70    
 71    def is_minor(self):
 72        for tone in self.tones:
 73            if tone != self.root_tone:
 74                distance = self.semitone_distance(self.root_tone, tone)
 75                if Interval (distance).__str__() == "minor 3rd":
 76                    return True
 77        return False
 78    
 79    def is_major(self):
 80        for tone in self.tones:
 81            if tone != self.root_tone:
 82                distance = self.semitone_distance(self.root_tone, tone)
 83                if Interval(distance).__str__() == "major 3rd":
 84                    return True
 85        return False
 86    
 87    def is_power_chord(self):
 88        if not self.is_major and not self.is_minor:
 89            return True
 90        return False 
 91
 92    def __sub__(self, other):
 93        if isinstance(other, Tone):
 94            if other.name not in [tone.name for tone in self.tones]:
 95                raise TypeError(f"Cannot remove tone {other} from chord {self}")
 96            
 97        new_tones = [tone for tone in self.tones if tone.name != other.name]
 98
 99        if len(new_tones) == 1:
100            raise TypeError("Cannot have a chord made of only 1 unique tone")
101        
102        new_root_tone = new_tones[0]
103
104        return Chord(new_root_tone, *new_tones)
105    
106    def __add__(self, other):
107        if isinstance(other, Chord):
108            combined_tones = list(self.tones)  
109            for tone in other.tones:
110                if tone not in combined_tones:
111                    combined_tones.append(tone)  
112            new_root_tone = combined_tones[0]
113            unique_tones = []
114            for tone in combined_tones:
115                if tone not in unique_tones:
116                    unique_tones.append(tone)
117            return Chord(new_root_tone, *unique_tones)
118        elif isinstance(other, Tone):
119            combined_tones = list(self.tones)
120            if other not in combined_tones:
121                combined_tones.append(other)
122            new_root_tone = combined_tones[0]
123            return Chord(new_root_tone, *combined_tones)
124    
125    def transposed(self, interval):
126        if not isinstance(interval, Interval):
127            raise TypeError("Interval is expected")
128
129        transposed_tones = [
130            Tone(Tone.tones[(Tone.tones.index(tone.name) + interval.semitones) % 12])
131            for tone in self.tones
132        ]
133        return Chord(transposed_tones[0], *transposed_tones[1:])
F..F..F.EE.E....F.....F....F.....F...
======================================================================
ERROR: test_transposed_negative (test.TestBasicChordFunctionality.test_transposed_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 136, in test_transposed_negative
    result_chord = e_minor_chord.transposed(-Interval(2))
                                            ^^^^^^^^^^^^
TypeError: bad operand type for unary -: 'Interval'
======================================================================
ERROR: test_transposed_negative_overflow (test.TestBasicChordFunctionality.test_transposed_negative_overflow)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 150, in test_transposed_negative_overflow
    result_chord = e_minor_chord.transposed(-Interval(8))
                                            ^^^^^^^^^^^^
TypeError: bad operand type for unary -: 'Interval'
======================================================================
ERROR: test_interval_negative (test.TestBasicIntervalFunctionality.test_interval_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 50, in test_interval_negative
    minor_2nd = -major_7th
                ^^^^^^^^^^
TypeError: bad operand type for unary -: 'Interval'
======================================================================
FAIL: test_chord_not_enough_tones (test.TestBasicChordFunctionality.test_chord_not_enough_tones)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 83, in test_chord_not_enough_tones
    with self.assertRaises(TypeError) as err:
AssertionError: TypeError not raised
======================================================================
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-A-A-A-A-A-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-A-A-A-A-A-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
======================================================================
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_add_chords_repeating_notes (test.TestOperations.test_add_chords_repeating_notes)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 305, in test_add_chords_repeating_notes
    self.assertEqual(str(result_chord), "C-G")
AssertionError: 'C-C-G-G' != 'C-G'
- C-C-G-G
+ C-G
======================================================================
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-F-G#-G#-C-C' != 'F-G#-C'
- F-F-G#-G#-C-C
+ F-G#-C
======================================================================
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
======================================================================
FAIL: test_tone_addition_same_tone (test.TestOperations.test_tone_addition_same_tone)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/test.py", line 166, in test_tone_addition_same_tone
    with self.assertRaises(TypeError) as err:
AssertionError: TypeError not raised
----------------------------------------------------------------------
Ran 37 tests in 0.002s
FAILED (failures=7, errors=3)
| f | 1 | class Tone: | f | 1 | class Tone: | 
| 2 | tones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | 2 | tones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | ||
| 3 | 3 | ||||
| 4 | def __init__(self, name): | 4 | def __init__(self, name): | ||
| 5 | self.name = name | 5 | self.name = name | ||
| 6 | 6 | ||||
| 7 | def __str__(self): | 7 | def __str__(self): | ||
| 8 | return self.name | 8 | return self.name | ||
| 9 | 9 | ||||
| 10 | def __add__(self, other): | 10 | def __add__(self, other): | ||
| 11 | if isinstance(other, Tone): | 11 | if isinstance(other, Tone): | ||
| 12 | return Chord(self, other) | 12 | return Chord(self, other) | ||
| 13 | elif isinstance(other, Interval): | 13 | elif isinstance(other, Interval): | ||
| 14 | curr_index = (Tone.tones.index(self.name) + other.semitones) % 12 | 14 | curr_index = (Tone.tones.index(self.name) + other.semitones) % 12 | ||
| 15 | return Tone(Tone.tones[curr_index]) | 15 | return Tone(Tone.tones[curr_index]) | ||
| 16 | 16 | ||||
| 17 | def __sub__(self, other): | 17 | def __sub__(self, other): | ||
| 18 | if isinstance(other, Tone): | 18 | if isinstance(other, Tone): | ||
| 19 | distance = (Tone.tones.index(self.name) - Tone.tones.index(other.name)) % 12 | 19 | distance = (Tone.tones.index(self.name) - Tone.tones.index(other.name)) % 12 | ||
| 20 | return Interval(distance) | 20 | return Interval(distance) | ||
| 21 | 21 | ||||
| 22 | elif isinstance(other, Interval): | 22 | elif isinstance(other, Interval): | ||
| 23 | curr_index = (Tone.tones.index(self.name) - other.semitones) % 12 | 23 | curr_index = (Tone.tones.index(self.name) - other.semitones) % 12 | ||
| 24 | return Tone(Tone.tones[curr_index]) | 24 | return Tone(Tone.tones[curr_index]) | ||
| 25 | 25 | ||||
| 26 | 26 | ||||
| 27 | class Interval: | 27 | class Interval: | ||
| 28 | INTERVAL_NAMES = { | 28 | INTERVAL_NAMES = { | ||
| 29 | 0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd", | 29 | 0: "unison", 1: "minor 2nd", 2: "major 2nd", 3: "minor 3rd", | ||
| 30 | 4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th", | 30 | 4: "major 3rd", 5: "perfect 4th", 6: "diminished 5th", | ||
| 31 | 7: "perfect 5th", 8: "minor 6th", 9: "major 6th", | 31 | 7: "perfect 5th", 8: "minor 6th", 9: "major 6th", | ||
| 32 | 10: "minor 7th", 11: "major 7th" | 32 | 10: "minor 7th", 11: "major 7th" | ||
| 33 | } | 33 | } | ||
| 34 | 34 | ||||
| 35 | def __init__(self, semitones): | 35 | def __init__(self, semitones): | ||
| 36 | self.semitones = semitones % 12 | 36 | self.semitones = semitones % 12 | ||
| 37 | 37 | ||||
| 38 | def __str__(self): | 38 | def __str__(self): | ||
| 39 | return self.INTERVAL_NAMES[self.semitones] | 39 | return self.INTERVAL_NAMES[self.semitones] | ||
| 40 | 40 | ||||
| 41 | def __add__(self, other): | 41 | def __add__(self, other): | ||
| 42 | if isinstance(other, Interval): | 42 | if isinstance(other, Interval): | ||
| 43 | new_semitones = (self.semitones + other.semitones) % 12 | 43 | new_semitones = (self.semitones + other.semitones) % 12 | ||
| 44 | return Interval(new_semitones) | 44 | return Interval(new_semitones) | ||
| 45 | else: | 45 | else: | ||
| 46 | raise TypeError("Invalid operation") | 46 | raise TypeError("Invalid operation") | ||
| n | 47 | n | 47 | ||
| 48 | |||||
| 48 | class Chord: | 49 | class Chord: | ||
| 49 | def __init__(self, root_tone, *tones): | 50 | def __init__(self, root_tone, *tones): | ||
| 50 | unique_tones = {root_tone, *tones} | 51 | unique_tones = {root_tone, *tones} | ||
| 51 | 52 | ||||
| 52 | if len(unique_tones) < 2: | 53 | if len(unique_tones) < 2: | ||
| 53 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 54 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
| 54 | 55 | ||||
| 55 | self.root_tone = root_tone | 56 | self.root_tone = root_tone | ||
| 56 | self.tones = unique_tones | 57 | self.tones = unique_tones | ||
| 57 | 58 | ||||
| 58 | start_index = Tone.tones.index(root_tone.name) | 59 | start_index = Tone.tones.index(root_tone.name) | ||
| 59 | sorted_tones = sorted(unique_tones, key=lambda t: (Tone.tones * 2).index(t.name, start_index)) | 60 | sorted_tones = sorted(unique_tones, key=lambda t: (Tone.tones * 2).index(t.name, start_index)) | ||
| 60 | 61 | ||||
| 61 | self.tones = [Tone(t.name) for t in sorted_tones] | 62 | self.tones = [Tone(t.name) for t in sorted_tones] | ||
| 62 | 63 | ||||
| 63 | def __str__(self): | 64 | def __str__(self): | ||
| 64 | return "-".join(str(tone) for tone in self.tones) | 65 | return "-".join(str(tone) for tone in self.tones) | ||
| 65 | 66 | ||||
| 66 | #Find distance between two tones | 67 | #Find distance between two tones | ||
| 67 | def semitone_distance(self, tone1, tone2): | 68 | def semitone_distance(self, tone1, tone2): | ||
| 68 | return (Tone.tones.index(tone2.name) - Tone.tones.index(tone1.name)) % 12 | 69 | return (Tone.tones.index(tone2.name) - Tone.tones.index(tone1.name)) % 12 | ||
| 69 | 70 | ||||
| 70 | def is_minor(self): | 71 | def is_minor(self): | ||
| 71 | for tone in self.tones: | 72 | for tone in self.tones: | ||
| 72 | if tone != self.root_tone: | 73 | if tone != self.root_tone: | ||
| 73 | distance = self.semitone_distance(self.root_tone, tone) | 74 | distance = self.semitone_distance(self.root_tone, tone) | ||
| 74 | if Interval (distance).__str__() == "minor 3rd": | 75 | if Interval (distance).__str__() == "minor 3rd": | ||
| 75 | return True | 76 | return True | ||
| 76 | return False | 77 | return False | ||
| 77 | 78 | ||||
| 78 | def is_major(self): | 79 | def is_major(self): | ||
| 79 | for tone in self.tones: | 80 | for tone in self.tones: | ||
| 80 | if tone != self.root_tone: | 81 | if tone != self.root_tone: | ||
| 81 | distance = self.semitone_distance(self.root_tone, tone) | 82 | distance = self.semitone_distance(self.root_tone, tone) | ||
| 82 | if Interval(distance).__str__() == "major 3rd": | 83 | if Interval(distance).__str__() == "major 3rd": | ||
| 83 | return True | 84 | return True | ||
| 84 | return False | 85 | return False | ||
| 85 | 86 | ||||
| 86 | def is_power_chord(self): | 87 | def is_power_chord(self): | ||
| 87 | if not self.is_major and not self.is_minor: | 88 | if not self.is_major and not self.is_minor: | ||
| 88 | return True | 89 | return True | ||
| 89 | return False | 90 | return False | ||
| 90 | 91 | ||||
| 91 | def __sub__(self, other): | 92 | def __sub__(self, other): | ||
| 92 | if isinstance(other, Tone): | 93 | if isinstance(other, Tone): | ||
| n | 93 | # if other not in self.tones: | n | 94 | if other.name not in [tone.name for tone in self.tones]: | 
| 94 | # raise TypeError(f"Cannot remove tone {other} from chord {self}") | 95 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||
| 95 | |||||
| 96 | # Проверка дали тонът съществува в акорда | ||||
| 97 | if other.name not in [tone.name for tone in self.tones]: | ||||
| 98 | raise TypeError(f"Cannot remove tone {other} from chord {self}") | ||||
| 99 | 96 | ||||
| n | 100 | n | |||
| 101 | new_tones = [tone for tone in self.tones if tone.name != other.name] | 97 | new_tones = [tone for tone in self.tones if tone.name != other.name] | ||
| 102 | 98 | ||||
| 103 | if len(new_tones) == 1: | 99 | if len(new_tones) == 1: | ||
| 104 | raise TypeError("Cannot have a chord made of only 1 unique tone") | 100 | raise TypeError("Cannot have a chord made of only 1 unique tone") | ||
| 105 | 101 | ||||
| 106 | new_root_tone = new_tones[0] | 102 | new_root_tone = new_tones[0] | ||
| 107 | 103 | ||||
| 108 | return Chord(new_root_tone, *new_tones) | 104 | return Chord(new_root_tone, *new_tones) | ||
| 109 | 105 | ||||
| 110 | def __add__(self, other): | 106 | def __add__(self, other): | ||
| 111 | if isinstance(other, Chord): | 107 | if isinstance(other, Chord): | ||
| 112 | combined_tones = list(self.tones) | 108 | combined_tones = list(self.tones) | ||
| 113 | for tone in other.tones: | 109 | for tone in other.tones: | ||
| 114 | if tone not in combined_tones: | 110 | if tone not in combined_tones: | ||
| 115 | combined_tones.append(tone) | 111 | combined_tones.append(tone) | ||
| 116 | new_root_tone = combined_tones[0] | 112 | new_root_tone = combined_tones[0] | ||
| 117 | unique_tones = [] | 113 | unique_tones = [] | ||
| 118 | for tone in combined_tones: | 114 | for tone in combined_tones: | ||
| 119 | if tone not in unique_tones: | 115 | if tone not in unique_tones: | ||
| 120 | unique_tones.append(tone) | 116 | unique_tones.append(tone) | ||
| 121 | return Chord(new_root_tone, *unique_tones) | 117 | return Chord(new_root_tone, *unique_tones) | ||
| 122 | elif isinstance(other, Tone): | 118 | elif isinstance(other, Tone): | ||
| 123 | combined_tones = list(self.tones) | 119 | combined_tones = list(self.tones) | ||
| 124 | if other not in combined_tones: | 120 | if other not in combined_tones: | ||
| 125 | combined_tones.append(other) | 121 | combined_tones.append(other) | ||
| 126 | new_root_tone = combined_tones[0] | 122 | new_root_tone = combined_tones[0] | ||
| 127 | return Chord(new_root_tone, *combined_tones) | 123 | return Chord(new_root_tone, *combined_tones) | ||
| 128 | 124 | ||||
| 129 | def transposed(self, interval): | 125 | def transposed(self, interval): | ||
| 130 | if not isinstance(interval, Interval): | 126 | if not isinstance(interval, Interval): | ||
| 131 | raise TypeError("Interval is expected") | 127 | raise TypeError("Interval is expected") | ||
| 132 | 128 | ||||
| 133 | transposed_tones = [ | 129 | transposed_tones = [ | ||
| 134 | Tone(Tone.tones[(Tone.tones.index(tone.name) + interval.semitones) % 12]) | 130 | Tone(Tone.tones[(Tone.tones.index(tone.name) + interval.semitones) % 12]) | ||
| 135 | for tone in self.tones | 131 | for tone in self.tones | ||
| 136 | ] | 132 | ] | ||
| 137 | return Chord(transposed_tones[0], *transposed_tones[1:]) | 133 | return Chord(transposed_tones[0], *transposed_tones[1:]) | ||
| t | 138 | t | |||
| 139 | |||||
| 140 | # c_minor_chord = Chord(Tone("C"), Tone("D#"), Tone("G")) | ||||
| 141 | # print(str(c_minor_chord)) # "C-D#-G" | ||||
| 142 | |||||
| 143 | # d_minor_chord = c_minor_chord.transposed(Interval(2)) | ||||
| 144 | # print(str(d_minor_chord)) # "D-F-A" | ||||
| 145 | |||||
| 146 | # a_sharp_minor_chord = d_minor_chord.transposed(Interval(-4)) | ||||
| 147 | # print(str(a_sharp_minor_chord)) # "A#-C#-F" | ||||
| 148 | |||||
| 149 | # c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G")) | ||||
| 150 | # result_chord = c_major_chord - Tone("E") | ||||
| 151 | # print(result_chord) | ||||
| 152 | |||||
| 153 | # c_power_chord = Chord(Tone("C"), Tone("G")) | ||||
| 154 | # result_chord = c_power_chord - Tone("G") | ||||
| 155 | |||||
| 156 | # c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G")) | ||||
| 157 | # result_chord = c_major_chord - Tone("E") | ||||
| 158 | # print(result_chord) | ||||
| 159 | c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G")) | ||||
| 160 | result_chord = c_major_chord - Tone("E") | ||||
| 161 | print(result_chord) | ||||
| 162 | |||||
| 163 | |||||
| 164 | 
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 
 | 
 | |||||||||
07.11.2024 12:05
07.11.2024 12:05
07.11.2024 12:06
07.11.2024 12:07
07.11.2024 12:10
07.11.2024 12:09
07.11.2024 12:11
07.11.2024 12:11
07.11.2024 12:12
07.11.2024 12:13