Домашни > Pitches love the D > Решения > Решението на Илиан Запрянов

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

10 точки общо

37 успешни теста
0 неуспешни теста
Код

  1class Tone:
  2    """Represent a musical tone."""
  3    
  4    TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
  5    
  6    def __init__(self, name):
  7        self.name = name
  8        
  9    def __str__(self):
 10        return self.name
 11    
 12    def __int__(self):
 13        return Tone.TONE_SCALE.index(self.name)
 14    
 15    def __eq__(self, other):
 16        return self.name == other.name
 17    
 18    def __hash__(self):
 19        """Return a hash value for the Tone object using its name."""
 20        return hash(self.name)
 21    
 22    def __add__(self, other):
 23        if isinstance(other, Tone):
 24            return Chord(self, other)
 25        elif isinstance(other, Interval):
 26            new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone
 27            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
 28        raise TypeError("Invalid operation")
 29    
 30    def __radd__(self, other):
 31        if isinstance(other, Interval):
 32            raise TypeError("Invalid operation")
 33    
 34    def __sub__(self, other):
 35        if isinstance(other, Tone):
 36            return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))
 37        elif isinstance(other, Interval):
 38            new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone
 39            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
 40        raise TypeError("Invalid operation")
 41    
 42    def __rsub__(self, other):
 43        if isinstance(other, Interval):
 44            raise TypeError("Invalid operation")
 45
 46
 47class Interval:
 48    """Represent a music interval."""
 49    
 50    SEMITONE_NAMES = (
 51        "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",
 52        "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",
 53        "major 6th", "minor 7th", "major 7th"
 54    )
 55    
 56    def __init__(self, semitone):
 57        if not isinstance(semitone, int):
 58            raise TypeError("Invalid semitone")
 59            
 60        self.semitone = semitone % len(Interval.SEMITONE_NAMES)
 61        
 62    def __str__(self):
 63        return Interval.SEMITONE_NAMES[self.semitone]
 64      
 65    def __neg__(self):
 66        return Interval(-self.semitone)
 67    
 68    def __add__(self, other):
 69        if isinstance(other, Interval):
 70            return Interval(self.semitone + other.semitone)
 71        raise TypeError("Invalid operation")
 72    
 73    def __sub__(self, other):
 74        if isinstance(other, Interval):
 75            return Interval(self.semitone - other.semitone)
 76        raise TypeError("Invalid operation")
 77
 78
 79class Chord:
 80    """Represent a music chord."""
 81    
 82    def __init__(self, *tones):
 83        
 84        unique_tones = list(set(tones))
 85        if len(unique_tones) <= 1:
 86            raise TypeError("Cannot have a chord made of only 1 unique tone")
 87
 88        if not all(isinstance(tone, Tone) for tone in unique_tones):
 89            raise TypeError("Invalid Tone found")
 90        
 91        self.root = tones[0]
 92        self.tones = list(filter(lambda tone: tone != self.root, unique_tones))
 93
 94    def sort_tones(self):
 95        """Sort the tones relatively."""
 96        
 97        root_index = Tone.TONE_SCALE.index(str(self.root))
 98        relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]
 99        
100        return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))
101
102    def __str__(self):
103        sorted_tones = self.sort_tones()
104        return f'{self.root}-{"-".join(map(str, sorted_tones))}'
105    
106    def find_new_value(self, tone, interval):
107        """Set new values after transpose."""
108        
109        if not isinstance(interval, Interval):
110            raise TypeError("Invalid Interval")
111
112        new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone
113        return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
114            
115    def transposed(self, interval):
116        if not isinstance(interval, Interval):
117            raise TypeError("Invalid operation")
118
119        transposed_root = self.find_new_value(self.root, interval)
120        transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]
121        
122        return Chord(*([transposed_root] + transposed_tones))
123        
124    def is_minor(self):
125        return any(str(tone - self.root) == "minor 3rd" for tone in self.tones)
126    
127    def is_major(self):
128        return any(str(tone - self.root) == "major 3rd" for tone in self.tones)
129    
130    def is_power_chord(self):
131        return not self.is_major() and not self.is_minor()
132    
133    def __add__(self, other):
134        if isinstance(other, Tone):
135            return Chord(*([self.root] + self.tones + [other]))
136        elif isinstance(other, Chord):
137            return Chord(*([self.root] + self.tones + [other.root] + other.tones))
138        raise TypeError("Invalid operation")
139    
140    def __sub__(self, other):
141        if isinstance(other, Tone):
142            new_tones = [self.root] + self.tones
143            if other not in new_tones:
144                raise TypeError (f"Cannot remove tone {other} from chord {self}")
145            
146            new_tones.remove(other)
147            return Chord(*new_tones)
148        raise TypeError("Invalid operation")

.....................................
----------------------------------------------------------------------
Ran 37 tests in 0.001s

OK

Дискусия
Виктор Бечев
03.11.2024 22:31

Генерално не сме против дефанзивността при взимането на input, просто знай, че нямаме навика да тестваме с невалиден вход.
История

f1class Tone:f1class Tone:
2    """Represent a musical tone."""2    """Represent a musical tone."""
3    3    
4    TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")4    TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
5    5    
6    def __init__(self, name):6    def __init__(self, name):
7        self.name = name7        self.name = name
8        8        
9    def __str__(self):9    def __str__(self):
10        return self.name10        return self.name
11    11    
12    def __int__(self):12    def __int__(self):
13        return Tone.TONE_SCALE.index(self.name)13        return Tone.TONE_SCALE.index(self.name)
14    14    
15    def __eq__(self, other):15    def __eq__(self, other):
16        return self.name == other.name16        return self.name == other.name
17    17    
18    def __hash__(self):18    def __hash__(self):
19        """Return a hash value for the Tone object using its name."""19        """Return a hash value for the Tone object using its name."""
20        return hash(self.name)20        return hash(self.name)
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            new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone26            new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone
27            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])27            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
n28        raise TypeError("Invalid Tone or Interval.")n28        raise TypeError("Invalid operation")
29    29    
30    def __radd__(self, other):30    def __radd__(self, other):
31        if isinstance(other, Interval):31        if isinstance(other, Interval):
32            raise TypeError("Invalid operation")32            raise TypeError("Invalid operation")
33    33    
34    def __sub__(self, other):34    def __sub__(self, other):
35        if isinstance(other, Tone):35        if isinstance(other, Tone):
36            return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))36            return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))
37        elif isinstance(other, Interval):37        elif isinstance(other, Interval):
38            new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone38            new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone
39            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])39            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
n40        raise TypeError("Invalid Tone or Interval.")n40        raise TypeError("Invalid operation")
41    41    
42    def __rsub__(self, other):42    def __rsub__(self, other):
43        if isinstance(other, Interval):43        if isinstance(other, Interval):
44            raise TypeError("Invalid operation")44            raise TypeError("Invalid operation")
4545
4646
47class Interval:47class Interval:
48    """Represent a music interval."""48    """Represent a music interval."""
49    49    
50    SEMITONE_NAMES = (50    SEMITONE_NAMES = (
51        "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",51        "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",
52        "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",52        "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",
53        "major 6th", "minor 7th", "major 7th"53        "major 6th", "minor 7th", "major 7th"
54    )54    )
55    55    
56    def __init__(self, semitone):56    def __init__(self, semitone):
57        if not isinstance(semitone, int):57        if not isinstance(semitone, int):
58            raise TypeError("Invalid semitone")58            raise TypeError("Invalid semitone")
59            59            
60        self.semitone = semitone % len(Interval.SEMITONE_NAMES)60        self.semitone = semitone % len(Interval.SEMITONE_NAMES)
61        61        
62    def __str__(self):62    def __str__(self):
63        return Interval.SEMITONE_NAMES[self.semitone]63        return Interval.SEMITONE_NAMES[self.semitone]
64      64      
65    def __neg__(self):65    def __neg__(self):
66        return Interval(-self.semitone)66        return Interval(-self.semitone)
67    67    
68    def __add__(self, other):68    def __add__(self, other):
69        if isinstance(other, Interval):69        if isinstance(other, Interval):
70            return Interval(self.semitone + other.semitone)70            return Interval(self.semitone + other.semitone)
71        raise TypeError("Invalid operation")71        raise TypeError("Invalid operation")
72    72    
73    def __sub__(self, other):73    def __sub__(self, other):
74        if isinstance(other, Interval):74        if isinstance(other, Interval):
75            return Interval(self.semitone - other.semitone)75            return Interval(self.semitone - other.semitone)
76        raise TypeError("Invalid operation")76        raise TypeError("Invalid operation")
7777
7878
79class Chord:79class Chord:
80    """Represent a music chord."""80    """Represent a music chord."""
81    81    
82    def __init__(self, *tones):82    def __init__(self, *tones):
83        83        
84        unique_tones = list(set(tones))84        unique_tones = list(set(tones))
85        if len(unique_tones) <= 1:85        if len(unique_tones) <= 1:
86            raise TypeError("Cannot have a chord made of only 1 unique tone")86            raise TypeError("Cannot have a chord made of only 1 unique tone")
8787
88        if not all(isinstance(tone, Tone) for tone in unique_tones):88        if not all(isinstance(tone, Tone) for tone in unique_tones):
89            raise TypeError("Invalid Tone found")89            raise TypeError("Invalid Tone found")
90        90        
91        self.root = tones[0]91        self.root = tones[0]
92        self.tones = list(filter(lambda tone: tone != self.root, unique_tones))92        self.tones = list(filter(lambda tone: tone != self.root, unique_tones))
9393
94    def sort_tones(self):94    def sort_tones(self):
95        """Sort the tones relatively."""95        """Sort the tones relatively."""
96        96        
97        root_index = Tone.TONE_SCALE.index(str(self.root))97        root_index = Tone.TONE_SCALE.index(str(self.root))
98        relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]98        relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]
99        99        
100        return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))100        return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))
101101
102    def __str__(self):102    def __str__(self):
103        sorted_tones = self.sort_tones()103        sorted_tones = self.sort_tones()
104        return f'{self.root}-{"-".join(map(str, sorted_tones))}'104        return f'{self.root}-{"-".join(map(str, sorted_tones))}'
105    105    
106    def find_new_value(self, tone, interval):106    def find_new_value(self, tone, interval):
107        """Set new values after transpose."""107        """Set new values after transpose."""
108        108        
109        if not isinstance(interval, Interval):109        if not isinstance(interval, Interval):
110            raise TypeError("Invalid Interval")110            raise TypeError("Invalid Interval")
111111
112        new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone112        new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone
113        return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])113        return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
114            114            
115    def transposed(self, interval):115    def transposed(self, interval):
116        if not isinstance(interval, Interval):116        if not isinstance(interval, Interval):
n117            raise TypeError("Invalid Interval")n117            raise TypeError("Invalid operation")
118118
119        transposed_root = self.find_new_value(self.root, interval)119        transposed_root = self.find_new_value(self.root, interval)
120        transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]120        transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]
121        121        
122        return Chord(*([transposed_root] + transposed_tones))122        return Chord(*([transposed_root] + transposed_tones))
123        123        
124    def is_minor(self):124    def is_minor(self):
125        return any(str(tone - self.root) == "minor 3rd" for tone in self.tones)125        return any(str(tone - self.root) == "minor 3rd" for tone in self.tones)
126    126    
127    def is_major(self):127    def is_major(self):
128        return any(str(tone - self.root) == "major 3rd" for tone in self.tones)128        return any(str(tone - self.root) == "major 3rd" for tone in self.tones)
129    129    
130    def is_power_chord(self):130    def is_power_chord(self):
131        return not self.is_major() and not self.is_minor()131        return not self.is_major() and not self.is_minor()
132    132    
133    def __add__(self, other):133    def __add__(self, other):
134        if isinstance(other, Tone):134        if isinstance(other, Tone):
135            return Chord(*([self.root] + self.tones + [other]))135            return Chord(*([self.root] + self.tones + [other]))
136        elif isinstance(other, Chord):136        elif isinstance(other, Chord):
137            return Chord(*([self.root] + self.tones + [other.root] + other.tones))137            return Chord(*([self.root] + self.tones + [other.root] + other.tones))
n138        return TypeError("Invalid Tone or Chord")n138        raise TypeError("Invalid operation")
139    139    
140    def __sub__(self, other):140    def __sub__(self, other):
141        if isinstance(other, Tone):141        if isinstance(other, Tone):
142            new_tones = [self.root] + self.tones142            new_tones = [self.root] + self.tones
143            if other not in new_tones:143            if other not in new_tones:
144                raise TypeError (f"Cannot remove tone {other} from chord {self}")144                raise TypeError (f"Cannot remove tone {other} from chord {self}")
145            145            
146            new_tones.remove(other)146            new_tones.remove(other)
147            return Chord(*new_tones)147            return Chord(*new_tones)
t148        raise TypeError("Invalid Tone")t148        raise TypeError("Invalid operation")
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1class Tone:f1class Tone:
2    """Represent a musical tone."""2    """Represent a musical tone."""
3    3    
4    TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")4    TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
5    5    
6    def __init__(self, name):6    def __init__(self, name):
7        self.name = name7        self.name = name
8        8        
9    def __str__(self):9    def __str__(self):
10        return self.name10        return self.name
11    11    
12    def __int__(self):12    def __int__(self):
13        return Tone.TONE_SCALE.index(self.name)13        return Tone.TONE_SCALE.index(self.name)
14    14    
15    def __eq__(self, other):15    def __eq__(self, other):
16        return self.name == other.name16        return self.name == other.name
17    17    
18    def __hash__(self):18    def __hash__(self):
19        """Return a hash value for the Tone object using its name."""19        """Return a hash value for the Tone object using its name."""
20        return hash(self.name)20        return hash(self.name)
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            new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone26            new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone
27            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])27            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
28        raise TypeError("Invalid Tone or Interval.")28        raise TypeError("Invalid Tone or Interval.")
29    29    
30    def __radd__(self, other):30    def __radd__(self, other):
31        if isinstance(other, Interval):31        if isinstance(other, Interval):
32            raise TypeError("Invalid operation")32            raise TypeError("Invalid operation")
33    33    
34    def __sub__(self, other):34    def __sub__(self, other):
35        if isinstance(other, Tone):35        if isinstance(other, Tone):
36            return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))36            return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))
37        elif isinstance(other, Interval):37        elif isinstance(other, Interval):
38            new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone38            new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone
39            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])39            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
40        raise TypeError("Invalid Tone or Interval.")40        raise TypeError("Invalid Tone or Interval.")
41    41    
42    def __rsub__(self, other):42    def __rsub__(self, other):
43        if isinstance(other, Interval):43        if isinstance(other, Interval):
44            raise TypeError("Invalid operation")44            raise TypeError("Invalid operation")
4545
4646
47class Interval:47class Interval:
48    """Represent a music interval."""48    """Represent a music interval."""
49    49    
50    SEMITONE_NAMES = (50    SEMITONE_NAMES = (
51        "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",51        "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",
52        "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",52        "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",
53        "major 6th", "minor 7th", "major 7th"53        "major 6th", "minor 7th", "major 7th"
54    )54    )
55    55    
56    def __init__(self, semitone):56    def __init__(self, semitone):
57        if not isinstance(semitone, int):57        if not isinstance(semitone, int):
58            raise TypeError("Invalid semitone")58            raise TypeError("Invalid semitone")
59            59            
60        self.semitone = semitone % len(Interval.SEMITONE_NAMES)60        self.semitone = semitone % len(Interval.SEMITONE_NAMES)
61        61        
62    def __str__(self):62    def __str__(self):
63        return Interval.SEMITONE_NAMES[self.semitone]63        return Interval.SEMITONE_NAMES[self.semitone]
64      64      
65    def __neg__(self):65    def __neg__(self):
66        return Interval(-self.semitone)66        return Interval(-self.semitone)
67    67    
68    def __add__(self, other):68    def __add__(self, other):
69        if isinstance(other, Interval):69        if isinstance(other, Interval):
70            return Interval(self.semitone + other.semitone)70            return Interval(self.semitone + other.semitone)
71        raise TypeError("Invalid operation")71        raise TypeError("Invalid operation")
72    72    
73    def __sub__(self, other):73    def __sub__(self, other):
74        if isinstance(other, Interval):74        if isinstance(other, Interval):
75            return Interval(self.semitone - other.semitone)75            return Interval(self.semitone - other.semitone)
76        raise TypeError("Invalid operation")76        raise TypeError("Invalid operation")
7777
7878
79class Chord:79class Chord:
80    """Represent a music chord."""80    """Represent a music chord."""
81    81    
82    def __init__(self, *tones):82    def __init__(self, *tones):
n83        if len(tones) == 1 and isinstance(tones[0], (list, tuple)):n
84           tones = tones[0]
85        83        
86        unique_tones = list(set(tones))84        unique_tones = list(set(tones))
87        if len(unique_tones) <= 1:85        if len(unique_tones) <= 1:
88            raise TypeError("Cannot have a chord made of only 1 unique tone")86            raise TypeError("Cannot have a chord made of only 1 unique tone")
8987
90        if not all(isinstance(tone, Tone) for tone in unique_tones):88        if not all(isinstance(tone, Tone) for tone in unique_tones):
91            raise TypeError("Invalid Tone found")89            raise TypeError("Invalid Tone found")
92        90        
93        self.root = tones[0]91        self.root = tones[0]
94        self.tones = list(filter(lambda tone: tone != self.root, unique_tones))92        self.tones = list(filter(lambda tone: tone != self.root, unique_tones))
9593
96    def sort_tones(self):94    def sort_tones(self):
97        """Sort the tones relatively."""95        """Sort the tones relatively."""
98        96        
99        root_index = Tone.TONE_SCALE.index(str(self.root))97        root_index = Tone.TONE_SCALE.index(str(self.root))
100        relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]98        relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]
101        99        
102        return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))100        return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))
103101
104    def __str__(self):102    def __str__(self):
105        sorted_tones = self.sort_tones()103        sorted_tones = self.sort_tones()
106        return f'{self.root}-{"-".join(map(str, sorted_tones))}'104        return f'{self.root}-{"-".join(map(str, sorted_tones))}'
107    105    
108    def find_new_value(self, tone, interval):106    def find_new_value(self, tone, interval):
109        """Set new values after transpose."""107        """Set new values after transpose."""
110        108        
111        if not isinstance(interval, Interval):109        if not isinstance(interval, Interval):
112            raise TypeError("Invalid Interval")110            raise TypeError("Invalid Interval")
113111
114        new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone112        new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone
115        return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])113        return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
116            114            
117    def transposed(self, interval):115    def transposed(self, interval):
118        if not isinstance(interval, Interval):116        if not isinstance(interval, Interval):
119            raise TypeError("Invalid Interval")117            raise TypeError("Invalid Interval")
120118
121        transposed_root = self.find_new_value(self.root, interval)119        transposed_root = self.find_new_value(self.root, interval)
122        transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]120        transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]
123        121        
n124        return Chord([transposed_root] + transposed_tones)n122        return Chord(*([transposed_root] + transposed_tones))
125        123        
126    def is_minor(self):124    def is_minor(self):
127        return any(str(tone - self.root) == "minor 3rd" for tone in self.tones)125        return any(str(tone - self.root) == "minor 3rd" for tone in self.tones)
128    126    
129    def is_major(self):127    def is_major(self):
130        return any(str(tone - self.root) == "major 3rd" for tone in self.tones)128        return any(str(tone - self.root) == "major 3rd" for tone in self.tones)
131    129    
132    def is_power_chord(self):130    def is_power_chord(self):
133        return not self.is_major() and not self.is_minor()131        return not self.is_major() and not self.is_minor()
134    132    
135    def __add__(self, other):133    def __add__(self, other):
136        if isinstance(other, Tone):134        if isinstance(other, Tone):
n137            return Chord([self.root] + self.tones + [other])n135            return Chord(*([self.root] + self.tones + [other]))
138        elif isinstance(other, Chord):136        elif isinstance(other, Chord):
n139            return Chord([self.root] + self.tones + [other.root] + other.tones)n137            return Chord(*([self.root] + self.tones + [other.root] + other.tones))
140        return TypeError("Invalid Tone or Chord")138        return TypeError("Invalid Tone or Chord")
141    139    
142    def __sub__(self, other):140    def __sub__(self, other):
143        if isinstance(other, Tone):141        if isinstance(other, Tone):
144            new_tones = [self.root] + self.tones142            new_tones = [self.root] + self.tones
145            if other not in new_tones:143            if other not in new_tones:
146                raise TypeError (f"Cannot remove tone {other} from chord {self}")144                raise TypeError (f"Cannot remove tone {other} from chord {self}")
147            145            
148            new_tones.remove(other)146            new_tones.remove(other)
t149            return Chord(new_tones)t147            return Chord(*new_tones)
150        raise TypeError("Invalid Tone")148        raise TypeError("Invalid Tone")
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1class Tone:f1class Tone:
2    """Represent a musical tone."""2    """Represent a musical tone."""
3    3    
4    TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")4    TONE_SCALE = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
5    5    
6    def __init__(self, name):6    def __init__(self, name):
7        self.name = name7        self.name = name
8        8        
9    def __str__(self):9    def __str__(self):
10        return self.name10        return self.name
11    11    
12    def __int__(self):12    def __int__(self):
13        return Tone.TONE_SCALE.index(self.name)13        return Tone.TONE_SCALE.index(self.name)
14    14    
15    def __eq__(self, other):15    def __eq__(self, other):
n16        if isinstance(other, Tone):n
17            return self.name == other.name16        return self.name == other.name
18        return False
19    17    
20    def __hash__(self):18    def __hash__(self):
21        """Return a hash value for the Tone object using its name."""19        """Return a hash value for the Tone object using its name."""
22        return hash(self.name)20        return hash(self.name)
23    21    
24    def __add__(self, other):22    def __add__(self, other):
25        if isinstance(other, Tone):23        if isinstance(other, Tone):
26            return Chord(self, other)24            return Chord(self, other)
27        elif isinstance(other, Interval):25        elif isinstance(other, Interval):
28            new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone26            new_tone_index = Tone.TONE_SCALE.index(self.name) + other.semitone
29            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])27            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
30        raise TypeError("Invalid Tone or Interval.")28        raise TypeError("Invalid Tone or Interval.")
31    29    
32    def __radd__(self, other):30    def __radd__(self, other):
33        if isinstance(other, Interval):31        if isinstance(other, Interval):
34            raise TypeError("Invalid operation")32            raise TypeError("Invalid operation")
35    33    
36    def __sub__(self, other):34    def __sub__(self, other):
37        if isinstance(other, Tone):35        if isinstance(other, Tone):
38            return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))36            return Interval((int(self) - int(other)) % len(Tone.TONE_SCALE))
39        elif isinstance(other, Interval):37        elif isinstance(other, Interval):
40            new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone38            new_tone_index = Tone.TONE_SCALE.index(self.name) - other.semitone
41            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])39            return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
42        raise TypeError("Invalid Tone or Interval.")40        raise TypeError("Invalid Tone or Interval.")
43    41    
44    def __rsub__(self, other):42    def __rsub__(self, other):
45        if isinstance(other, Interval):43        if isinstance(other, Interval):
46            raise TypeError("Invalid operation")44            raise TypeError("Invalid operation")
4745
4846
49class Interval:47class Interval:
50    """Represent a music interval."""48    """Represent a music interval."""
51    49    
n52    SEMITONE_NAMES = {n50    SEMITONE_NAMES = (
53        0: "unison",51        "unison", "minor 2nd", "major 2nd", "minor 3rd", "major 3rd",
54        1: "minor 2nd",52        "perfect 4th", "diminished 5th", "perfect 5th", "minor 6th",
55        2: "major 2nd",53        "major 6th", "minor 7th", "major 7th"
56        3: "minor 3rd",
57        4: "major 3rd",
58        5: "perfect 4th",
59        6: "diminished 5th",
60        7: "perfect 5th",
61        8: "minor 6th",
62        9: "major 6th",
63        10: "minor 7th",
64        11: "major 7th",
65    }54    )
66    55    
67    def __init__(self, semitone):56    def __init__(self, semitone):
68        if not isinstance(semitone, int):57        if not isinstance(semitone, int):
69            raise TypeError("Invalid semitone")58            raise TypeError("Invalid semitone")
70            59            
71        self.semitone = semitone % len(Interval.SEMITONE_NAMES)60        self.semitone = semitone % len(Interval.SEMITONE_NAMES)
72        61        
73    def __str__(self):62    def __str__(self):
n74        return Interval.SEMITONE_NAMES.get(self.semitone, "Invalid semitone")n63        return Interval.SEMITONE_NAMES[self.semitone]
75      64      
76    def __neg__(self):65    def __neg__(self):
77        return Interval(-self.semitone)66        return Interval(-self.semitone)
78    67    
79    def __add__(self, other):68    def __add__(self, other):
80        if isinstance(other, Interval):69        if isinstance(other, Interval):
81            return Interval(self.semitone + other.semitone)70            return Interval(self.semitone + other.semitone)
82        raise TypeError("Invalid operation")71        raise TypeError("Invalid operation")
83    72    
84    def __sub__(self, other):73    def __sub__(self, other):
85        if isinstance(other, Interval):74        if isinstance(other, Interval):
86            return Interval(self.semitone - other.semitone)75            return Interval(self.semitone - other.semitone)
87        raise TypeError("Invalid operation")76        raise TypeError("Invalid operation")
8877
8978
90class Chord:79class Chord:
n91    """Represent a music cord."""n80    """Represent a music chord."""
92    81    
93    def __init__(self, *tones):82    def __init__(self, *tones):
94        if len(tones) == 1 and isinstance(tones[0], (list, tuple)):83        if len(tones) == 1 and isinstance(tones[0], (list, tuple)):
n95            tones = tones[0]n84           tones = tones[0]
96        85        
97        unique_tones = list(set(tones))86        unique_tones = list(set(tones))
98        if len(unique_tones) <= 1:87        if len(unique_tones) <= 1:
99            raise TypeError("Cannot have a chord made of only 1 unique tone")88            raise TypeError("Cannot have a chord made of only 1 unique tone")
10089
101        if not all(isinstance(tone, Tone) for tone in unique_tones):90        if not all(isinstance(tone, Tone) for tone in unique_tones):
102            raise TypeError("Invalid Tone found")91            raise TypeError("Invalid Tone found")
103        92        
104        self.root = tones[0]93        self.root = tones[0]
n105        self.tones =  list(filter(lambda tone: tone != self.root, unique_tones))n94        self.tones = list(filter(lambda tone: tone != self.root, unique_tones))
10695
107    def sort_tones(self):96    def sort_tones(self):
108        """Sort the tones relatively."""97        """Sort the tones relatively."""
109        98        
110        root_index = Tone.TONE_SCALE.index(str(self.root))99        root_index = Tone.TONE_SCALE.index(str(self.root))
111        relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]100        relative_scale = Tone.TONE_SCALE[root_index:] + Tone.TONE_SCALE[:root_index]
112        101        
113        return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))102        return sorted(self.tones, key=lambda tone: relative_scale.index(str(tone)))
114103
115    def __str__(self):104    def __str__(self):
116        sorted_tones = self.sort_tones()105        sorted_tones = self.sort_tones()
117        return f'{self.root}-{"-".join(map(str, sorted_tones))}'106        return f'{self.root}-{"-".join(map(str, sorted_tones))}'
118    107    
119    def find_new_value(self, tone, interval):108    def find_new_value(self, tone, interval):
120        """Set new values after transpose."""109        """Set new values after transpose."""
121        110        
122        if not isinstance(interval, Interval):111        if not isinstance(interval, Interval):
123            raise TypeError("Invalid Interval")112            raise TypeError("Invalid Interval")
124113
125        new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone114        new_tone_index = Tone.TONE_SCALE.index(str(tone)) + interval.semitone
126        return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])115        return Tone(Tone.TONE_SCALE[new_tone_index % len(Tone.TONE_SCALE)])
127            116            
128    def transposed(self, interval):117    def transposed(self, interval):
129        if not isinstance(interval, Interval):118        if not isinstance(interval, Interval):
130            raise TypeError("Invalid Interval")119            raise TypeError("Invalid Interval")
131120
132        transposed_root = self.find_new_value(self.root, interval)121        transposed_root = self.find_new_value(self.root, interval)
133        transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]122        transposed_tones = [self.find_new_value(tone, interval) for tone in self.tones]
134        123        
135        return Chord([transposed_root] + transposed_tones)124        return Chord([transposed_root] + transposed_tones)
136        125        
137    def is_minor(self):126    def is_minor(self):
n138        return any([True for tone in self.tones if str(tone - self.root) == "minor 3rd"])n127        return any(str(tone - self.root) == "minor 3rd" for tone in self.tones)
139    128    
140    def is_major(self):129    def is_major(self):
n141        return any([True for tone in self.tones if str(tone - self.root) == "major 3rd"])n130        return any(str(tone - self.root) == "major 3rd" for tone in self.tones)
142    131    
143    def is_power_chord(self):132    def is_power_chord(self):
144        return not self.is_major() and not self.is_minor()133        return not self.is_major() and not self.is_minor()
145    134    
146    def __add__(self, other):135    def __add__(self, other):
147        if isinstance(other, Tone):136        if isinstance(other, Tone):
148            return Chord([self.root] + self.tones + [other])137            return Chord([self.root] + self.tones + [other])
149        elif isinstance(other, Chord):138        elif isinstance(other, Chord):
150            return Chord([self.root] + self.tones + [other.root] + other.tones)139            return Chord([self.root] + self.tones + [other.root] + other.tones)
151        return TypeError("Invalid Tone or Chord")140        return TypeError("Invalid Tone or Chord")
152    141    
153    def __sub__(self, other):142    def __sub__(self, other):
154        if isinstance(other, Tone):143        if isinstance(other, Tone):
155            new_tones = [self.root] + self.tones144            new_tones = [self.root] + self.tones
156            if other not in new_tones:145            if other not in new_tones:
t157                raise TypeError (f"Cannot remove tone {str(other)} from chord {str(self)}")t146                raise TypeError (f"Cannot remove tone {other} from chord {self}")
158            147            
159            new_tones.remove(other)148            new_tones.remove(other)
160            return Chord(new_tones)149            return Chord(new_tones)
161        raise TypeError("Invalid Tone")150        raise TypeError("Invalid Tone")
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op