| 1 | 1 | class Scale: |
| 2 | 2 | def __init__(self, tonic): |
| 3 | | pass |
| 3 | # Edge Case: Normalize tonic to handle case insensitivity |
| 4 | # Preserve the original case to distinguish between major (uppercase) and minor (lowercase) keys |
| 5 | self.tonic = tonic |
| 6 | |
| 7 | # Edge Case: Handle special case for flat symbol in tonic |
| 8 | if len(self.tonic) > 1 and self.tonic[1] == 'b': |
| 9 | self.tonic = self.tonic[0].upper() + 'b' |
| 10 | |
| 11 | # Define the chromatic scales with sharps and flats (using ASCII symbols) |
| 12 | self.sharp_chromatic = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'] |
| 13 | self.flat_chromatic = ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab'] |
| 14 | |
| 15 | # Keys that use flats (according to specification table) |
| 16 | # Flat: F, B♭, E♭, A♭, D♭, G♭ and d, g, c, f, b♭, e♭ |
| 17 | self.flat_keys = {'F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'd', 'g', 'c', 'f', 'bb', 'eb'} |
| 18 | |
| 19 | # Determine whether to use flats or sharps |
| 20 | # Edge Case: C and a follow sharps convention for ascending scale |
| 21 | if self.tonic in self.flat_keys: |
| 22 | self.chromatic_scale = self.flat_chromatic |
| 23 | else: |
| 24 | self.chromatic_scale = self.sharp_chromatic |
| 25 | |
| 26 | # Edge Case: Handle enharmonic equivalents for tonics like 'Bb' or 'A#' |
| 27 | # Find the tonic in the selected scale |
| 28 | # Use uppercase version for lookup since scales contain uppercase notes |
| 29 | # For single letters, just uppercase them. For multi-character, uppercase the first letter. |
| 30 | lookup_tonic = self.tonic.upper() if len(self.tonic) == 1 else self.tonic[0].upper() + self.tonic[1:] |
| 31 | try: |
| 32 | self.tonic_index = self.chromatic_scale.index(lookup_tonic) |
| 33 | except ValueError: |
| 34 | # If not found, try to find enharmonic equivalent |
| 35 | # This handles cases like 'A#' when using flats or 'Bb' when using sharps |
| 36 | if self.chromatic_scale is self.sharp_chromatic: |
| 37 | enharmonic_map = {'Bb': 'A#', 'Db': 'C#', 'Eb': 'D#', 'Gb': 'F#', 'Ab': 'G#'} |
| 38 | if self.tonic in enharmonic_map: |
| 39 | self.tonic = enharmonic_map[self.tonic] |
| 40 | else: # flat_chromatic |
| 41 | enharmonic_map = {'A#': 'Bb', 'C#': 'Db', 'D#': 'Eb', 'F#': 'Gb', 'G#': 'Ab'} |
| 42 | if self.tonic in enharmonic_map: |
| 43 | self.tonic = enharmonic_map[self.tonic] |
| 44 | |
| 45 | try: |
| 46 | self.tonic_index = self.chromatic_scale.index(lookup_tonic) |
| 47 | except ValueError: |
| 48 | # Edge Case: Invalid tonic note |
| 49 | raise ValueError(f"Invalid tonic: {tonic}") |
| 4 | 50 | |
| 5 | 51 | def chromatic(self): |
| 6 | | pass |
| 7 | | |
| 52 | # Return the chromatic scale starting with the tonic |
| 53 | # All notes should be uppercase except for "b" in flats |
| 54 | scale = [self.chromatic_scale[(self.tonic_index + i) % 12] for i in range(12)] |
| 55 | return [note.upper() if len(note) == 1 else note[0].upper() + note[1:] for note in scale] |
| 56 | |
| 8 | 57 | def interval(self, intervals): |
| 9 | | pass |
| 58 | # Edge Case: Empty interval pattern |
| 59 | if not intervals: |
| 60 | return [self.tonic.upper() if len(self.tonic) == 1 else self.tonic[0].upper() + self.tonic[1:]] |
| 61 | |
| 62 | # Define interval steps |
| 63 | interval_steps = {'m': 1, 'M': 2, 'A': 3} |
| 64 | |
| 65 | # Start with properly formatted tonic |
| 66 | first_note = self.tonic.upper() if len(self.tonic) == 1 else self.tonic[0].upper() + self.tonic[1:] |
| 67 | result = [first_note] |
| 68 | current_index = self.tonic_index |
| 69 | |
| 70 | # Edge Case: Invalid interval characters |
| 71 | for interval in intervals: |
| 72 | if interval not in interval_steps: |
| 73 | raise ValueError(f"Invalid interval: {interval}") |
| 74 | |
| 75 | step = interval_steps[interval] |
| 76 | current_index = (current_index + step) % 12 |
| 77 | note = self.chromatic_scale[current_index] |
| 78 | # Format note: uppercase except for "b" in flats |
| 79 | formatted_note = note.upper() if len(note) == 1 else note[0].upper() + note[1:] |
| 80 | result.append(formatted_note) |
| 81 | |
| 82 | return result |
| 83 | # Handled Edge Cases: Invalid tonic notes, Empty interval patterns, Case sensitivity, Enharmonic equivalents, Invalid interval characters |