| 5 | 6 | |
| 6 | 7 | export class Scale { |
| 7 | 8 | constructor(tonic) { |
| 8 | | throw new Error('Remove this line and implement the function'); |
| 9 | this.tonic = tonic.toLowerCase(); |
| 10 | |
| 11 | // Edge Case: Handle invalid tonic notes |
| 12 | const validTonics = ['c', 'c#', 'db', 'd', 'd#', 'eb', 'e', 'f', 'f#', 'gb', 'g', 'g#', 'ab', 'a', 'a#', 'bb', 'b']; |
| 13 | if (!validTonics.includes(this.tonic)) { |
| 14 | throw new Error('Invalid tonic'); |
| 15 | } |
| 16 | |
| 17 | // Define which tonics use sharps and which use flats based on the problem statement table |
| 18 | // Major sharp keys: G, D, A, E, B, F♯ |
| 19 | // Minor sharp keys: e, b, f♯, c♯, g♯, d♯ |
| 20 | // Major flat keys: F, B♭, E♭, A♭, D♭, G♭ |
| 21 | // Minor flat keys: d, g, c, f, b♭, e♭ |
| 22 | // Natural keys: C (major), a (minor) - use sharps when ascending |
| 23 | |
| 24 | this.sharpKeys = ['c', 'g', 'd', 'a', 'e', 'b', 'f#', 'e', 'b', 'f#', 'c#', 'g#', 'd#']; |
| 25 | this.flatKeys = ['f', 'bb', 'eb', 'ab', 'db', 'gb', 'd', 'g', 'c', 'f', 'bb', 'eb']; |
| 26 | |
| 27 | // Edge Case: Handle special case for natural notes C and a |
| 28 | if (this.tonic === 'c' || this.tonic === 'a') { |
| 29 | this.useSharps = true; |
| 30 | } else { |
| 31 | this.useSharps = this.sharpKeys.includes(this.tonic); |
| 32 | } |
| 33 | |
| 34 | // Define the chromatic scales |
| 35 | this.sharpChromatic = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']; |
| 36 | this.flatChromatic = ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab']; |
| 37 | |
| 38 | // Handled Edge Cases: Invalid tonic notes, Special case for natural notes C and a |
| 9 | 39 | } |
| 10 | 40 | |
| 11 | 41 | chromatic() { |
| 12 | | throw new Error('Remove this line and implement the function'); |
| 42 | let scale; |
| 43 | if (this.useSharps) { |
| 44 | scale = this.sharpChromatic; |
| 45 | } else { |
| 46 | scale = this.flatChromatic; |
| 47 | } |
| 48 | |
| 49 | // Find the index of the tonic |
| 50 | const tonicUpper = this.tonic.charAt(0).toUpperCase() + this.tonic.slice(1); |
| 51 | let tonicIndex = scale.indexOf(tonicUpper); |
| 52 | |
| 53 | // Edge Case: Handle flat tonics in sharp scale and vice versa |
| 54 | if (tonicIndex === -1) { |
| 55 | // Try to find enharmonic equivalent |
| 56 | const enharmonicMap = { |
| 57 | 'Db': 'C#', 'Eb': 'D#', 'Gb': 'F#', 'Ab': 'G#', 'Bb': 'A#', |
| 58 | 'C#': 'Db', 'D#': 'Eb', 'F#': 'Gb', 'G#': 'Ab', 'A#': 'Bb' |
| 59 | }; |
| 60 | tonicIndex = scale.indexOf(enharmonicMap[tonicUpper]); |
| 61 | } |
| 62 | |
| 63 | // Edge Case: Handle case where tonic still not found |
| 64 | if (tonicIndex === -1) { |
| 65 | throw new Error('Tonic not found in chromatic scale'); |
| 66 | } |
| 67 | |
| 68 | // Rotate the scale to start with the tonic |
| 69 | return [...scale.slice(tonicIndex), ...scale.slice(0, tonicIndex)]; |
| 70 | |
| 71 | // Handled Edge Cases: Flat tonics in sharp scale, Sharp tonics in flat scale, Tonic not found in chromatic scale |
| 13 | 72 | } |
| 14 | 73 | |
| 15 | 74 | interval(intervals) { |
| 16 | | throw new Error('Remove this line and implement the function'); |
| 75 | // Edge Case: Handle empty or invalid intervals |
| 76 | if (!intervals || typeof intervals !== 'string') { |
| 77 | throw new Error('Invalid intervals'); |
| 78 | } |
| 79 | |
| 80 | const chromaticScale = this.chromatic(); |
| 81 | const result = [chromaticScale[0]]; // Start with the tonic |
| 82 | let currentIndex = 0; |
| 83 | |
| 84 | // Define interval mappings |
| 85 | const intervalSteps = { |
| 86 | 'm': 1, // minor second (half step) |
| 87 | 'M': 2, // major second (whole step) |
| 88 | 'A': 3 // augmented second (three semitones) |
| 89 | }; |
| 90 | |
| 91 | for (const interval of intervals) { |
| 92 | // Edge Case: Handle invalid interval characters |
| 93 | if (!intervalSteps.hasOwnProperty(interval)) { |
| 94 | throw new Error('Invalid interval character'); |
| 95 | } |
| 96 | |
| 97 | currentIndex += intervalSteps[interval]; |
| 98 | // Edge Case: Handle wrapping around the chromatic scale |
| 99 | currentIndex = currentIndex % 12; |
| 100 | result.push(chromaticScale[currentIndex]); |
| 101 | } |
| 102 | |
| 103 | return result; |
| 104 | |
| 105 | // Handled Edge Cases: Empty or invalid intervals, Invalid interval characters, Wrapping around the chromatic scale |
| 17 | 106 | } |
| 18 | 107 | } |