├── .gitignore ├── .npmignore ├── .prettierrc ├── README.md ├── jest.config.js ├── package.json ├── src ├── __tests__ │ ├── accidental.test.ts │ ├── chord.test.ts │ ├── interval.test.ts │ ├── keySignature.test.ts │ ├── keySignatures │ │ ├── keySignatureOfA.test.ts │ │ ├── keySignatureOfAb.test.ts │ │ ├── keySignatureOfB.test.ts │ │ ├── keySignatureOfBb.test.ts │ │ ├── keySignatureOfC.test.ts │ │ ├── keySignatureOfD.test.ts │ │ ├── keySignatureOfDb.test.ts │ │ ├── keySignatureOfE.test.ts │ │ ├── keySignatureOfEb.test.ts │ │ ├── keySignatureOfF.test.ts │ │ ├── keySignatureOfFSharp.test.ts │ │ ├── keySignatureOfG.test.ts │ │ └── keySignatureOfGb.test.ts │ ├── name.test.ts │ ├── note.test.ts │ ├── octave.test.ts │ └── utils │ │ ├── inputSanitization.test.ts │ │ └── invertArray.test.ts ├── accidental.ts ├── accidentals │ ├── IAccidental.ts │ ├── doubleFlat.ts │ ├── doubleSharp.ts │ ├── flat.ts │ ├── index.ts │ ├── natural.ts │ └── sharp.ts ├── chord.ts ├── chords │ ├── augmentedChord.ts │ ├── augmentedMajorSeventhChord.ts │ ├── augmentedSeventhChord.ts │ ├── baseChord.ts │ ├── diminishedChord.ts │ ├── diminishedSeventhChord.ts │ ├── dominantSeventhChord.ts │ ├── halfDiminishedSeventhChord.ts │ ├── index.ts │ ├── invertedChord.ts │ ├── majorChord.ts │ ├── majorSeventhChord.ts │ ├── minorChord.ts │ ├── minorMajorSeventh.ts │ ├── minorSeventhChord.ts │ ├── minorSixthChord.ts │ ├── sixthChord.ts │ ├── strategies.ts │ ├── suspendedChord.ts │ └── suspendedSecondChord.ts ├── definedInterval.ts ├── index.ts ├── interval.ts ├── keySignature.ts ├── keySignatures │ ├── baseKeySignature.ts │ ├── index.ts │ ├── keySignatureOfA.ts │ ├── keySignatureOfAb.ts │ ├── keySignatureOfB.ts │ ├── keySignatureOfBb.ts │ ├── keySignatureOfC.ts │ ├── keySignatureOfD.ts │ ├── keySignatureOfDb.ts │ ├── keySignatureOfE.ts │ ├── keySignatureOfEb.ts │ ├── keySignatureOfF.ts │ ├── keySignatureOfFSharp.ts │ ├── keySignatureOfG.ts │ └── keySignatureOfGb.ts ├── name.ts ├── note.ts ├── octave.ts └── utils │ ├── index.ts │ ├── inputSanitization.ts │ └── invertArray.ts ├── tsconfig.build.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | lib/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | src/__tests__/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all", 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chord JS 2 | 3 | Chord JS is a music theory package that identifies notes, chords, and key signatures on an 88-key piano. 4 | 5 | To install run 6 | 7 | ```bash 8 | npm install @patrady/chord-js 9 | ``` 10 | 11 | or if you're using yarn 12 | 13 | ```bash 14 | yarn add @patrady/chord-js 15 | ``` 16 | 17 | ## Chords 18 | 19 | To translate a series of notes into a chord, use 20 | 21 | ```ts 22 | import { Chord } from '@patrady/chord-js'; 23 | 24 | const chord = new Chord.for('C E G'); 25 | 26 | chord?.getName(); // C 27 | ``` 28 | 29 | This table shows the type of supported chords with examples 30 | 31 | | Chord | Example | 32 | | -------------------------------------------------------------------------------------- | ---------------------------------- | 33 | | [Major](https://en.wikipedia.org/wiki/Major_chord) | `Chord.for("C E G"); // C` | 34 | | [Minor](https://en.wikipedia.org/wiki/Minor_chord) | `Chord.for("C Eb G"); // Cm` | 35 | | [Suspended](https://en.wikipedia.org/wiki/Suspended_chord) | `Chord.for("C F G"); // Csus` | 36 | | [Suspended Second](https://en.wikipedia.org/wiki/Suspended_chord) | `Chord.for("C D G"); // Csus2` | 37 | | [Augmented](https://en.wikipedia.org/wiki/Augmented_triad) | `Chord.for("C E G#"); // Caug` | 38 | | [Diminished](https://en.wikipedia.org/wiki/Diminished_triad) | `Chord.for("C Eb Gb"); // Cdim` | 39 | | [Inverted](https://en.wikipedia.org/wiki/Major_chord#Inversions) | `Chord.for("E G C"); // C/E` | 40 | | [Sixth](https://en.wikipedia.org/wiki/Sixth_chord) | `Chord.for("C E G A"); // C6` | 41 | | [Minor Sixth](https://en.wikipedia.org/wiki/Sixth_chord) | `Chord.for('C Eb G A'); // Cm6` | 42 | | [Dominant Seventh](https://en.wikipedia.org/wiki/Dominant_seventh_chord) | `Chord.for("C E G Bb"); // C7` | 43 | | [Diminished Seventh](https://en.wikipedia.org/wiki/Diminished_seventh_chord) | `Chord.for("C Eb Gb A"); // Cdim7` | 44 | | [Augmented Seventh](https://en.wikipedia.org/wiki/Augmented_seventh_chord) | `Chord.for("C E G# Bb"); // C+7` | 45 | | [Major Seventh](https://en.wikipedia.org/wiki/Major_seventh_chord) | `Chord.for("C E G B"); // Cmaj7` | 46 | | [Augmented Major Seventh](https://en.wikipedia.org/wiki/Augmented_major_seventh_chord) | `Chord.for("C E G# B"); // Cmaj+7` | 47 | | [Minor Seventh](https://en.wikipedia.org/wiki/Minor_seventh_chord) | `Chord.for("C Eb G Bb"); // Cm7` | 48 | | [Minor Major Seventh](https://en.wikipedia.org/wiki/Minor_major_seventh_chord) | `Chord.for("C Eb G B"); // Cm7+` | 49 | | [Half-Diminished Seventh](https://en.wikipedia.org/wiki/Half-diminished_seventh_chord) | `Chord.for("C Eb Gb Bb"); // Cø7` | 50 | 51 | ### Key Signatures 52 | 53 | A Key Signature is a combination of sharps and flats at the beginning of each stave. 54 | 55 | ```ts 56 | import { Note, KeySignatureOfD } from '@patrady/chord-js'; 57 | 58 | new KeySignatureOfD().getNotes(); // D, E, F#, G, A, B, C#, D 59 | 60 | new KeySignatureOfD().normalize(new Note('Gb')); // F# 61 | 62 | new KeySignatureOfD().isInKey(new Note('Gb')); // false 63 | new KeySignatureOfD().isInKey(new Note('F#')); // true 64 | ``` 65 | 66 | | Attribute | Description | Example | 67 | | ----------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------- | 68 | | `getNotes()` | Returns an array of eight notes from the base to the octave. | `new KeySignatureOfD().getNotes(); // D, E, F#, G, A, B, C#, D` | 69 | | `normalize(note)` | Normalizes a note from one key signature to the current key signature. | `new KeySignatureOfD().normalize(new Note("Gb")); // F#` | 70 | | `isInKey(note)` | Returns whether a note is in the key signature | `new KeySignatureOfD().isInKey(new Note("Gb")); // false` | 71 | 72 | ### Supported Key Signatures 73 | 74 | The major key signatures are supported but less popular ones are not. Check this table to see if the one you need is supported: 75 | 76 | | Key Signature | Supported | Name | 77 | | ------------- | --------- | ---------------------- | 78 | | C | ✅ | `KeySignatureOfC` | 79 | | Cb | ❌ | | 80 | | C# | ❌ | | 81 | | D | ✅ | `KeySignatureOfD` | 82 | | Db | ✅ | `KeySignatureOfDb` | 83 | | D# | ❌ | | 84 | | E | ✅ | `KeySignatureOfE` | 85 | | Eb | ✅ | `KeySignatureOfEb` | 86 | | E# | ❌ | | 87 | | F | ✅ | `KeySignatureOfF` | 88 | | Fb | ❌ | | 89 | | F# | ✅ | `KeySignatureOfFsharp` | 90 | | G | ✅ | `KeySignatureOfG` | 91 | | Gb | ✅ | `KeySignatureOfGb` | 92 | | G# | ❌ | | 93 | | A | ✅ | `KeySignatureOfA` | 94 | | Ab | ✅ | `KeySignatureOfAb` | 95 | | A# | ❌ | | 96 | | B | ✅ | `KeySignatureOfB` | 97 | | Bb | ✅ | `KeySignatureOfBb` | 98 | | B# | ❌ | | 99 | 100 | 101 | ## Notes 102 | 103 | A Note is the fundamental element of music. Notes are simply frequencies and are used to create [chords](#chords) and [key signatures](#key-signatures). 104 | 105 | ```ts 106 | import { Note } from '@patrady/chord-js'; 107 | 108 | const note = new Note('Eb4'); 109 | ``` 110 | 111 | | Attribute | Description | Example | 112 | | --------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------- | 113 | | `getName()` | The name of the note and accidental | `note.getName(); // Eb` | 114 | | `getScientificName()` | The name of the note, accidental, and octave | `note.getScientificName(); // Eb4` | 115 | | `getOctave()` | The octave between 0 and 8 | `note.getOctave(); // 4` | 116 | | `getFrequency()` | The frequency in Hz, up to 5 decimal places | `note.getFrequency(); // 311.12698` | 117 | | `getKeyNumber()` | The [number](https://en.wikipedia.org/wiki/Piano_key_frequencies) of the key on an 88-key piano | `note.getKeyNumber(); // 43` | 118 | | `getMidiValue()` | The [MIDI note](https://en.wikipedia.org/wiki/Piano_key_frequencies) of the key on an 88-key piano | `note.getMidiValue(); // 63` | 119 | 120 | ## MIDI Keyboard 121 | 122 | When interacting with a MIDI keyboard and you want to convert a [MIDI value](https://www.inspiredacoustics.com/en/MIDI_note_numbers_and_center_frequencies) to a note, use 123 | 124 | ```ts 125 | import { Note } from '@patrady/chord-js'; 126 | 127 | const note = Note.fromMidi(24); 128 | 129 | note.getScientificName(); // C1 130 | ``` 131 | 132 | For [enharmonic](https://en.wikipedia.org/wiki/Enharmonic) notes, the MIDI value will be the same. For example, C# and Db in the 1st octave will have the same MIDI value of 25. 133 | To choose a specific enharmonic, normalize the note to a key signature: 134 | 135 | ```ts 136 | import { Note, KeySignatureOfD, KeySignatureOfDb } from '@patrady/chord-js'; 137 | 138 | const note = Note.fromMidi(25); 139 | 140 | new KeySignatureOfD().normalize(note); // C# 141 | new KeySignatureOfDb().normalize(note); // Db 142 | ``` 143 | 144 | A chord can also be determined from the MIDI notes like so 145 | 146 | ```ts 147 | import { Note } from '@patrady/chord-js'; 148 | 149 | const C = Note.fromMidi(60); 150 | const E = Note.fromMidi(64); 151 | const G = Note.fromMidi(67); 152 | 153 | Chord.for([C, E, G])?.getName(); // C 154 | ``` 155 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@patrady/chord-js", 3 | "version": "2.2.2", 4 | "main": "lib/index.js", 5 | "types": "lib/index.d.ts", 6 | "repository": "https://github.com/patrady/chord-js.git", 7 | "author": "Patrick Brady ", 8 | "license": "MIT", 9 | "private": false, 10 | "files": ["lib/**/*"], 11 | "scripts": { 12 | "test": "jest", 13 | "build": "yarn run clean && tsc --project tsconfig.build.json", 14 | "clean": "rm -r ./lib", 15 | "format": "prettier --write \"src/**/*.ts\"", 16 | "lint": "tslint -p tsconfig.json", 17 | "prepare": "yarn build", 18 | "prepublishOnly": "yarn test && yarn lint", 19 | "preversion": "yarn lint", 20 | "version": "yarn format && git add -A src", 21 | "postversion": "git push && git push --tags" 22 | }, 23 | "devDependencies": { 24 | "@types/jest": "^28.1.8", 25 | "jest": "^29.0.1", 26 | "prettier": "^2.7.1", 27 | "ts-jest": "^28.0.8", 28 | "tslint": "^6.1.3", 29 | "tslint-config-prettier": "^1.18.0", 30 | "typescript": "^4.8.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/__tests__/accidental.test.ts: -------------------------------------------------------------------------------- 1 | import { Accidental } from '../accidental'; 2 | 3 | describe('#new', () => { 4 | describe('with a valid accidental', () => { 5 | it('returns an accidental', () => { 6 | expect(Accidental.for('b')).toEqual(Accidental.flat()); 7 | expect(Accidental.for('𝄫')).toEqual(Accidental.doubleFlat()); 8 | expect(Accidental.for('#')).toEqual(Accidental.sharp()); 9 | expect(Accidental.for('𝄪')).toEqual(Accidental.doubleSharp()); 10 | expect(Accidental.for('♮')).toEqual(Accidental.natural()); 11 | }); 12 | }); 13 | 14 | describe('with no value', () => { 15 | it('returns a natural', () => { 16 | expect(Accidental.for()).toEqual(Accidental.natural()); 17 | }); 18 | }); 19 | }); 20 | 21 | describe('#getKeyIndex', () => { 22 | describe('with a sharp', () => { 23 | it('returns 1', () => { 24 | expect(Accidental.for('#').getKeyIndex()).toEqual(1); 25 | }); 26 | }); 27 | 28 | describe('with a double sharp', () => { 29 | it('returns 2', () => { 30 | expect(Accidental.for('𝄪').getKeyIndex()).toEqual(2); 31 | }); 32 | }); 33 | 34 | describe('with a flat', () => { 35 | it('returns -1', () => { 36 | expect(Accidental.for('b').getKeyIndex()).toEqual(-1); 37 | }); 38 | }); 39 | 40 | describe('with a double flat', () => { 41 | it('returns -2', () => { 42 | expect(Accidental.for('𝄫').getKeyIndex()).toEqual(-2); 43 | }); 44 | }); 45 | 46 | describe('with a natural', () => { 47 | it('returns 0', () => { 48 | expect(Accidental.for('♮').getKeyIndex()).toEqual(0); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('#getValue', () => { 54 | describe('with a sharp', () => { 55 | it('returns a sharp', () => { 56 | expect(Accidental.for('#').getValue()).toEqual('#'); 57 | }); 58 | }); 59 | 60 | describe('with a double sharp', () => { 61 | it('returns a double sharp', () => { 62 | expect(Accidental.for('𝄪').getValue()).toEqual('𝄪'); 63 | }); 64 | }); 65 | 66 | describe('with a flat', () => { 67 | it('returns a flat', () => { 68 | expect(Accidental.for('b').getValue()).toEqual('b'); 69 | }); 70 | }); 71 | 72 | describe('with a double flat', () => { 73 | it('returns a double flat', () => { 74 | expect(Accidental.for('𝄫').getValue()).toEqual('𝄫'); 75 | }); 76 | }); 77 | 78 | describe('with a natural', () => { 79 | it('returns an empty string', () => { 80 | expect(Accidental.for('♮').getValue()).toEqual(''); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/__tests__/chord.test.ts: -------------------------------------------------------------------------------- 1 | import { Chord } from '../chord'; 2 | 3 | describe('Major Chords', () => { 4 | it('returns the name of the major chord', () => { 5 | expect(Chord.for('C E G')?.getName()).toEqual('C'); 6 | expect(Chord.for('C# E# G#')?.getName()).toEqual('C#'); 7 | expect(Chord.for('Db F Ab')?.getName()).toEqual('Db'); 8 | expect(Chord.for('D F# A')?.getName()).toEqual('D'); 9 | expect(Chord.for('Eb G Bb')?.getName()).toEqual('Eb'); 10 | expect(Chord.for('E G# B')?.getName()).toEqual('E'); 11 | expect(Chord.for('F A C5')?.getName()).toEqual('F'); 12 | expect(Chord.for('F# A# C#5')?.getName()).toEqual('F#'); 13 | expect(Chord.for('Gb Bb Db5')?.getName()).toEqual('Gb'); 14 | expect(Chord.for('G B D5')?.getName()).toEqual('G'); 15 | expect(Chord.for('G# B# D#5')?.getName()).toEqual('G#'); 16 | expect(Chord.for('Ab3 C Eb')?.getName()).toEqual('Ab'); 17 | expect(Chord.for('A3 C# E')?.getName()).toEqual('A'); 18 | expect(Chord.for('Bb3 D F')?.getName()).toEqual('Bb'); 19 | expect(Chord.for('B3 D# F#')?.getName()).toEqual('B'); 20 | }); 21 | }); 22 | 23 | describe('Inverted Major Chords', () => { 24 | it('returns the name of the inverted major chord', () => { 25 | expect(Chord.for('E G C5')?.getName()).toEqual('C/E'); 26 | expect(Chord.for('G3 C E')?.getName()).toEqual('C/G'); 27 | expect(Chord.for('G#3 C# E#')?.getName()).toEqual('C#/G#'); 28 | expect(Chord.for('E# G# C#5')?.getName()).toEqual('C#/E#'); 29 | expect(Chord.for('F Ab Db5')?.getName()).toEqual('Db/F'); 30 | expect(Chord.for('Ab3 Db F')?.getName()).toEqual('Db/Ab'); 31 | expect(Chord.for('A3 D F#')?.getName()).toEqual('D/A'); 32 | expect(Chord.for('F# A D5')?.getName()).toEqual('D/F#'); 33 | expect(Chord.for('G Bb Eb5')?.getName()).toEqual('Eb/G'); 34 | expect(Chord.for('Bb3 Eb G')?.getName()).toEqual('Eb/Bb'); 35 | expect(Chord.for('G# B E5')?.getName()).toEqual('E/G#'); 36 | expect(Chord.for('B3 E G#')?.getName()).toEqual('E/B'); 37 | expect(Chord.for('A3 C F')?.getName()).toEqual('F/A'); 38 | expect(Chord.for('C F A')?.getName()).toEqual('F/C'); 39 | expect(Chord.for('A#3 C# F#')?.getName()).toEqual('F#/A#'); 40 | expect(Chord.for('C# F# A#')?.getName()).toEqual('F#/C#'); 41 | expect(Chord.for('Bb3 Db Gb')?.getName()).toEqual('Gb/Bb'); 42 | expect(Chord.for('Db Gb Bb')?.getName()).toEqual('Gb/Db'); 43 | expect(Chord.for('B3 D G')?.getName()).toEqual('G/B'); 44 | expect(Chord.for('D G B')?.getName()).toEqual('G/D'); 45 | expect(Chord.for('B#3 D# G#')?.getName()).toEqual('G#/B#'); 46 | expect(Chord.for('D# G# B#')?.getName()).toEqual('G#/D#'); 47 | expect(Chord.for('C Eb Ab')?.getName()).toEqual('Ab/C'); 48 | expect(Chord.for('Eb Ab C5')?.getName()).toEqual('Ab/Eb'); 49 | expect(Chord.for('C# E A')?.getName()).toEqual('A/C#'); 50 | expect(Chord.for('E A C#5')?.getName()).toEqual('A/E'); 51 | expect(Chord.for('D F Bb')?.getName()).toEqual('Bb/D'); 52 | expect(Chord.for('F Bb D5')?.getName()).toEqual('Bb/F'); 53 | expect(Chord.for('D# F# B')?.getName()).toEqual('B/D#'); 54 | expect(Chord.for('F# B D#5')?.getName()).toEqual('B/F#'); 55 | }); 56 | }); 57 | 58 | describe('Minor Chords', () => { 59 | it('returns the name of the minor chord', () => { 60 | expect(Chord.for('C Eb G')?.getName()).toEqual('Cm'); 61 | expect(Chord.for('C# E G#')?.getName()).toEqual('C#m'); 62 | expect(Chord.for('Db Fb Ab')?.getName()).toEqual('Dbm'); 63 | expect(Chord.for('D F A')?.getName()).toEqual('Dm'); 64 | expect(Chord.for('Eb Gb Bb')?.getName()).toEqual('Ebm'); 65 | expect(Chord.for('E G B')?.getName()).toEqual('Em'); 66 | expect(Chord.for('F Ab C5')?.getName()).toEqual('Fm'); 67 | expect(Chord.for('F# A C#5')?.getName()).toEqual('F#m'); 68 | expect(Chord.for('G Bb D5')?.getName()).toEqual('Gm'); 69 | expect(Chord.for('G# B D#5')?.getName()).toEqual('G#m'); 70 | expect(Chord.for('Ab3 Cb Eb')?.getName()).toEqual('Abm'); 71 | expect(Chord.for('A3 C E')?.getName()).toEqual('Am'); 72 | expect(Chord.for('Bb3 Db F')?.getName()).toEqual('Bbm'); 73 | expect(Chord.for('B3 D F#')?.getName()).toEqual('Bm'); 74 | }); 75 | }); 76 | 77 | describe('Inverted Minor Chords', () => { 78 | it('returns the name of the inverted minor chord', () => { 79 | expect(Chord.for('Eb G C5')?.getName()).toEqual('Cm/Eb'); 80 | expect(Chord.for('G3 C Eb')?.getName()).toEqual('Cm/G'); 81 | expect(Chord.for('E G# C#5')?.getName()).toEqual('C#m/E'); 82 | expect(Chord.for('G#3 C# E')?.getName()).toEqual('C#m/G#'); 83 | expect(Chord.for('Fb Ab Db5')?.getName()).toEqual('Dbm/Fb'); 84 | expect(Chord.for('Ab3 Db Fb')?.getName()).toEqual('Dbm/Ab'); 85 | expect(Chord.for('F A D5')?.getName()).toEqual('Dm/F'); 86 | expect(Chord.for('A3 D F')?.getName()).toEqual('Dm/A'); 87 | expect(Chord.for('Gb Bb Eb5')?.getName()).toEqual('Ebm/Gb'); 88 | expect(Chord.for('Bb3 Eb Gb')?.getName()).toEqual('Ebm/Bb'); 89 | expect(Chord.for('G B E5')?.getName()).toEqual('Em/G'); 90 | expect(Chord.for('B3 E G')?.getName()).toEqual('Em/B'); 91 | expect(Chord.for('Ab3 C F')?.getName()).toEqual('Fm/Ab'); 92 | expect(Chord.for('C F Ab')?.getName()).toEqual('Fm/C'); 93 | expect(Chord.for('A3 C# F#')?.getName()).toEqual('F#m/A'); 94 | expect(Chord.for('C# F# A')?.getName()).toEqual('F#m/C#'); 95 | expect(Chord.for('Bb3 D G')?.getName()).toEqual('Gm/Bb'); 96 | expect(Chord.for('D G Bb')?.getName()).toEqual('Gm/D'); 97 | expect(Chord.for('B3 D# G#')?.getName()).toEqual('G#m/B'); 98 | expect(Chord.for('D# G# B')?.getName()).toEqual('G#m/D#'); 99 | expect(Chord.for('Cb Eb Ab')?.getName()).toEqual('Abm/Cb'); 100 | expect(Chord.for('Eb Ab Cb5')?.getName()).toEqual('Abm/Eb'); 101 | expect(Chord.for('C E A')?.getName()).toEqual('Am/C'); 102 | expect(Chord.for('E A C5')?.getName()).toEqual('Am/E'); 103 | expect(Chord.for('Db F Bb')?.getName()).toEqual('Bbm/Db'); 104 | expect(Chord.for('F Bb Db5')?.getName()).toEqual('Bbm/F'); 105 | expect(Chord.for('D F# B')?.getName()).toEqual('Bm/D'); 106 | expect(Chord.for('F# B D5')?.getName()).toEqual('Bm/F#'); 107 | }); 108 | }); 109 | 110 | describe('Suspended Chords', () => { 111 | it('returns the name of the suspended chord', () => { 112 | expect(Chord.for('C F G')?.getName()).toEqual('Csus'); 113 | expect(Chord.for('C# F# G#')?.getName()).toEqual('C#sus'); 114 | expect(Chord.for('Db Gb Ab')?.getName()).toEqual('Dbsus'); 115 | expect(Chord.for('Eb Ab Bb')?.getName()).toEqual('Ebsus'); 116 | expect(Chord.for('E A B')?.getName()).toEqual('Esus'); 117 | expect(Chord.for('F Bb C5')?.getName()).toEqual('Fsus'); 118 | expect(Chord.for('F# B C#5')?.getName()).toEqual('F#sus'); 119 | expect(Chord.for('G3 C D')?.getName()).toEqual('Gsus'); 120 | expect(Chord.for('Ab3 Db Eb')?.getName()).toEqual('Absus'); 121 | expect(Chord.for('A3 D E')?.getName()).toEqual('Asus'); 122 | expect(Chord.for('Bb3 Eb F')?.getName()).toEqual('Bbsus'); 123 | expect(Chord.for('B3 E F#')?.getName()).toEqual('Bsus'); 124 | }); 125 | }); 126 | 127 | describe('Inverted Suspended Chords', () => { 128 | it('returns the name of the inverted suspended chord', () => { 129 | expect(Chord.for('G3 C F')?.getName()).toEqual('Csus/G'); 130 | expect(Chord.for('G#3 C# F#')?.getName()).toEqual('C#sus/G#'); 131 | expect(Chord.for('Ab3 Db Gb')?.getName()).toEqual('Dbsus/Ab'); 132 | expect(Chord.for('Bb3 Eb Ab')?.getName()).toEqual('Ebsus/Bb'); 133 | expect(Chord.for('B3 E A')?.getName()).toEqual('Esus/B'); 134 | expect(Chord.for('C F Bb')?.getName()).toEqual('Fsus/C'); 135 | expect(Chord.for('C# F# B')?.getName()).toEqual('F#sus/C#'); 136 | expect(Chord.for('D G C5')?.getName()).toEqual('Gsus/D'); 137 | expect(Chord.for('Eb Ab Db5')?.getName()).toEqual('Absus/Eb'); 138 | expect(Chord.for('E A D5')?.getName()).toEqual('Asus/E'); 139 | expect(Chord.for('F Bb Eb5')?.getName()).toEqual('Bbsus/F'); 140 | expect(Chord.for('F# B E5')?.getName()).toEqual('Bsus/F#'); 141 | }); 142 | }); 143 | 144 | describe('Suspended Second Chords', () => { 145 | it('returns the name of the suspended second chord', () => { 146 | expect(Chord.for('C D G')?.getName()).toEqual('Csus2'); 147 | expect(Chord.for('C# D# G#')?.getName()).toEqual('C#sus2'); 148 | expect(Chord.for('Db Eb Ab')?.getName()).toEqual('Dbsus2'); 149 | expect(Chord.for('D E A')?.getName()).toEqual('Dsus2'); 150 | expect(Chord.for('Eb F Bb')?.getName()).toEqual('Ebsus2'); 151 | expect(Chord.for('E F# B')?.getName()).toEqual('Esus2'); 152 | expect(Chord.for('F G C5')?.getName()).toEqual('Fsus2'); 153 | expect(Chord.for('F# G# C#5')?.getName()).toEqual('F#sus2'); 154 | expect(Chord.for('Gb Ab Db5')?.getName()).toEqual('Gbsus2'); 155 | expect(Chord.for('G A D5')?.getName()).toEqual('Gsus2'); 156 | expect(Chord.for('Ab Bb Eb5')?.getName()).toEqual('Absus2'); 157 | expect(Chord.for('A B E5')?.getName()).toEqual('Asus2'); 158 | expect(Chord.for('Bb3 C F')?.getName()).toEqual('Bbsus2'); 159 | expect(Chord.for('B3 C# F#')?.getName()).toEqual('Bsus2'); 160 | }); 161 | }); 162 | 163 | describe('Augmented Chords', () => { 164 | it('returns the name of the augmented chord', () => { 165 | expect(Chord.for('C E G#')?.getName()).toEqual('Caug'); 166 | expect(Chord.for('C# E# G𝄪')?.getName()).toEqual('C#aug'); 167 | expect(Chord.for('Db F A')?.getName()).toEqual('Dbaug'); 168 | expect(Chord.for('D F# A#')?.getName()).toEqual('Daug'); 169 | expect(Chord.for('Eb G B')?.getName()).toEqual('Ebaug'); 170 | expect(Chord.for('E G# B#')?.getName()).toEqual('Eaug'); 171 | expect(Chord.for('F A C#5')?.getName()).toEqual('Faug'); 172 | expect(Chord.for('F# A# C𝄪5')?.getName()).toEqual('F#aug'); 173 | expect(Chord.for('Gb Bb D5')?.getName()).toEqual('Gbaug'); 174 | expect(Chord.for('G B D#5')?.getName()).toEqual('Gaug'); 175 | expect(Chord.for('G# B# D𝄪5')?.getName()).toEqual('G#aug'); 176 | expect(Chord.for('Ab3 C E')?.getName()).toEqual('Abaug'); 177 | expect(Chord.for('A3 C# E#')?.getName()).toEqual('Aaug'); 178 | expect(Chord.for('Bb3 D F#')?.getName()).toEqual('Bbaug'); 179 | expect(Chord.for('B3 D# F𝄪')?.getName()).toEqual('Baug'); 180 | }); 181 | }); 182 | 183 | describe('Diminished Chords', () => { 184 | it('returns the name of the diminished chord', () => { 185 | expect(Chord.for('C Eb Gb')?.getName()).toEqual('Cdim'); 186 | expect(Chord.for('C# E G')?.getName()).toEqual('C#dim'); 187 | expect(Chord.for('Db Fb A𝄫')?.getName()).toEqual('Dbdim'); 188 | expect(Chord.for('D F Ab')?.getName()).toEqual('Ddim'); 189 | expect(Chord.for('D# F# A')?.getName()).toEqual('D#dim'); 190 | expect(Chord.for('Eb Gb B𝄫')?.getName()).toEqual('Ebdim'); 191 | expect(Chord.for('E G Bb')?.getName()).toEqual('Edim'); 192 | expect(Chord.for('E# G# B')?.getName()).toEqual('E#dim'); 193 | expect(Chord.for('F Ab Cb5')?.getName()).toEqual('Fdim'); 194 | expect(Chord.for('F# A C5')?.getName()).toEqual('F#dim'); 195 | expect(Chord.for('Gb B𝄫 D𝄫5')?.getName()).toEqual('Gbdim'); 196 | expect(Chord.for('G Bb Db5')?.getName()).toEqual('Gdim'); 197 | expect(Chord.for('G# B D5')?.getName()).toEqual('G#dim'); 198 | expect(Chord.for('Ab3 Cb E𝄫')?.getName()).toEqual('Abdim'); 199 | expect(Chord.for('A3 C Eb')?.getName()).toEqual('Adim'); 200 | expect(Chord.for('A#3 C# E')?.getName()).toEqual('A#dim'); 201 | expect(Chord.for('Bb3 Db Fb')?.getName()).toEqual('Bbdim'); 202 | expect(Chord.for('B3 D F')?.getName()).toEqual('Bdim'); 203 | expect(Chord.for('B#3 D# F#')?.getName()).toEqual('B#dim'); 204 | }); 205 | }); 206 | 207 | describe('Sixth Chords', () => { 208 | it('returns the name of the sixth chord', () => { 209 | expect(Chord.for('C E G A')?.getName()).toEqual('C6'); 210 | expect(Chord.for('C# E# G# A#')?.getName()).toEqual('C#6'); 211 | expect(Chord.for('Db F Ab Bb')?.getName()).toEqual('Db6'); 212 | expect(Chord.for('D F# A B')?.getName()).toEqual('D6'); 213 | expect(Chord.for('Eb G Bb C5')?.getName()).toEqual('Eb6'); 214 | expect(Chord.for('E G# B C#5')?.getName()).toEqual('E6'); 215 | expect(Chord.for('F A C5 D5')?.getName()).toEqual('F6'); 216 | expect(Chord.for('F# A# C#5 D#5')?.getName()).toEqual('F#6'); 217 | expect(Chord.for('Gb Bb Db5 Eb5')?.getName()).toEqual('Gb6'); 218 | expect(Chord.for('G B D5 E5')?.getName()).toEqual('G6'); 219 | expect(Chord.for('G# B# D#5 E#5')?.getName()).toEqual('G#6'); 220 | expect(Chord.for('Ab3 C Eb F')?.getName()).toEqual('Ab6'); 221 | expect(Chord.for('A3 C# E F#')?.getName()).toEqual('A6'); 222 | expect(Chord.for('Bb3 D F G')?.getName()).toEqual('Bb6'); 223 | expect(Chord.for('B3 D# F# G#')?.getName()).toEqual('B6'); 224 | }); 225 | }); 226 | 227 | describe('Minor Sixth Chords', () => { 228 | it('returns the name of the minor sixth chord', () => { 229 | expect(Chord.for('C Eb G A')?.getName()).toEqual('Cm6'); 230 | expect(Chord.for('C# E G# A#')?.getName()).toEqual('C#m6'); 231 | expect(Chord.for('Db Fb Ab Bb')?.getName()).toEqual('Dbm6'); 232 | expect(Chord.for('D F A B')?.getName()).toEqual('Dm6'); 233 | expect(Chord.for('Eb Gb Bb C5')?.getName()).toEqual('Ebm6'); 234 | expect(Chord.for('E G B C#5')?.getName()).toEqual('Em6'); 235 | expect(Chord.for('F Ab C5 D5')?.getName()).toEqual('Fm6'); 236 | expect(Chord.for('F# A C#5 D#5')?.getName()).toEqual('F#m6'); 237 | expect(Chord.for('G Bb D5 E5')?.getName()).toEqual('Gm6'); 238 | expect(Chord.for('G# B D#5 E#5')?.getName()).toEqual('G#m6'); 239 | expect(Chord.for('Ab3 Cb Eb F')?.getName()).toEqual('Abm6'); 240 | expect(Chord.for('A3 C E F#')?.getName()).toEqual('Am6'); 241 | expect(Chord.for('Bb3 Db F G')?.getName()).toEqual('Bbm6'); 242 | expect(Chord.for('B3 D F# G#')?.getName()).toEqual('Bm6'); 243 | }); 244 | }); 245 | 246 | describe('Dominant Seventh Chords', () => { 247 | it('returns the name of the dominant seventh', () => { 248 | expect(Chord.for('C E G Bb')?.getName()).toEqual('C7'); 249 | expect(Chord.for('Db F Ab Cb5')?.getName()).toEqual('Db7'); 250 | expect(Chord.for('D F# A C5')?.getName()).toEqual('D7'); 251 | expect(Chord.for('Eb G Bb Db5')?.getName()).toEqual('Eb7'); 252 | expect(Chord.for('E G# B D5')?.getName()).toEqual('E7'); 253 | expect(Chord.for('F A C5 Eb5')?.getName()).toEqual('F7'); 254 | expect(Chord.for('Gb Bb Db5 Fb5')?.getName()).toEqual('Gb7'); 255 | expect(Chord.for('G B D5 F5')?.getName()).toEqual('G7'); 256 | expect(Chord.for('Ab3 C Eb Gb')?.getName()).toEqual('Ab7'); 257 | expect(Chord.for('A3 C# E G')?.getName()).toEqual('A7'); 258 | expect(Chord.for('Bb3 D F Ab')?.getName()).toEqual('Bb7'); 259 | expect(Chord.for('B3 D# F# A')?.getName()).toEqual('B7'); 260 | }); 261 | }); 262 | 263 | describe('Augmented Seventh Chords', () => { 264 | it('returns the name of the augmented dominant seventh', () => { 265 | expect(Chord.for('C E G# Bb')?.getName()).toEqual('C+7'); 266 | expect(Chord.for('Db F A Cb5')?.getName()).toEqual('Db+7'); 267 | expect(Chord.for('D F# A# C5')?.getName()).toEqual('D+7'); 268 | expect(Chord.for('Eb G B Db5')?.getName()).toEqual('Eb+7'); 269 | expect(Chord.for('E G# B# D5')?.getName()).toEqual('E+7'); 270 | expect(Chord.for('F A C#5 Eb5')?.getName()).toEqual('F+7'); 271 | expect(Chord.for('Gb Bb D5 Fb5')?.getName()).toEqual('Gb+7'); 272 | expect(Chord.for('G B D#5 F5')?.getName()).toEqual('G+7'); 273 | expect(Chord.for('Ab3 C E Gb')?.getName()).toEqual('Ab+7'); 274 | expect(Chord.for('A3 C# E# G')?.getName()).toEqual('A+7'); 275 | expect(Chord.for('Bb3 D F# Ab')?.getName()).toEqual('Bb+7'); 276 | expect(Chord.for('B3 D# F𝄪 A')?.getName()).toEqual('B+7'); 277 | }); 278 | }); 279 | 280 | describe('Major Seventh Chords', () => { 281 | it('returns the name of the major seventh', () => { 282 | expect(Chord.for('C E G B')?.getName()).toEqual('Cmaj7'); 283 | expect(Chord.for('C# E# G# B#')?.getName()).toEqual('C#maj7'); 284 | expect(Chord.for('Db F Ab C5')?.getName()).toEqual('Dbmaj7'); 285 | expect(Chord.for('D F# A C#5')?.getName()).toEqual('Dmaj7'); 286 | expect(Chord.for('D# F𝄪 A# C𝄪5')?.getName()).toEqual('D#maj7'); 287 | expect(Chord.for('Eb G Bb D5')?.getName()).toEqual('Ebmaj7'); 288 | expect(Chord.for('E G# B D#5')?.getName()).toEqual('Emaj7'); 289 | expect(Chord.for('F A C5 E5')?.getName()).toEqual('Fmaj7'); 290 | expect(Chord.for('F# A# C#5 E#5')?.getName()).toEqual('F#maj7'); 291 | expect(Chord.for('Gb Bb Db5 F5')?.getName()).toEqual('Gbmaj7'); 292 | expect(Chord.for('G B D5 F#5')?.getName()).toEqual('Gmaj7'); 293 | expect(Chord.for('G# B# D#5 F𝄪5')?.getName()).toEqual('G#maj7'); 294 | expect(Chord.for('Ab3 C Eb G')?.getName()).toEqual('Abmaj7'); 295 | expect(Chord.for('A3 C# E G#')?.getName()).toEqual('Amaj7'); 296 | expect(Chord.for('A#3 C𝄪 E# G𝄪')?.getName()).toEqual('A#maj7'); 297 | expect(Chord.for('Bb3 D F A')?.getName()).toEqual('Bbmaj7'); 298 | expect(Chord.for('B3 D# F# A#')?.getName()).toEqual('Bmaj7'); 299 | }); 300 | }); 301 | 302 | describe('Augmented Major Seventh Chords', () => { 303 | it('returns the name of the augmented major seventh', () => { 304 | expect(Chord.for('C E G# B')?.getName()).toEqual('Cmaj+7'); 305 | expect(Chord.for('C# E# G𝄪 B#')?.getName()).toEqual('C#maj+7'); 306 | expect(Chord.for('Db F A C5')?.getName()).toEqual('Dbmaj+7'); 307 | expect(Chord.for('D F# A# C#5')?.getName()).toEqual('Dmaj+7'); 308 | expect(Chord.for('D# F𝄪 A𝄪 C𝄪5')?.getName()).toEqual('D#maj+7'); 309 | expect(Chord.for('Eb G B D5')?.getName()).toEqual('Ebmaj+7'); 310 | expect(Chord.for('E G# B# D#5')?.getName()).toEqual('Emaj+7'); 311 | expect(Chord.for('F A C#5 E5')?.getName()).toEqual('Fmaj+7'); 312 | expect(Chord.for('F# A# C𝄪5 E#5')?.getName()).toEqual('F#maj+7'); 313 | expect(Chord.for('Gb Bb D5 F5')?.getName()).toEqual('Gbmaj+7'); 314 | expect(Chord.for('G B D#5 F#5')?.getName()).toEqual('Gmaj+7'); 315 | expect(Chord.for('G# B# D𝄪5 F𝄪5')?.getName()).toEqual('G#maj+7'); 316 | expect(Chord.for('Ab3 C E G')?.getName()).toEqual('Abmaj+7'); 317 | expect(Chord.for('A3 C# E# G#')?.getName()).toEqual('Amaj+7'); 318 | expect(Chord.for('A#3 C𝄪 E𝄪 G𝄪')?.getName()).toEqual('A#maj+7'); 319 | expect(Chord.for('Bb3 D F# A')?.getName()).toEqual('Bbmaj+7'); 320 | expect(Chord.for('B3 D# F𝄪 A#')?.getName()).toEqual('Bmaj+7'); 321 | }); 322 | }); 323 | 324 | describe('Minor Seventh Chords', () => { 325 | it('returns the name of the minor seventh', () => { 326 | expect(Chord.for('C Eb G Bb')?.getName()).toEqual('Cm7'); 327 | expect(Chord.for('C# E G# B')?.getName()).toEqual('C#m7'); 328 | expect(Chord.for('Db Fb Ab Cb5')?.getName()).toEqual('Dbm7'); 329 | expect(Chord.for('D F A C5')?.getName()).toEqual('Dm7'); 330 | expect(Chord.for('D# F# A# C#5')?.getName()).toEqual('D#m7'); 331 | expect(Chord.for('Eb Gb Bb Db5')?.getName()).toEqual('Ebm7'); 332 | expect(Chord.for('E G B D5')?.getName()).toEqual('Em7'); 333 | expect(Chord.for('F Ab C5 Eb5')?.getName()).toEqual('Fm7'); 334 | expect(Chord.for('F# A C#5 E5')?.getName()).toEqual('F#m7'); 335 | expect(Chord.for('Gb B𝄫 Db5 Fb5')?.getName()).toEqual('Gbm7'); 336 | expect(Chord.for('G Bb D5 F5')?.getName()).toEqual('Gm7'); 337 | expect(Chord.for('G# B D#5 F#5')?.getName()).toEqual('G#m7'); 338 | expect(Chord.for('Ab3 Cb Eb Gb')?.getName()).toEqual('Abm7'); 339 | expect(Chord.for('A3 C E G')?.getName()).toEqual('Am7'); 340 | expect(Chord.for('A#3 C# E# G#')?.getName()).toEqual('A#m7'); 341 | expect(Chord.for('Bb3 Db F Ab')?.getName()).toEqual('Bbm7'); 342 | expect(Chord.for('B3 D F# A')?.getName()).toEqual('Bm7'); 343 | }); 344 | }); 345 | 346 | describe('Diminished Seventh Chords', () => { 347 | it('returns the name of the diminished seventh chord', () => { 348 | expect(Chord.for('C Eb Gb B𝄫')?.getName()).toEqual('Cdim7'); 349 | expect(Chord.for('C# E G Bb')?.getName()).toEqual('C#dim7'); 350 | expect(Chord.for('Db Fb A𝄫 C𝄫5')?.getName()).toEqual('Dbdim7'); 351 | expect(Chord.for('D F Ab Cb5')?.getName()).toEqual('Ddim7'); 352 | expect(Chord.for('D# F# A C5')?.getName()).toEqual('D#dim7'); 353 | expect(Chord.for('Eb Gb B𝄫 D𝄫5')?.getName()).toEqual('Ebdim7'); 354 | expect(Chord.for('E G Bb Db5')?.getName()).toEqual('Edim7'); 355 | expect(Chord.for('E# G# B D5')?.getName()).toEqual('E#dim7'); 356 | expect(Chord.for('F Ab Cb5 E𝄫5')?.getName()).toEqual('Fdim7'); 357 | expect(Chord.for('F# A C5 Eb5')?.getName()).toEqual('F#dim7'); 358 | expect(Chord.for('Gb B𝄫 D𝄫5 F𝄫5')?.getName()).toEqual('Gbdim7'); 359 | expect(Chord.for('G Bb Db5 Fb5')?.getName()).toEqual('Gdim7'); 360 | expect(Chord.for('G# B D5 F5')?.getName()).toEqual('G#dim7'); 361 | expect(Chord.for('Ab3 Cb E𝄫 G𝄫')?.getName()).toEqual('Abdim7'); 362 | expect(Chord.for('A3 C Eb Gb')?.getName()).toEqual('Adim7'); 363 | expect(Chord.for('A#3 C# E G')?.getName()).toEqual('A#dim7'); 364 | expect(Chord.for('Bb3 Db Fb A𝄫')?.getName()).toEqual('Bbdim7'); 365 | expect(Chord.for('B3 D F Ab')?.getName()).toEqual('Bdim7'); 366 | expect(Chord.for('B#3 D# F# A')?.getName()).toEqual('B#dim7'); 367 | }); 368 | }); 369 | 370 | describe('Half-Diminished Seventh Chords', () => { 371 | it('returns the name of the half-diminished seventh chord', () => { 372 | expect(Chord.for('C Eb Gb Bb')?.getName()).toEqual('Cø7'); 373 | expect(Chord.for('C# E G B')?.getName()).toEqual('C#ø7'); 374 | expect(Chord.for('Db Fb A𝄫 Cb5')?.getName()).toEqual('Dbø7'); 375 | expect(Chord.for('D F Ab C5')?.getName()).toEqual('Dø7'); 376 | expect(Chord.for('D# F# A C#5')?.getName()).toEqual('D#ø7'); 377 | expect(Chord.for('Eb Gb B𝄫 Db5')?.getName()).toEqual('Ebø7'); 378 | expect(Chord.for('E G Bb D5')?.getName()).toEqual('Eø7'); 379 | expect(Chord.for('E# G# B D#5')?.getName()).toEqual('E#ø7'); 380 | expect(Chord.for('F Ab Cb5 Eb5')?.getName()).toEqual('Fø7'); 381 | expect(Chord.for('F# A C5 E5')?.getName()).toEqual('F#ø7'); 382 | expect(Chord.for('Gb B𝄫 D𝄫5 Fb5')?.getName()).toEqual('Gbø7'); 383 | expect(Chord.for('G Bb Db5 F5')?.getName()).toEqual('Gø7'); 384 | expect(Chord.for('G# B D5 F#5')?.getName()).toEqual('G#ø7'); 385 | expect(Chord.for('Ab3 Cb E𝄫 Gb')?.getName()).toEqual('Abø7'); 386 | expect(Chord.for('A3 C Eb G')?.getName()).toEqual('Aø7'); 387 | expect(Chord.for('A#3 C# E G#')?.getName()).toEqual('A#ø7'); 388 | expect(Chord.for('Bb3 Db Fb Ab')?.getName()).toEqual('Bbø7'); 389 | expect(Chord.for('B3 D F A')?.getName()).toEqual('Bø7'); 390 | expect(Chord.for('B#3 D# F# A#')?.getName()).toEqual('B#ø7'); 391 | }); 392 | }); 393 | 394 | describe('Minor Major Seventh Chords', () => { 395 | it('returns the name of the minor major seventh', () => { 396 | expect(Chord.for('C Eb G B')?.getName()).toEqual('Cm7+'); 397 | expect(Chord.for('C# E G# B#')?.getName()).toEqual('C#m7+'); 398 | expect(Chord.for('Db Fb Ab C5')?.getName()).toEqual('Dbm7+'); 399 | expect(Chord.for('D F A C#5')?.getName()).toEqual('Dm7+'); 400 | expect(Chord.for('D# F# A# C𝄪5')?.getName()).toEqual('D#m7+'); 401 | expect(Chord.for('Eb Gb Bb D5')?.getName()).toEqual('Ebm7+'); 402 | expect(Chord.for('E G B D#5')?.getName()).toEqual('Em7+'); 403 | expect(Chord.for('F Ab C5 E5')?.getName()).toEqual('Fm7+'); 404 | expect(Chord.for('F# A C#5 E#5')?.getName()).toEqual('F#m7+'); 405 | expect(Chord.for('Gb B𝄫 Db5 F5')?.getName()).toEqual('Gbm7+'); 406 | expect(Chord.for('G Bb D5 F#5')?.getName()).toEqual('Gm7+'); 407 | expect(Chord.for('G# B D#5 F𝄪5')?.getName()).toEqual('G#m7+'); 408 | expect(Chord.for('Ab3 Cb Eb G')?.getName()).toEqual('Abm7+'); 409 | expect(Chord.for('A3 C E G#')?.getName()).toEqual('Am7+'); 410 | expect(Chord.for('A#3 C# E# G𝄪')?.getName()).toEqual('A#m7+'); 411 | expect(Chord.for('Bb3 Db F A')?.getName()).toEqual('Bbm7+'); 412 | expect(Chord.for('B3 D F# A#')?.getName()).toEqual('Bm7+'); 413 | }); 414 | }); 415 | -------------------------------------------------------------------------------- /src/__tests__/interval.test.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | 3 | describe('#getSemitones', () => { 4 | it('returns the number of half steps between two notes', () => { 5 | expect(Interval.between('C', 'C').getSemitones()).toEqual(0); 6 | expect(Interval.between('C', 'C#').getSemitones()).toEqual(1); 7 | expect(Interval.between('C', 'Db').getSemitones()).toEqual(1); 8 | expect(Interval.between('C', 'D').getSemitones()).toEqual(2); 9 | expect(Interval.between('C', 'D#').getSemitones()).toEqual(3); 10 | expect(Interval.between('C', 'Eb').getSemitones()).toEqual(3); 11 | expect(Interval.between('C', 'E').getSemitones()).toEqual(4); 12 | expect(Interval.between('C', 'Fb').getSemitones()).toEqual(4); 13 | expect(Interval.between('C', 'E#').getSemitones()).toEqual(5); 14 | expect(Interval.between('C', 'F').getSemitones()).toEqual(5); 15 | expect(Interval.between('C', 'F#').getSemitones()).toEqual(6); 16 | expect(Interval.between('C', 'Gb').getSemitones()).toEqual(6); 17 | expect(Interval.between('C', 'G').getSemitones()).toEqual(7); 18 | expect(Interval.between('C', 'G#').getSemitones()).toEqual(8); 19 | expect(Interval.between('C', 'Ab').getSemitones()).toEqual(8); 20 | expect(Interval.between('C', 'A').getSemitones()).toEqual(9); 21 | expect(Interval.between('C', 'A#').getSemitones()).toEqual(10); 22 | expect(Interval.between('C', 'Bb').getSemitones()).toEqual(10); 23 | expect(Interval.between('C', 'B').getSemitones()).toEqual(11); 24 | 25 | expect(Interval.between('D', 'D').getSemitones()).toEqual(0); 26 | expect(Interval.between('D', 'D#').getSemitones()).toEqual(1); 27 | expect(Interval.between('D', 'Eb').getSemitones()).toEqual(1); 28 | expect(Interval.between('D', 'E').getSemitones()).toEqual(2); 29 | expect(Interval.between('D', 'Fb').getSemitones()).toEqual(2); 30 | expect(Interval.between('D', 'E#').getSemitones()).toEqual(3); 31 | expect(Interval.between('D', 'F').getSemitones()).toEqual(3); 32 | expect(Interval.between('D', 'F#').getSemitones()).toEqual(4); 33 | expect(Interval.between('D', 'Gb').getSemitones()).toEqual(4); 34 | expect(Interval.between('D', 'G').getSemitones()).toEqual(5); 35 | expect(Interval.between('D', 'G#').getSemitones()).toEqual(6); 36 | expect(Interval.between('D', 'Ab').getSemitones()).toEqual(6); 37 | expect(Interval.between('D', 'A').getSemitones()).toEqual(7); 38 | expect(Interval.between('D', 'A#').getSemitones()).toEqual(8); 39 | expect(Interval.between('D', 'Bb').getSemitones()).toEqual(8); 40 | expect(Interval.between('D', 'B').getSemitones()).toEqual(9); 41 | expect(Interval.between('D', 'B#').getSemitones()).toEqual(10); 42 | expect(Interval.between('D', 'C').getSemitones()).toEqual(10); 43 | expect(Interval.between('D', 'C#').getSemitones()).toEqual(11); 44 | expect(Interval.between('D', 'Db').getSemitones()).toEqual(11); 45 | 46 | expect(Interval.between('F', 'F').getSemitones()).toEqual(0); 47 | expect(Interval.between('F', 'F#').getSemitones()).toEqual(1); 48 | expect(Interval.between('F', 'Gb').getSemitones()).toEqual(1); 49 | expect(Interval.between('F', 'G').getSemitones()).toEqual(2); 50 | expect(Interval.between('F', 'G#').getSemitones()).toEqual(3); 51 | expect(Interval.between('F', 'Ab').getSemitones()).toEqual(3); 52 | expect(Interval.between('F', 'A').getSemitones()).toEqual(4); 53 | expect(Interval.between('F', 'A#').getSemitones()).toEqual(5); 54 | expect(Interval.between('F', 'Bb').getSemitones()).toEqual(5); 55 | expect(Interval.between('F', 'B').getSemitones()).toEqual(6); 56 | expect(Interval.between('F', 'B#').getSemitones()).toEqual(7); 57 | expect(Interval.between('F', 'C').getSemitones()).toEqual(7); 58 | expect(Interval.between('F', 'C#').getSemitones()).toEqual(8); 59 | expect(Interval.between('F', 'Db').getSemitones()).toEqual(8); 60 | expect(Interval.between('F', 'D').getSemitones()).toEqual(9); 61 | expect(Interval.between('F', 'D#').getSemitones()).toEqual(10); 62 | expect(Interval.between('F', 'Eb').getSemitones()).toEqual(10); 63 | expect(Interval.between('F', 'E').getSemitones()).toEqual(11); 64 | expect(Interval.between('F', 'Fb').getSemitones()).toEqual(11); 65 | }); 66 | }); 67 | 68 | describe('#isWithinSemitones', () => { 69 | describe('when the number of semitones is equal or less than', () => { 70 | it('returns true', () => { 71 | expect(Interval.between("C", "E").isWithinSemitones(5)).toBeTruthy(); 72 | expect(Interval.between("C", "F").isWithinSemitones(5)).toBeTruthy(); 73 | }); 74 | }); 75 | 76 | describe('when the number of semitones is greater than', () => { 77 | it('returns false', () => { 78 | expect(Interval.between("C", "F#").isWithinSemitones(5)).toBeFalsy(); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('#isNone', () => { 84 | describe('when the notes are the same', () => { 85 | it('returns true', () => { 86 | expect(Interval.between('C', 'C').isNone()).toBeTruthy(); 87 | expect(Interval.between('A', 'A').isNone()).toBeTruthy(); 88 | }); 89 | }); 90 | 91 | describe('when the notes are not the same', () => { 92 | it('returns false', () => { 93 | expect(Interval.between('C', 'D').isNone()).toBeFalsy(); 94 | }); 95 | }); 96 | }); 97 | 98 | describe('#isMajor2nd', () => { 99 | describe('and there are 2 semitones between the notes', () => { 100 | it('returns true', () => { 101 | expect(Interval.between('C', 'D').isMajor2nd()).toBeTruthy(); 102 | expect(Interval.between('C#', 'D#').isMajor2nd()).toBeTruthy(); 103 | expect(Interval.between('D', 'E').isMajor2nd()).toBeTruthy(); 104 | expect(Interval.between('Eb', 'F').isMajor2nd()).toBeTruthy(); 105 | expect(Interval.between('E', 'F#').isMajor2nd()).toBeTruthy(); 106 | expect(Interval.between('F', 'G').isMajor2nd()).toBeTruthy(); 107 | expect(Interval.between('F#', 'G#').isMajor2nd()).toBeTruthy(); 108 | expect(Interval.between('G', 'A').isMajor2nd()).toBeTruthy(); 109 | expect(Interval.between('G#', 'A#').isMajor2nd()).toBeTruthy(); 110 | expect(Interval.between('Ab', 'Bb').isMajor2nd()).toBeTruthy(); 111 | expect(Interval.between('A', 'B').isMajor2nd()).toBeTruthy(); 112 | expect(Interval.between('Bb', 'C').isMajor2nd()).toBeTruthy(); 113 | }); 114 | }); 115 | 116 | describe('and there are not 2 semitones between the notes', () => { 117 | it('returns false', () => { 118 | expect(Interval.between('C', 'F').isMajor2nd()).toBeFalsy(); 119 | }); 120 | }); 121 | }); 122 | 123 | describe('#isMajor3rd', () => { 124 | describe('and there are 4 semitones between the notes', () => { 125 | it('returns true', () => { 126 | expect(Interval.between('C', 'E').isMajor3rd()).toBeTruthy(); 127 | expect(Interval.between('Db', 'F').isMajor3rd()).toBeTruthy(); 128 | expect(Interval.between('D', 'F#').isMajor3rd()).toBeTruthy(); 129 | expect(Interval.between('Eb', 'G').isMajor3rd()).toBeTruthy(); 130 | expect(Interval.between('E', 'G#').isMajor3rd()).toBeTruthy(); 131 | expect(Interval.between('F', 'A').isMajor3rd()).toBeTruthy(); 132 | expect(Interval.between('F#', 'A#').isMajor3rd()).toBeTruthy(); 133 | expect(Interval.between('G', 'B').isMajor3rd()).toBeTruthy(); 134 | expect(Interval.between('Ab', 'C').isMajor3rd()).toBeTruthy(); 135 | expect(Interval.between('A', 'Db').isMajor3rd()).toBeTruthy(); 136 | expect(Interval.between('Bb', 'D').isMajor3rd()).toBeTruthy(); 137 | }); 138 | }); 139 | 140 | describe('and there are not 4 semitones between the notes', () => { 141 | it('returns false', () => { 142 | expect(Interval.between('C', 'D').isMajor3rd()).toBeFalsy(); 143 | }); 144 | }); 145 | }); 146 | 147 | describe('#isMajor6th', () => { 148 | describe('and there are 9 semitones between the notes', () => { 149 | it('returns true', () => { 150 | expect(Interval.between('C', 'A').isMajor6th()).toBeTruthy(); 151 | expect(Interval.between('Db', 'Bb').isMajor6th()).toBeTruthy(); 152 | expect(Interval.between('D', 'B').isMajor6th()).toBeTruthy(); 153 | expect(Interval.between('Eb', 'C').isMajor6th()).toBeTruthy(); 154 | expect(Interval.between('E', 'C#').isMajor6th()).toBeTruthy(); 155 | expect(Interval.between('F', 'D').isMajor6th()).toBeTruthy(); 156 | expect(Interval.between('F#', 'D#').isMajor6th()).toBeTruthy(); 157 | expect(Interval.between('G', 'E').isMajor6th()).toBeTruthy(); 158 | expect(Interval.between('Ab', 'F').isMajor6th()).toBeTruthy(); 159 | expect(Interval.between('A', 'F#').isMajor6th()).toBeTruthy(); 160 | expect(Interval.between('Bb', 'G').isMajor6th()).toBeTruthy(); 161 | }); 162 | }); 163 | 164 | describe('and there are not 9 semitones between the notes', () => { 165 | it('returns false', () => { 166 | expect(Interval.between('C', 'F').isMajor6th()).toBeFalsy(); 167 | }); 168 | }); 169 | }); 170 | 171 | describe('#isMajor7th', () => { 172 | describe('and there are 11 semitones between the notes', () => { 173 | it('returns true', () => { 174 | expect(Interval.between('C', 'B').isMajor7th()).toBeTruthy(); 175 | expect(Interval.between('Db', 'C').isMajor7th()).toBeTruthy(); 176 | expect(Interval.between('D', 'C#').isMajor7th()).toBeTruthy(); 177 | expect(Interval.between('Eb', 'D').isMajor7th()).toBeTruthy(); 178 | expect(Interval.between('E', 'Eb').isMajor7th()).toBeTruthy(); 179 | expect(Interval.between('F', 'E').isMajor7th()).toBeTruthy(); 180 | expect(Interval.between('F#', 'F').isMajor7th()).toBeTruthy(); 181 | expect(Interval.between('G', 'F#').isMajor7th()).toBeTruthy(); 182 | expect(Interval.between('Ab', 'G').isMajor7th()).toBeTruthy(); 183 | expect(Interval.between('A', 'G#').isMajor7th()).toBeTruthy(); 184 | expect(Interval.between('Bb', 'A').isMajor7th()).toBeTruthy(); 185 | }); 186 | }); 187 | 188 | describe('and there are not 11 semitones between the notes', () => { 189 | it('returns false', () => { 190 | expect(Interval.between('C', 'A').isMajor7th()).toBeFalsy(); 191 | }); 192 | }); 193 | }); 194 | 195 | describe('#isMinor2nd', () => { 196 | describe('and there are 1 semitone between the notes', () => { 197 | it('returns true', () => { 198 | expect(Interval.between('C', 'Db').isMinor2nd()).toBeTruthy(); 199 | expect(Interval.between('C#', 'D').isMinor2nd()).toBeTruthy(); 200 | expect(Interval.between('D', 'Eb').isMinor2nd()).toBeTruthy(); 201 | expect(Interval.between('E', 'F').isMinor2nd()).toBeTruthy(); 202 | expect(Interval.between('F', 'F#').isMinor2nd()).toBeTruthy(); 203 | expect(Interval.between('F#', 'G').isMinor2nd()).toBeTruthy(); 204 | expect(Interval.between('G', 'G#').isMinor2nd()).toBeTruthy(); 205 | expect(Interval.between('G#', 'A').isMinor2nd()).toBeTruthy(); 206 | expect(Interval.between('A', 'Bb').isMinor2nd()).toBeTruthy(); 207 | expect(Interval.between('B', 'C').isMinor2nd()).toBeTruthy(); 208 | }); 209 | }); 210 | 211 | describe('and there are not 1 semitone between the notes', () => { 212 | it('returns false', () => { 213 | expect(Interval.between('C', 'D').isMinor2nd()).toBeFalsy(); 214 | }); 215 | }); 216 | }); 217 | 218 | describe('#isMinor3rd', () => { 219 | describe('and there are 3 semitones between the notes', () => { 220 | it('returns true', () => { 221 | expect(Interval.between('C', 'Eb').isMinor3rd()).toBeTruthy(); 222 | expect(Interval.between('C#', 'E').isMinor3rd()).toBeTruthy(); 223 | expect(Interval.between('Db', 'Fb').isMinor3rd()).toBeTruthy(); 224 | expect(Interval.between('D', 'F').isMinor3rd()).toBeTruthy(); 225 | expect(Interval.between('D#', 'F#').isMinor3rd()).toBeTruthy(); 226 | expect(Interval.between('Eb', 'Gb').isMinor3rd()).toBeTruthy(); 227 | expect(Interval.between('E', 'G').isMinor3rd()).toBeTruthy(); 228 | expect(Interval.between('F', 'Ab').isMinor3rd()).toBeTruthy(); 229 | expect(Interval.between('F#', 'A').isMinor3rd()).toBeTruthy(); 230 | expect(Interval.between('G', 'Bb').isMinor3rd()).toBeTruthy(); 231 | expect(Interval.between('G#', 'B').isMinor3rd()).toBeTruthy(); 232 | expect(Interval.between('Ab', 'Cb').isMinor3rd()).toBeTruthy(); 233 | expect(Interval.between('A', 'C').isMinor3rd()).toBeTruthy(); 234 | expect(Interval.between('Bb', 'Db').isMinor3rd()).toBeTruthy(); 235 | expect(Interval.between('B', 'D').isMinor3rd()).toBeTruthy(); 236 | }); 237 | }); 238 | 239 | describe('and there are not 3 semitones between the notes', () => { 240 | it('returns false', () => { 241 | expect(Interval.between('C', 'D').isMinor3rd()).toBeFalsy(); 242 | }); 243 | }); 244 | }); 245 | 246 | describe('#isMinor6th', () => { 247 | describe('and there are 8 semitones between the notes', () => { 248 | it('returns true', () => { 249 | expect(Interval.between('C', 'Ab').isMinor6th()).toBeTruthy(); 250 | expect(Interval.between('Db', 'A').isMinor6th()).toBeTruthy(); 251 | expect(Interval.between('D', 'Bb').isMinor6th()).toBeTruthy(); 252 | expect(Interval.between('Eb', 'Cb').isMinor6th()).toBeTruthy(); 253 | expect(Interval.between('E', 'C').isMinor6th()).toBeTruthy(); 254 | expect(Interval.between('F', 'Db').isMinor6th()).toBeTruthy(); 255 | expect(Interval.between('F#', 'D').isMinor6th()).toBeTruthy(); 256 | expect(Interval.between('G', 'Eb').isMinor6th()).toBeTruthy(); 257 | expect(Interval.between('Ab', 'Fb').isMinor6th()).toBeTruthy(); 258 | expect(Interval.between('A', 'F').isMinor6th()).toBeTruthy(); 259 | expect(Interval.between('Bb', 'Gb').isMinor6th()).toBeTruthy(); 260 | }); 261 | }); 262 | 263 | describe('and there are not 8 semitones between the notes', () => { 264 | it('returns false', () => { 265 | expect(Interval.between('C', 'F').isMinor6th()).toBeFalsy(); 266 | }); 267 | }); 268 | }); 269 | 270 | describe('#isMinor7th', () => { 271 | describe('and there are 10 semitones between the notes', () => { 272 | it('returns true', () => { 273 | expect(Interval.between('C', 'Bb').isMinor7th()).toBeTruthy(); 274 | expect(Interval.between('Db', 'Cb').isMinor7th()).toBeTruthy(); 275 | expect(Interval.between('D', 'C').isMinor7th()).toBeTruthy(); 276 | expect(Interval.between('Eb', 'Db').isMinor7th()).toBeTruthy(); 277 | expect(Interval.between('E', 'D').isMinor7th()).toBeTruthy(); 278 | expect(Interval.between('F', 'Eb').isMinor7th()).toBeTruthy(); 279 | expect(Interval.between('F#', 'E').isMinor7th()).toBeTruthy(); 280 | expect(Interval.between('G', 'F').isMinor7th()).toBeTruthy(); 281 | expect(Interval.between('Ab', 'Gb').isMinor7th()).toBeTruthy(); 282 | expect(Interval.between('A', 'G').isMinor7th()).toBeTruthy(); 283 | expect(Interval.between('Bb', 'Ab').isMinor7th()).toBeTruthy(); 284 | }); 285 | }); 286 | 287 | describe('and there are not 10 semitones between the notes', () => { 288 | it('returns false', () => { 289 | expect(Interval.between('C', 'A').isMinor7th()).toBeFalsy(); 290 | }); 291 | }); 292 | }); 293 | 294 | describe('#isPerfect4th', () => { 295 | describe('and there are 5 semitones between the notes', () => { 296 | it('returns true', () => { 297 | expect(Interval.between('C', 'F').isPerfect4th()).toBeTruthy(); 298 | expect(Interval.between('C#', 'F#').isPerfect4th()).toBeTruthy(); 299 | expect(Interval.between('Db', 'Gb').isPerfect4th()).toBeTruthy(); 300 | expect(Interval.between('D', 'G').isPerfect4th()).toBeTruthy(); 301 | expect(Interval.between('Eb', 'Ab').isPerfect4th()).toBeTruthy(); 302 | expect(Interval.between('E', 'A').isPerfect4th()).toBeTruthy(); 303 | expect(Interval.between('F', 'Bb').isPerfect4th()).toBeTruthy(); 304 | expect(Interval.between('F#', 'B').isPerfect4th()).toBeTruthy(); 305 | expect(Interval.between('G', 'C').isPerfect4th()).toBeTruthy(); 306 | expect(Interval.between('G#', 'C#').isPerfect4th()).toBeTruthy(); 307 | expect(Interval.between('Ab', 'Db').isPerfect4th()).toBeTruthy(); 308 | expect(Interval.between('A', 'D').isPerfect4th()).toBeTruthy(); 309 | expect(Interval.between('Bb', 'Eb').isPerfect4th()).toBeTruthy(); 310 | expect(Interval.between('B', 'E').isPerfect4th()).toBeTruthy(); 311 | }); 312 | }); 313 | 314 | describe('and there are not 5 semitones between the notes', () => { 315 | it('returns false', () => { 316 | expect(Interval.between('C', 'E').isPerfect4th()).toBeFalsy(); 317 | }); 318 | }); 319 | }); 320 | 321 | describe('#isPerfect5th', () => { 322 | describe('and there are 7 semitones between the notes', () => { 323 | it('returns true', () => { 324 | expect(Interval.between('C', 'G').isPerfect5th()).toBeTruthy(); 325 | expect(Interval.between('Db', 'Ab').isPerfect5th()).toBeTruthy(); 326 | expect(Interval.between('D', 'A').isPerfect5th()).toBeTruthy(); 327 | expect(Interval.between('Eb', 'Bb').isPerfect5th()).toBeTruthy(); 328 | expect(Interval.between('E', 'B').isPerfect5th()).toBeTruthy(); 329 | expect(Interval.between('F', 'C').isPerfect5th()).toBeTruthy(); 330 | expect(Interval.between('F#', 'C#').isPerfect5th()).toBeTruthy(); 331 | expect(Interval.between('G', 'D').isPerfect5th()).toBeTruthy(); 332 | expect(Interval.between('Ab', 'Eb').isPerfect5th()).toBeTruthy(); 333 | expect(Interval.between('A', 'E').isPerfect5th()).toBeTruthy(); 334 | expect(Interval.between('Bb', 'F').isPerfect5th()).toBeTruthy(); 335 | expect(Interval.between('B', 'F#').isPerfect5th()).toBeTruthy(); 336 | }); 337 | }); 338 | 339 | describe('and there is not 7 semitones between the notes', () => { 340 | it('returns false', () => { 341 | expect(Interval.between('C', 'D').isPerfect5th()).toBeFalsy(); 342 | }); 343 | }); 344 | }); 345 | 346 | describe('#isAugmented4th', () => { 347 | describe('and there are 6 semitones between the notes', () => { 348 | it('returns true', () => { 349 | expect(Interval.between('C', 'F#').isAugmented4th()).toBeTruthy(); 350 | expect(Interval.between('C#', 'G').isAugmented4th()).toBeTruthy(); 351 | expect(Interval.between('Db', 'G').isAugmented4th()).toBeTruthy(); 352 | expect(Interval.between('D', 'G#').isAugmented4th()).toBeTruthy(); 353 | expect(Interval.between('Eb', 'A').isAugmented4th()).toBeTruthy(); 354 | expect(Interval.between('E', 'A#').isAugmented4th()).toBeTruthy(); 355 | expect(Interval.between('F', 'B').isAugmented4th()).toBeTruthy(); 356 | expect(Interval.between('F#', 'C').isAugmented4th()).toBeTruthy(); 357 | expect(Interval.between('G', 'C#').isAugmented4th()).toBeTruthy(); 358 | expect(Interval.between('G#', 'D').isAugmented4th()).toBeTruthy(); 359 | expect(Interval.between('Ab', 'D').isAugmented4th()).toBeTruthy(); 360 | expect(Interval.between('A', 'D#').isAugmented4th()).toBeTruthy(); 361 | expect(Interval.between('Bb', 'E').isAugmented4th()).toBeTruthy(); 362 | expect(Interval.between('B', 'F').isAugmented4th()).toBeTruthy(); 363 | }); 364 | }); 365 | 366 | describe('and there are not 6 semitones between the notes', () => { 367 | it('returns false', () => { 368 | expect(Interval.between('C', 'E').isAugmented4th()).toBeFalsy(); 369 | }); 370 | }); 371 | }); 372 | 373 | describe('#isAugmented5th', () => { 374 | describe('and there are 8 semitones between the notes', () => { 375 | it('returns true', () => { 376 | expect(Interval.between('C', 'G#').isAugmented5th()).toBeTruthy(); 377 | expect(Interval.between('Db', 'A').isAugmented5th()).toBeTruthy(); 378 | expect(Interval.between('D', 'A#').isAugmented5th()).toBeTruthy(); 379 | expect(Interval.between('Eb', 'B').isAugmented5th()).toBeTruthy(); 380 | expect(Interval.between('E', 'B#').isAugmented5th()).toBeTruthy(); 381 | expect(Interval.between('F', 'C#').isAugmented5th()).toBeTruthy(); 382 | expect(Interval.between('F#', 'D').isAugmented5th()).toBeTruthy(); 383 | expect(Interval.between('G', 'D#').isAugmented5th()).toBeTruthy(); 384 | expect(Interval.between('Ab', 'E').isAugmented5th()).toBeTruthy(); 385 | expect(Interval.between('A', 'E#').isAugmented5th()).toBeTruthy(); 386 | expect(Interval.between('Bb', 'F#').isAugmented5th()).toBeTruthy(); 387 | expect(Interval.between('B', 'G').isAugmented5th()).toBeTruthy(); 388 | }); 389 | }); 390 | 391 | describe('and there is not 8 semitones between the notes', () => { 392 | it('returns false', () => { 393 | expect(Interval.between('C', 'D').isAugmented5th()).toBeFalsy(); 394 | }); 395 | }); 396 | }); 397 | 398 | describe('#isDiminished4th', () => { 399 | describe('and there are 4 semitones between the notes', () => { 400 | it('returns true', () => { 401 | expect(Interval.between('C', 'Fb').isDiminished4th()).toBeTruthy(); 402 | expect(Interval.between('C#', 'F').isDiminished4th()).toBeTruthy(); 403 | expect(Interval.between('Db', 'F').isDiminished4th()).toBeTruthy(); 404 | expect(Interval.between('D', 'F#').isDiminished4th()).toBeTruthy(); 405 | expect(Interval.between('Eb', 'G').isDiminished4th()).toBeTruthy(); 406 | expect(Interval.between('E', 'G#').isDiminished4th()).toBeTruthy(); 407 | expect(Interval.between('F', 'A').isDiminished4th()).toBeTruthy(); 408 | expect(Interval.between('F#', 'A#').isDiminished4th()).toBeTruthy(); 409 | expect(Interval.between('G', 'Cb').isDiminished4th()).toBeTruthy(); 410 | expect(Interval.between('G#', 'C').isDiminished4th()).toBeTruthy(); 411 | expect(Interval.between('Ab', 'C').isDiminished4th()).toBeTruthy(); 412 | expect(Interval.between('A', 'Db').isDiminished4th()).toBeTruthy(); 413 | expect(Interval.between('Bb', 'D').isDiminished4th()).toBeTruthy(); 414 | expect(Interval.between('B', 'D#').isDiminished4th()).toBeTruthy(); 415 | }); 416 | }); 417 | 418 | describe('and there are not 4 semitones between the notes', () => { 419 | it('returns false', () => { 420 | expect(Interval.between('C', 'F').isDiminished4th()).toBeFalsy(); 421 | }); 422 | }); 423 | }); 424 | 425 | describe('#isDiminished5th', () => { 426 | describe('and there are 6 semitones between the notes', () => { 427 | it('returns true', () => { 428 | expect(Interval.between('C', 'Gb').isDiminished5th()).toBeTruthy(); 429 | expect(Interval.between('Db', 'G').isDiminished5th()).toBeTruthy(); 430 | expect(Interval.between('D', 'G#').isDiminished5th()).toBeTruthy(); 431 | expect(Interval.between('Eb', 'A').isDiminished5th()).toBeTruthy(); 432 | expect(Interval.between('E', 'Bb').isDiminished5th()).toBeTruthy(); 433 | expect(Interval.between('F', 'B').isDiminished5th()).toBeTruthy(); 434 | expect(Interval.between('F#', 'C').isDiminished5th()).toBeTruthy(); 435 | expect(Interval.between('G', 'C#').isDiminished5th()).toBeTruthy(); 436 | expect(Interval.between('Ab', 'D').isDiminished5th()).toBeTruthy(); 437 | expect(Interval.between('A', 'D#').isDiminished5th()).toBeTruthy(); 438 | expect(Interval.between('Bb', 'E').isDiminished5th()).toBeTruthy(); 439 | expect(Interval.between('B', 'F').isDiminished5th()).toBeTruthy(); 440 | }); 441 | }); 442 | 443 | describe('and there is not 6 semitones between the notes', () => { 444 | it('returns false', () => { 445 | expect(Interval.between('C', 'D').isDiminished5th()).toBeFalsy(); 446 | }); 447 | }); 448 | }); 449 | 450 | describe('#isDiminished7th', () => { 451 | describe('and there are 9 semitones between the notes', () => { 452 | it('returns true', () => { 453 | expect(Interval.between('C', 'A').isDiminished7th()).toBeTruthy(); 454 | expect(Interval.between('Db', 'Bb').isDiminished7th()).toBeTruthy(); 455 | expect(Interval.between('D', 'B').isDiminished7th()).toBeTruthy(); 456 | expect(Interval.between('Eb', 'C').isDiminished7th()).toBeTruthy(); 457 | expect(Interval.between('E', 'C#').isDiminished7th()).toBeTruthy(); 458 | expect(Interval.between('F', 'D').isDiminished7th()).toBeTruthy(); 459 | expect(Interval.between('F#', 'D#').isDiminished7th()).toBeTruthy(); 460 | expect(Interval.between('G', 'E').isDiminished7th()).toBeTruthy(); 461 | expect(Interval.between('Ab', 'F').isDiminished7th()).toBeTruthy(); 462 | expect(Interval.between('A', 'F#').isDiminished7th()).toBeTruthy(); 463 | expect(Interval.between('Bb', 'G').isDiminished7th()).toBeTruthy(); 464 | }); 465 | }); 466 | 467 | describe('and there is not 9 semitones between the notes', () => { 468 | it('returns false', () => { 469 | expect(Interval.between('C', 'D').isDiminished7th()).toBeFalsy(); 470 | }); 471 | }); 472 | }); 473 | 474 | describe('.major2nd', () => { 475 | it('returns 2', () => { 476 | expect(Interval.major2nd()).toEqual(2); 477 | }); 478 | }); 479 | 480 | describe('.major3rd', () => { 481 | it('returns 4', () => { 482 | expect(Interval.major3rd()).toEqual(4); 483 | }); 484 | }); 485 | 486 | describe('.perfect4th', () => { 487 | it('returns 5', () => { 488 | expect(Interval.perfect4th()).toEqual(5); 489 | }); 490 | }); 491 | 492 | describe('.perfect5th', () => { 493 | it('returns 7', () => { 494 | expect(Interval.perfect5th()).toEqual(7); 495 | }); 496 | }); 497 | 498 | describe('.major6th', () => { 499 | it('returns 9', () => { 500 | expect(Interval.major6th()).toEqual(9); 501 | }); 502 | }); 503 | 504 | describe('.major7th', () => { 505 | it('returns 11', () => { 506 | expect(Interval.major7th()).toEqual(11); 507 | }); 508 | }); 509 | 510 | describe('.octave', () => { 511 | it('returns 12', () => { 512 | expect(Interval.octave()).toEqual(12); 513 | }); 514 | }); 515 | -------------------------------------------------------------------------------- /src/__tests__/keySignature.test.ts: -------------------------------------------------------------------------------- 1 | import { Chord } from '../chord'; 2 | import { KeySignatureOfC, KeySignatureOfD, KeySignatureOfF } from '../keySignatures'; 3 | import { Note } from '../note'; 4 | 5 | describe('#normalize', () => { 6 | describe('when the note is an enharmonic in the key', () => { 7 | it('returns the enharmonic in the key', () => { 8 | expect(new KeySignatureOfF().normalize(new Note('A#')).equals(new Note('Bb'))).toBeTruthy(); 9 | expect(new KeySignatureOfF().normalize(new Note('A#5')).equals(new Note('Bb5'))).toBeTruthy(); 10 | expect(new KeySignatureOfD().normalize(new Note('A𝄪5')).equals(new Note('B5'))).toBeTruthy(); 11 | expect(new KeySignatureOfD().normalize(new Note('Db5')).equals(new Note('C#5'))).toBeTruthy(); 12 | }); 13 | }); 14 | 15 | describe('when the note is out of key', () => { 16 | it('returns the note', () => { 17 | expect(new KeySignatureOfF().normalize(new Note('F#')).equals(new Note('F#'))).toBeTruthy(); 18 | expect(new KeySignatureOfD().normalize(new Note('G#5')).equals(new Note('G#5'))).toBeTruthy(); 19 | }); 20 | }); 21 | }); 22 | 23 | describe('#isInKey', () => { 24 | describe('when the note is in the key', () => { 25 | it('returns true', () => { 26 | expect(new KeySignatureOfC().isInKey(new Note('C'))).toBeTruthy(); 27 | }); 28 | }); 29 | 30 | describe('when the note is not in the key', () => { 31 | it('returns false', () => { 32 | expect(new KeySignatureOfC().isInKey(new Note('C#'))).toBeFalsy(); 33 | }); 34 | }); 35 | }); 36 | 37 | describe('#getDegree', () => { 38 | describe('when the tonic', () => { 39 | it('returns tonic', () => { 40 | const cMajor = Chord.for('C E G'); 41 | const cMajorOctaveHigher = Chord.for('C5 E5 G5'); 42 | 43 | expect(new KeySignatureOfC().getDegree(cMajor)).toEqual("tonic"); 44 | expect(new KeySignatureOfC().getDegree(cMajorOctaveHigher)).toEqual("tonic"); 45 | }); 46 | }); 47 | 48 | describe('when the supertonic', () => { 49 | it('returns supertonic', () => { 50 | const dMinor = Chord.for('D F A'); 51 | 52 | expect(new KeySignatureOfC().getDegree(dMinor)).toEqual("supertonic"); 53 | }); 54 | }); 55 | 56 | describe('when the mediant', () => { 57 | it('returns mediant', () => { 58 | const eMinor = Chord.for('E G B'); 59 | 60 | expect(new KeySignatureOfC().getDegree(eMinor)).toEqual("mediant"); 61 | }); 62 | }); 63 | 64 | describe('when the subdominant', () => { 65 | it('returns subdominant', () => { 66 | const fMajor = Chord.for('F A C'); 67 | 68 | expect(new KeySignatureOfC().getDegree(fMajor)).toEqual("subdominant"); 69 | }); 70 | }); 71 | 72 | describe('when the dominant', () => { 73 | it('returns dominant', () => { 74 | const gMajor = Chord.for('G B D'); 75 | 76 | expect(new KeySignatureOfC().getDegree(gMajor)).toEqual("dominant"); 77 | }); 78 | }); 79 | 80 | describe('when the submediant', () => { 81 | it('returns submediant', () => { 82 | const aMinor = Chord.for('A C E'); 83 | 84 | expect(new KeySignatureOfC().getDegree(aMinor)).toEqual("submediant"); 85 | }); 86 | }); 87 | 88 | describe('when the leading-tone', () => { 89 | it('returns leading-tone', () => { 90 | const bMinor = Chord.for('B D F#'); 91 | 92 | expect(new KeySignatureOfC().getDegree(bMinor)).toEqual("leading-tone"); 93 | }); 94 | }); 95 | 96 | describe('with an invalid chord', () => { 97 | it('returns undefined', () => { 98 | const invalidChord = Chord.for('C E F'); 99 | 100 | expect(new KeySignatureOfC().getDegree(invalidChord)).toBeUndefined(); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfA.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfA } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfA, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfA(); 7 | 8 | expect(key.isInKey(new Note('A4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('B4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('C#5'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('D5'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('E5'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('F#5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('G#5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('A5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('A#'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('B#'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('C'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('D#'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('E#'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('F'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('G'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfAb.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfAb } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfAb, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfAb(); 7 | 8 | expect(key.isInKey(new Note('Ab4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('Bb4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('C5'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('Db5'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('Eb5'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('F5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('G5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('Ab5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('A'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('B'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('Cb'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('D'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('E'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('Fb'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('Gb'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfB.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfB } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfB, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfB(); 7 | 8 | expect(key.isInKey(new Note('B4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('C#5'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('D#5'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('E5'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('F#5'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('G#5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('A#5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('B5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('B#'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('C'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('D'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('E#'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('F'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('G'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('A'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfBb.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfBb } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfBb, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfBb(); 7 | 8 | expect(key.isInKey(new Note('Bb3'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('C4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('D4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('Eb4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('F4'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('G4'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('A4'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('Bb5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('B'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('Cb'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('Db'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('E'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('Fb'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('Gb'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('Ab'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfC.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfC } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfC, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfC(); 7 | 8 | expect(key.isInKey(new Note('C4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('D4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('E4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('F4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('G4'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('A4'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('B4'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('C5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('C#'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('D#'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('E#'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('F#'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('G#'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('A#'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('B#'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfD.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfD } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfD, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfD(); 7 | 8 | expect(key.isInKey(new Note('D4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('E4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('F#4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('G4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('A4'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('B4'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('C#5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('D5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('D#'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('E#'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('F'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('G#'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('A#'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('B#'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('C'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfDb.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfDb } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfDb, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfDb(); 7 | 8 | expect(key.isInKey(new Note('Db4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('Eb4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('F4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('Gb4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('Ab4'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('Bb4'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('C5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('Db5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('D'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('E'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('Fb'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('G'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('A'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('B'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('Cb'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfE.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfE } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfE, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfE(); 7 | 8 | expect(key.isInKey(new Note('E4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('F#4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('G#4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('A4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('B4'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('C#5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('D#5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('E5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('E#'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('F'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('G'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('A#'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('B#'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('C'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('D'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfEb.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfEb } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfEb, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfEb(); 7 | 8 | expect(key.isInKey(new Note('Eb4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('F4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('G4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('Ab4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('Bb4'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('C5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('D5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('Eb5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('E'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('Fb'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('Gb'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('A'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('B'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('Cb'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('Db'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfF.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfF } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfF, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfF(); 7 | 8 | expect(key.isInKey(new Note('F4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('G4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('A4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('Bb4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('C5'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('D5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('E5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('F5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('Fb'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('Gb'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('Ab'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('B'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('Cb'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('Db'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('Eb'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfFSharp.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfFSharp } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfFSharp, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfFSharp(); 7 | 8 | expect(key.isInKey(new Note('F#4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('G#4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('A#4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('B4'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('C#5'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('D#5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('E#5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('F#5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('F'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('G'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('A'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('B#'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('C'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('D'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('E'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfG.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfG } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfG, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfG(); 7 | 8 | expect(key.isInKey(new Note('G4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('A4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('B4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('C5'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('D5'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('E5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('F#5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('G5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('G#'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('A#'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('B#'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('C#'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('D#'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('E#'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('F'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/keySignatures/keySignatureOfGb.test.ts: -------------------------------------------------------------------------------- 1 | import { KeySignatureOfGb } from '../../keySignatures'; 2 | import { Note } from '../../note'; 3 | 4 | describe(KeySignatureOfGb, () => { 5 | it('returns contains the right notes', () => { 6 | const key = new KeySignatureOfGb(); 7 | 8 | expect(key.isInKey(new Note('Gb4'))).toBeTruthy(); 9 | expect(key.isInKey(new Note('Ab4'))).toBeTruthy(); 10 | expect(key.isInKey(new Note('Bb4'))).toBeTruthy(); 11 | expect(key.isInKey(new Note('Cb5'))).toBeTruthy(); 12 | expect(key.isInKey(new Note('Db5'))).toBeTruthy(); 13 | expect(key.isInKey(new Note('Eb5'))).toBeTruthy(); 14 | expect(key.isInKey(new Note('F5'))).toBeTruthy(); 15 | expect(key.isInKey(new Note('Gb5'))).toBeTruthy(); 16 | 17 | expect(key.isInKey(new Note('G'))).toBeFalsy(); 18 | expect(key.isInKey(new Note('A'))).toBeFalsy(); 19 | expect(key.isInKey(new Note('B'))).toBeFalsy(); 20 | expect(key.isInKey(new Note('C'))).toBeFalsy(); 21 | expect(key.isInKey(new Note('D'))).toBeFalsy(); 22 | expect(key.isInKey(new Note('E'))).toBeFalsy(); 23 | expect(key.isInKey(new Note('Fb'))).toBeFalsy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/name.test.ts: -------------------------------------------------------------------------------- 1 | import { Name } from '../name'; 2 | 3 | describe('#new', () => { 4 | describe('with a valid name', () => { 5 | it('returns the name', () => { 6 | expect(new Name('C').value).toEqual('C'); 7 | expect(new Name('D').value).toEqual('D'); 8 | expect(new Name('E').value).toEqual('E'); 9 | expect(new Name('F').value).toEqual('F'); 10 | expect(new Name('G').value).toEqual('G'); 11 | expect(new Name('A').value).toEqual('A'); 12 | expect(new Name('B').value).toEqual('B'); 13 | }); 14 | }); 15 | 16 | describe('with an invalid name', () => { 17 | it('returns the C', () => { 18 | expect(new Name('H').value).toEqual('C'); 19 | expect(new Name('a').value).toEqual('C'); 20 | expect(new Name('I').value).toEqual('C'); 21 | expect(new Name('8').value).toEqual('C'); 22 | }); 23 | }); 24 | }); 25 | 26 | describe('#C', () => { 27 | describe('when the note is C', () => { 28 | it('returns true', () => { 29 | expect(new Name('C').isC()).toBeTruthy(); 30 | }); 31 | }); 32 | 33 | describe('when the note is not C', () => { 34 | it('returns false', () => { 35 | expect(new Name('D').isC()).toBeFalsy(); 36 | expect(new Name('E').isC()).toBeFalsy(); 37 | expect(new Name('F').isC()).toBeFalsy(); 38 | expect(new Name('G').isC()).toBeFalsy(); 39 | expect(new Name('A').isC()).toBeFalsy(); 40 | expect(new Name('B').isC()).toBeFalsy(); 41 | }); 42 | }); 43 | }); 44 | 45 | describe('#D', () => { 46 | describe('when the note is D', () => { 47 | it('returns true', () => { 48 | expect(new Name('D').isD()).toBeTruthy(); 49 | }); 50 | }); 51 | 52 | describe('when the note is not D', () => { 53 | it('returns false', () => { 54 | expect(new Name('C').isD()).toBeFalsy(); 55 | expect(new Name('E').isD()).toBeFalsy(); 56 | expect(new Name('F').isD()).toBeFalsy(); 57 | expect(new Name('G').isD()).toBeFalsy(); 58 | expect(new Name('A').isD()).toBeFalsy(); 59 | expect(new Name('B').isD()).toBeFalsy(); 60 | }); 61 | }); 62 | }); 63 | 64 | describe('#E', () => { 65 | describe('when the note is E', () => { 66 | it('returns true', () => { 67 | expect(new Name('E').isE()).toBeTruthy(); 68 | }); 69 | }); 70 | 71 | describe('when the note is not E', () => { 72 | it('returns false', () => { 73 | expect(new Name('C').isE()).toBeFalsy(); 74 | expect(new Name('D').isE()).toBeFalsy(); 75 | expect(new Name('F').isE()).toBeFalsy(); 76 | expect(new Name('G').isE()).toBeFalsy(); 77 | expect(new Name('A').isE()).toBeFalsy(); 78 | expect(new Name('B').isE()).toBeFalsy(); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('#F', () => { 84 | describe('when the note is F', () => { 85 | it('returns true', () => { 86 | expect(new Name('F').isF()).toBeTruthy(); 87 | }); 88 | }); 89 | 90 | describe('when the note is not F', () => { 91 | it('returns false', () => { 92 | expect(new Name('C').isF()).toBeFalsy(); 93 | expect(new Name('D').isF()).toBeFalsy(); 94 | expect(new Name('E').isF()).toBeFalsy(); 95 | expect(new Name('G').isF()).toBeFalsy(); 96 | expect(new Name('A').isF()).toBeFalsy(); 97 | expect(new Name('B').isF()).toBeFalsy(); 98 | }); 99 | }); 100 | }); 101 | 102 | describe('#G', () => { 103 | describe('when the note is G', () => { 104 | it('returns true', () => { 105 | expect(new Name('G').isG()).toBeTruthy(); 106 | }); 107 | }); 108 | 109 | describe('when the note is not G', () => { 110 | it('returns false', () => { 111 | expect(new Name('C').isG()).toBeFalsy(); 112 | expect(new Name('D').isG()).toBeFalsy(); 113 | expect(new Name('E').isG()).toBeFalsy(); 114 | expect(new Name('F').isG()).toBeFalsy(); 115 | expect(new Name('A').isG()).toBeFalsy(); 116 | expect(new Name('B').isG()).toBeFalsy(); 117 | }); 118 | }); 119 | }); 120 | 121 | describe('#A', () => { 122 | describe('when the note is A', () => { 123 | it('returns true', () => { 124 | expect(new Name('A').isA()).toBeTruthy(); 125 | }); 126 | }); 127 | 128 | describe('when the note is not A', () => { 129 | it('returns false', () => { 130 | expect(new Name('C').isA()).toBeFalsy(); 131 | expect(new Name('D').isA()).toBeFalsy(); 132 | expect(new Name('E').isA()).toBeFalsy(); 133 | expect(new Name('F').isA()).toBeFalsy(); 134 | expect(new Name('G').isA()).toBeFalsy(); 135 | expect(new Name('B').isA()).toBeFalsy(); 136 | }); 137 | }); 138 | }); 139 | 140 | describe('#B', () => { 141 | describe('when the note is B', () => { 142 | it('returns true', () => { 143 | expect(new Name('B').isB()).toBeTruthy(); 144 | }); 145 | }); 146 | 147 | describe('when the note is not B', () => { 148 | it('returns false', () => { 149 | expect(new Name('C').isB()).toBeFalsy(); 150 | expect(new Name('D').isB()).toBeFalsy(); 151 | expect(new Name('E').isB()).toBeFalsy(); 152 | expect(new Name('F').isB()).toBeFalsy(); 153 | expect(new Name('G').isB()).toBeFalsy(); 154 | expect(new Name('A').isB()).toBeFalsy(); 155 | }); 156 | }); 157 | }); 158 | 159 | describe('.fromMidi', () => { 160 | it('returns the name portion of the MIDI value', () => { 161 | expect(Name.fromMidi(24)).toEqual('C'); 162 | expect(Name.fromMidi(36)).toEqual('C'); 163 | expect(Name.fromMidi(48)).toEqual('C'); 164 | expect(Name.fromMidi(60)).toEqual('C'); 165 | expect(Name.fromMidi(72)).toEqual('C'); 166 | expect(Name.fromMidi(84)).toEqual('C'); 167 | expect(Name.fromMidi(96)).toEqual('C'); 168 | expect(Name.fromMidi(108)).toEqual('C'); 169 | 170 | expect(Name.fromMidi(25)).toEqual('Db'); 171 | expect(Name.fromMidi(37)).toEqual('Db'); 172 | expect(Name.fromMidi(49)).toEqual('Db'); 173 | expect(Name.fromMidi(61)).toEqual('Db'); 174 | expect(Name.fromMidi(73)).toEqual('Db'); 175 | expect(Name.fromMidi(85)).toEqual('Db'); 176 | expect(Name.fromMidi(97)).toEqual('Db'); 177 | 178 | expect(Name.fromMidi(26)).toEqual('D'); 179 | expect(Name.fromMidi(38)).toEqual('D'); 180 | expect(Name.fromMidi(50)).toEqual('D'); 181 | expect(Name.fromMidi(62)).toEqual('D'); 182 | expect(Name.fromMidi(74)).toEqual('D'); 183 | expect(Name.fromMidi(86)).toEqual('D'); 184 | expect(Name.fromMidi(98)).toEqual('D'); 185 | 186 | expect(Name.fromMidi(27)).toEqual('Eb'); 187 | expect(Name.fromMidi(39)).toEqual('Eb'); 188 | expect(Name.fromMidi(51)).toEqual('Eb'); 189 | expect(Name.fromMidi(63)).toEqual('Eb'); 190 | expect(Name.fromMidi(75)).toEqual('Eb'); 191 | expect(Name.fromMidi(87)).toEqual('Eb'); 192 | expect(Name.fromMidi(99)).toEqual('Eb'); 193 | 194 | expect(Name.fromMidi(28)).toEqual('E'); 195 | expect(Name.fromMidi(40)).toEqual('E'); 196 | expect(Name.fromMidi(52)).toEqual('E'); 197 | expect(Name.fromMidi(64)).toEqual('E'); 198 | expect(Name.fromMidi(76)).toEqual('E'); 199 | expect(Name.fromMidi(88)).toEqual('E'); 200 | expect(Name.fromMidi(100)).toEqual('E'); 201 | 202 | expect(Name.fromMidi(29)).toEqual('F'); 203 | expect(Name.fromMidi(41)).toEqual('F'); 204 | expect(Name.fromMidi(53)).toEqual('F'); 205 | expect(Name.fromMidi(65)).toEqual('F'); 206 | expect(Name.fromMidi(77)).toEqual('F'); 207 | expect(Name.fromMidi(89)).toEqual('F'); 208 | expect(Name.fromMidi(101)).toEqual('F'); 209 | 210 | expect(Name.fromMidi(30)).toEqual('Gb'); 211 | expect(Name.fromMidi(42)).toEqual('Gb'); 212 | expect(Name.fromMidi(54)).toEqual('Gb'); 213 | expect(Name.fromMidi(66)).toEqual('Gb'); 214 | expect(Name.fromMidi(78)).toEqual('Gb'); 215 | expect(Name.fromMidi(90)).toEqual('Gb'); 216 | expect(Name.fromMidi(102)).toEqual('Gb'); 217 | 218 | expect(Name.fromMidi(31)).toEqual('G'); 219 | expect(Name.fromMidi(43)).toEqual('G'); 220 | expect(Name.fromMidi(55)).toEqual('G'); 221 | expect(Name.fromMidi(67)).toEqual('G'); 222 | expect(Name.fromMidi(79)).toEqual('G'); 223 | expect(Name.fromMidi(91)).toEqual('G'); 224 | expect(Name.fromMidi(103)).toEqual('G'); 225 | 226 | expect(Name.fromMidi(32)).toEqual('Ab'); 227 | expect(Name.fromMidi(44)).toEqual('Ab'); 228 | expect(Name.fromMidi(56)).toEqual('Ab'); 229 | expect(Name.fromMidi(68)).toEqual('Ab'); 230 | expect(Name.fromMidi(80)).toEqual('Ab'); 231 | expect(Name.fromMidi(92)).toEqual('Ab'); 232 | expect(Name.fromMidi(104)).toEqual('Ab'); 233 | 234 | expect(Name.fromMidi(33)).toEqual('A'); 235 | expect(Name.fromMidi(45)).toEqual('A'); 236 | expect(Name.fromMidi(57)).toEqual('A'); 237 | expect(Name.fromMidi(69)).toEqual('A'); 238 | expect(Name.fromMidi(81)).toEqual('A'); 239 | expect(Name.fromMidi(93)).toEqual('A'); 240 | expect(Name.fromMidi(105)).toEqual('A'); 241 | 242 | expect(Name.fromMidi(34)).toEqual('Bb'); 243 | expect(Name.fromMidi(46)).toEqual('Bb'); 244 | expect(Name.fromMidi(58)).toEqual('Bb'); 245 | expect(Name.fromMidi(70)).toEqual('Bb'); 246 | expect(Name.fromMidi(82)).toEqual('Bb'); 247 | expect(Name.fromMidi(94)).toEqual('Bb'); 248 | expect(Name.fromMidi(106)).toEqual('Bb'); 249 | 250 | expect(Name.fromMidi(35)).toEqual('B'); 251 | expect(Name.fromMidi(47)).toEqual('B'); 252 | expect(Name.fromMidi(59)).toEqual('B'); 253 | expect(Name.fromMidi(71)).toEqual('B'); 254 | expect(Name.fromMidi(83)).toEqual('B'); 255 | expect(Name.fromMidi(95)).toEqual('B'); 256 | expect(Name.fromMidi(107)).toEqual('B'); 257 | }); 258 | }); 259 | -------------------------------------------------------------------------------- /src/__tests__/note.test.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | 3 | describe('.fromMidi', () => { 4 | it('returns an instance of a Note', () => { 5 | expect(Note.fromMidi(21).equals(new Note('A0'))).toBeTruthy(); 6 | expect(Note.fromMidi(22).equals(new Note('Bb0'))).toBeTruthy(); 7 | expect(Note.fromMidi(23).equals(new Note('B0'))).toBeTruthy(); 8 | expect(Note.fromMidi(24).equals(new Note('C1'))).toBeTruthy(); 9 | expect(Note.fromMidi(25).equals(new Note('Db1'))).toBeTruthy(); 10 | expect(Note.fromMidi(26).equals(new Note('D1'))).toBeTruthy(); 11 | expect(Note.fromMidi(27).equals(new Note('Eb1'))).toBeTruthy(); 12 | expect(Note.fromMidi(28).equals(new Note('E1'))).toBeTruthy(); 13 | expect(Note.fromMidi(29).equals(new Note('F1'))).toBeTruthy(); 14 | expect(Note.fromMidi(30).equals(new Note('Gb1'))).toBeTruthy(); 15 | expect(Note.fromMidi(31).equals(new Note('G1'))).toBeTruthy(); 16 | expect(Note.fromMidi(32).equals(new Note('Ab1'))).toBeTruthy(); 17 | expect(Note.fromMidi(33).equals(new Note('A1'))).toBeTruthy(); 18 | expect(Note.fromMidi(34).equals(new Note('Bb1'))).toBeTruthy(); 19 | expect(Note.fromMidi(35).equals(new Note('B1'))).toBeTruthy(); 20 | expect(Note.fromMidi(36).equals(new Note('C2'))).toBeTruthy(); 21 | expect(Note.fromMidi(37).equals(new Note('Db2'))).toBeTruthy(); 22 | expect(Note.fromMidi(38).equals(new Note('D2'))).toBeTruthy(); 23 | expect(Note.fromMidi(39).equals(new Note('Eb2'))).toBeTruthy(); 24 | expect(Note.fromMidi(40).equals(new Note('E2'))).toBeTruthy(); 25 | expect(Note.fromMidi(41).equals(new Note('F2'))).toBeTruthy(); 26 | expect(Note.fromMidi(42).equals(new Note('Gb2'))).toBeTruthy(); 27 | expect(Note.fromMidi(43).equals(new Note('G2'))).toBeTruthy(); 28 | expect(Note.fromMidi(44).equals(new Note('Ab2'))).toBeTruthy(); 29 | expect(Note.fromMidi(45).equals(new Note('A2'))).toBeTruthy(); 30 | expect(Note.fromMidi(46).equals(new Note('Bb2'))).toBeTruthy(); 31 | expect(Note.fromMidi(47).equals(new Note('B2'))).toBeTruthy(); 32 | expect(Note.fromMidi(48).equals(new Note('C3'))).toBeTruthy(); 33 | expect(Note.fromMidi(49).equals(new Note('Db3'))).toBeTruthy(); 34 | expect(Note.fromMidi(50).equals(new Note('D3'))).toBeTruthy(); 35 | expect(Note.fromMidi(51).equals(new Note('Eb3'))).toBeTruthy(); 36 | expect(Note.fromMidi(52).equals(new Note('E3'))).toBeTruthy(); 37 | expect(Note.fromMidi(53).equals(new Note('F3'))).toBeTruthy(); 38 | expect(Note.fromMidi(54).equals(new Note('Gb3'))).toBeTruthy(); 39 | expect(Note.fromMidi(55).equals(new Note('G3'))).toBeTruthy(); 40 | expect(Note.fromMidi(56).equals(new Note('Ab3'))).toBeTruthy(); 41 | expect(Note.fromMidi(57).equals(new Note('A3'))).toBeTruthy(); 42 | expect(Note.fromMidi(58).equals(new Note('Bb3'))).toBeTruthy(); 43 | expect(Note.fromMidi(59).equals(new Note('B3'))).toBeTruthy(); 44 | expect(Note.fromMidi(60).equals(new Note('C4'))).toBeTruthy(); 45 | expect(Note.fromMidi(61).equals(new Note('Db4'))).toBeTruthy(); 46 | expect(Note.fromMidi(62).equals(new Note('D4'))).toBeTruthy(); 47 | expect(Note.fromMidi(63).equals(new Note('Eb4'))).toBeTruthy(); 48 | expect(Note.fromMidi(64).equals(new Note('E4'))).toBeTruthy(); 49 | expect(Note.fromMidi(65).equals(new Note('F4'))).toBeTruthy(); 50 | expect(Note.fromMidi(66).equals(new Note('Gb4'))).toBeTruthy(); 51 | expect(Note.fromMidi(67).equals(new Note('G4'))).toBeTruthy(); 52 | expect(Note.fromMidi(68).equals(new Note('Ab4'))).toBeTruthy(); 53 | expect(Note.fromMidi(69).equals(new Note('A4'))).toBeTruthy(); 54 | expect(Note.fromMidi(70).equals(new Note('Bb4'))).toBeTruthy(); 55 | expect(Note.fromMidi(71).equals(new Note('B4'))).toBeTruthy(); 56 | expect(Note.fromMidi(72).equals(new Note('C5'))).toBeTruthy(); 57 | expect(Note.fromMidi(73).equals(new Note('Db5'))).toBeTruthy(); 58 | expect(Note.fromMidi(74).equals(new Note('D5'))).toBeTruthy(); 59 | expect(Note.fromMidi(75).equals(new Note('Eb5'))).toBeTruthy(); 60 | expect(Note.fromMidi(76).equals(new Note('E5'))).toBeTruthy(); 61 | expect(Note.fromMidi(77).equals(new Note('F5'))).toBeTruthy(); 62 | expect(Note.fromMidi(78).equals(new Note('Gb5'))).toBeTruthy(); 63 | expect(Note.fromMidi(79).equals(new Note('G5'))).toBeTruthy(); 64 | expect(Note.fromMidi(80).equals(new Note('Ab5'))).toBeTruthy(); 65 | expect(Note.fromMidi(81).equals(new Note('A5'))).toBeTruthy(); 66 | expect(Note.fromMidi(82).equals(new Note('Bb5'))).toBeTruthy(); 67 | expect(Note.fromMidi(83).equals(new Note('B5'))).toBeTruthy(); 68 | expect(Note.fromMidi(84).equals(new Note('C6'))).toBeTruthy(); 69 | expect(Note.fromMidi(85).equals(new Note('Db6'))).toBeTruthy(); 70 | expect(Note.fromMidi(86).equals(new Note('D6'))).toBeTruthy(); 71 | expect(Note.fromMidi(87).equals(new Note('Eb6'))).toBeTruthy(); 72 | expect(Note.fromMidi(88).equals(new Note('E6'))).toBeTruthy(); 73 | expect(Note.fromMidi(89).equals(new Note('F6'))).toBeTruthy(); 74 | expect(Note.fromMidi(90).equals(new Note('Gb6'))).toBeTruthy(); 75 | expect(Note.fromMidi(91).equals(new Note('G6'))).toBeTruthy(); 76 | expect(Note.fromMidi(92).equals(new Note('Ab6'))).toBeTruthy(); 77 | expect(Note.fromMidi(93).equals(new Note('A6'))).toBeTruthy(); 78 | expect(Note.fromMidi(94).equals(new Note('Bb6'))).toBeTruthy(); 79 | expect(Note.fromMidi(95).equals(new Note('B6'))).toBeTruthy(); 80 | expect(Note.fromMidi(96).equals(new Note('C7'))).toBeTruthy(); 81 | expect(Note.fromMidi(97).equals(new Note('Db7'))).toBeTruthy(); 82 | expect(Note.fromMidi(98).equals(new Note('D7'))).toBeTruthy(); 83 | expect(Note.fromMidi(99).equals(new Note('Eb7'))).toBeTruthy(); 84 | expect(Note.fromMidi(100).equals(new Note('E7'))).toBeTruthy(); 85 | expect(Note.fromMidi(101).equals(new Note('F7'))).toBeTruthy(); 86 | expect(Note.fromMidi(102).equals(new Note('Gb7'))).toBeTruthy(); 87 | expect(Note.fromMidi(103).equals(new Note('G7'))).toBeTruthy(); 88 | expect(Note.fromMidi(104).equals(new Note('Ab7'))).toBeTruthy(); 89 | expect(Note.fromMidi(105).equals(new Note('A7'))).toBeTruthy(); 90 | expect(Note.fromMidi(106).equals(new Note('Bb7'))).toBeTruthy(); 91 | expect(Note.fromMidi(107).equals(new Note('B7'))).toBeTruthy(); 92 | expect(Note.fromMidi(108).equals(new Note('C8'))).toBeTruthy(); 93 | }); 94 | 95 | describe('with an invalid midi value', () => { 96 | it('throws an error', () => { 97 | expect(() => Note.fromMidi(20)).toThrow('Invalid Midi note'); 98 | expect(() => Note.fromMidi(109)).toThrow('Invalid Midi note'); 99 | }); 100 | }); 101 | }); 102 | 103 | describe('#constructor', () => { 104 | describe('with a valid note', () => { 105 | it('returns an instance of Note', () => { 106 | expect(new Note('C')).toBeInstanceOf(Note); 107 | 108 | expect(new Note('C#')).toBeInstanceOf(Note); 109 | expect(new Note('C𝄪')).toBeInstanceOf(Note); 110 | expect(new Note('Cb')).toBeInstanceOf(Note); 111 | expect(new Note('C𝄫')).toBeInstanceOf(Note); 112 | expect(new Note('C♮')).toBeInstanceOf(Note); 113 | 114 | expect(new Note('C#8')).toBeInstanceOf(Note); 115 | }); 116 | }); 117 | 118 | describe('with an invalid note', () => { 119 | it('throws an error', () => { 120 | expect(() => new Note('abc')).toThrow(); 121 | 122 | expect(() => new Note('C0')).toThrow(); 123 | expect(() => new Note('D0')).toThrow(); 124 | expect(() => new Note('E0')).toThrow(); 125 | expect(() => new Note('F0')).toThrow(); 126 | expect(() => new Note('G0')).toThrow(); 127 | 128 | expect(() => new Note('D8')).toThrow(); 129 | expect(() => new Note('E8')).toThrow(); 130 | expect(() => new Note('F8')).toThrow(); 131 | expect(() => new Note('G8')).toThrow(); 132 | expect(() => new Note('A8')).toThrow(); 133 | expect(() => new Note('B8')).toThrow(); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('#getKeyNumber', () => { 139 | it('returns the key number', () => { 140 | expect(new Note('A0').getKeyNumber()).toEqual(1); 141 | expect(new Note('B𝄫0').getKeyNumber()).toEqual(1); 142 | expect(new Note('A#0').getKeyNumber()).toEqual(2); 143 | expect(new Note('Bb0').getKeyNumber()).toEqual(2); 144 | expect(new Note('A𝄪0').getKeyNumber()).toEqual(3); 145 | expect(new Note('B0').getKeyNumber()).toEqual(3); 146 | expect(new Note('B#0').getKeyNumber()).toEqual(4); 147 | 148 | expect(new Note('C1').getKeyNumber()).toEqual(4); 149 | expect(new Note('D𝄫1').getKeyNumber()).toEqual(4); 150 | expect(new Note('C#1').getKeyNumber()).toEqual(5); 151 | expect(new Note('Db1').getKeyNumber()).toEqual(5); 152 | expect(new Note('C𝄪1').getKeyNumber()).toEqual(6); 153 | expect(new Note('D1').getKeyNumber()).toEqual(6); 154 | expect(new Note('D#1').getKeyNumber()).toEqual(7); 155 | expect(new Note('Eb1').getKeyNumber()).toEqual(7); 156 | expect(new Note('E1').getKeyNumber()).toEqual(8); 157 | expect(new Note('E#1').getKeyNumber()).toEqual(9); 158 | expect(new Note('F1').getKeyNumber()).toEqual(9); 159 | expect(new Note('F#1').getKeyNumber()).toEqual(10); 160 | expect(new Note('Gb1').getKeyNumber()).toEqual(10); 161 | expect(new Note('F𝄪1').getKeyNumber()).toEqual(11); 162 | expect(new Note('G1').getKeyNumber()).toEqual(11); 163 | expect(new Note('G#1').getKeyNumber()).toEqual(12); 164 | expect(new Note('Ab1').getKeyNumber()).toEqual(12); 165 | expect(new Note('A1').getKeyNumber()).toEqual(13); 166 | expect(new Note('B𝄫1').getKeyNumber()).toEqual(13); 167 | expect(new Note('A#1').getKeyNumber()).toEqual(14); 168 | expect(new Note('Bb1').getKeyNumber()).toEqual(14); 169 | expect(new Note('B1').getKeyNumber()).toEqual(15); 170 | 171 | expect(new Note('C2').getKeyNumber()).toEqual(16); 172 | expect(new Note('C#2').getKeyNumber()).toEqual(17); 173 | expect(new Note('C𝄪2').getKeyNumber()).toEqual(18); 174 | expect(new Note('D3').getKeyNumber()).toEqual(30); 175 | expect(new Note('D#3').getKeyNumber()).toEqual(31); 176 | expect(new Note('E4').getKeyNumber()).toEqual(44); 177 | expect(new Note('F5').getKeyNumber()).toEqual(57); 178 | expect(new Note('F#5').getKeyNumber()).toEqual(58); 179 | expect(new Note('G𝄫6').getKeyNumber()).toEqual(69); 180 | expect(new Note('G6').getKeyNumber()).toEqual(71); 181 | expect(new Note('G#6').getKeyNumber()).toEqual(72); 182 | expect(new Note('A7').getKeyNumber()).toEqual(85); 183 | expect(new Note('A#7').getKeyNumber()).toEqual(86); 184 | }); 185 | }); 186 | 187 | describe('#getMidiValue', () => { 188 | it('returns the MIDI value', () => { 189 | expect(new Note('A0').getMidiValue()).toEqual(21); 190 | expect(new Note('B𝄫0').getMidiValue()).toEqual(21); 191 | expect(new Note('A#0').getMidiValue()).toEqual(22); 192 | expect(new Note('Bb0').getMidiValue()).toEqual(22); 193 | expect(new Note('A𝄪0').getMidiValue()).toEqual(23); 194 | expect(new Note('B0').getMidiValue()).toEqual(23); 195 | expect(new Note('B#0').getMidiValue()).toEqual(24); 196 | 197 | expect(new Note('C1').getMidiValue()).toEqual(24); 198 | expect(new Note('D𝄫1').getMidiValue()).toEqual(24); 199 | expect(new Note('C#1').getMidiValue()).toEqual(25); 200 | expect(new Note('Db1').getMidiValue()).toEqual(25); 201 | expect(new Note('C𝄪1').getMidiValue()).toEqual(26); 202 | expect(new Note('D1').getMidiValue()).toEqual(26); 203 | expect(new Note('D#1').getMidiValue()).toEqual(27); 204 | expect(new Note('Eb1').getMidiValue()).toEqual(27); 205 | expect(new Note('E1').getMidiValue()).toEqual(28); 206 | expect(new Note('Fb1').getMidiValue()).toEqual(28); 207 | expect(new Note('E#1').getMidiValue()).toEqual(29); 208 | expect(new Note('F1').getMidiValue()).toEqual(29); 209 | expect(new Note('F#1').getMidiValue()).toEqual(30); 210 | expect(new Note('Gb1').getMidiValue()).toEqual(30); 211 | expect(new Note('F𝄪1').getMidiValue()).toEqual(31); 212 | expect(new Note('G1').getMidiValue()).toEqual(31); 213 | expect(new Note('G#1').getMidiValue()).toEqual(32); 214 | expect(new Note('Ab1').getMidiValue()).toEqual(32); 215 | expect(new Note('A1').getMidiValue()).toEqual(33); 216 | expect(new Note('B𝄫1').getMidiValue()).toEqual(33); 217 | expect(new Note('A#1').getMidiValue()).toEqual(34); 218 | expect(new Note('Bb1').getMidiValue()).toEqual(34); 219 | expect(new Note('B1').getMidiValue()).toEqual(35); 220 | expect(new Note('B#1').getMidiValue()).toEqual(36); 221 | 222 | expect(new Note('C2').getMidiValue()).toEqual(36); 223 | expect(new Note('C#2').getMidiValue()).toEqual(37); 224 | expect(new Note('C𝄪2').getMidiValue()).toEqual(38); 225 | expect(new Note('Db3').getMidiValue()).toEqual(49); 226 | expect(new Note('D3').getMidiValue()).toEqual(50); 227 | expect(new Note('Eb4').getMidiValue()).toEqual(63); 228 | expect(new Note('E4').getMidiValue()).toEqual(64); 229 | expect(new Note('F5').getMidiValue()).toEqual(77); 230 | expect(new Note('F#5').getMidiValue()).toEqual(78); 231 | expect(new Note('G𝄫6').getMidiValue()).toEqual(89); 232 | expect(new Note('Gb6').getMidiValue()).toEqual(90); 233 | expect(new Note('G6').getMidiValue()).toEqual(91); 234 | expect(new Note('Ab7').getMidiValue()).toEqual(104); 235 | expect(new Note('A7').getMidiValue()).toEqual(105); 236 | }); 237 | }); 238 | 239 | describe('#getOctave', () => { 240 | describe('with an octave', () => { 241 | it('returns that octave', () => { 242 | expect(new Note('A0').getOctave()).toEqual(0); 243 | expect(new Note('Bb0').getOctave()).toEqual(0); 244 | expect(new Note('C1').getOctave()).toEqual(1); 245 | expect(new Note('C#1').getOctave()).toEqual(1); 246 | expect(new Note('D2').getOctave()).toEqual(2); 247 | expect(new Note('Db2').getOctave()).toEqual(2); 248 | expect(new Note('E3').getOctave()).toEqual(3); 249 | expect(new Note('Eb3').getOctave()).toEqual(3); 250 | expect(new Note('F4').getOctave()).toEqual(4); 251 | expect(new Note('F#4').getOctave()).toEqual(4); 252 | expect(new Note('G5').getOctave()).toEqual(5); 253 | expect(new Note('Gb5').getOctave()).toEqual(5); 254 | expect(new Note('A6').getOctave()).toEqual(6); 255 | expect(new Note('Ab6').getOctave()).toEqual(6); 256 | expect(new Note('B7').getOctave()).toEqual(7); 257 | expect(new Note('Bb7').getOctave()).toEqual(7); 258 | }); 259 | }); 260 | 261 | describe('without an octave', () => { 262 | it('returns the middle octave', () => { 263 | expect(new Note('C').getOctave()).toEqual(4); 264 | expect(new Note('C#').getOctave()).toEqual(4); 265 | expect(new Note('D').getOctave()).toEqual(4); 266 | expect(new Note('Db').getOctave()).toEqual(4); 267 | expect(new Note('E').getOctave()).toEqual(4); 268 | expect(new Note('Eb').getOctave()).toEqual(4); 269 | expect(new Note('F').getOctave()).toEqual(4); 270 | expect(new Note('F#').getOctave()).toEqual(4); 271 | expect(new Note('G').getOctave()).toEqual(4); 272 | expect(new Note('G#').getOctave()).toEqual(4); 273 | expect(new Note('A').getOctave()).toEqual(4); 274 | expect(new Note('Ab').getOctave()).toEqual(4); 275 | expect(new Note('B').getOctave()).toEqual(4); 276 | expect(new Note('Bb').getOctave()).toEqual(4); 277 | }); 278 | }); 279 | }); 280 | 281 | describe('#setOctave', () => { 282 | it('returns a new Note in that octave', () => { 283 | expect(new Note('C4').setOctave(1)).toEqual(new Note('C1')); 284 | }); 285 | }); 286 | 287 | describe('#getScientificName', () => { 288 | it('returns the note name and octave', () => { 289 | expect(new Note('C').getScientificName()).toEqual('C4'); 290 | expect(new Note('C#').getScientificName()).toEqual('C#4'); 291 | expect(new Note('Db').getScientificName()).toEqual('Db4'); 292 | expect(new Note('D').getScientificName()).toEqual('D4'); 293 | expect(new Note('D#').getScientificName()).toEqual('D#4'); 294 | expect(new Note('Eb').getScientificName()).toEqual('Eb4'); 295 | expect(new Note('E').getScientificName()).toEqual('E4'); 296 | expect(new Note('E#').getScientificName()).toEqual('E#4'); 297 | expect(new Note('Fb').getScientificName()).toEqual('Fb4'); 298 | expect(new Note('F').getScientificName()).toEqual('F4'); 299 | expect(new Note('F#').getScientificName()).toEqual('F#4'); 300 | expect(new Note('Gb').getScientificName()).toEqual('Gb4'); 301 | expect(new Note('G').getScientificName()).toEqual('G4'); 302 | expect(new Note('G#').getScientificName()).toEqual('G#4'); 303 | expect(new Note('Ab').getScientificName()).toEqual('Ab4'); 304 | expect(new Note('A').getScientificName()).toEqual('A4'); 305 | expect(new Note('A#').getScientificName()).toEqual('A#4'); 306 | expect(new Note('Bb').getScientificName()).toEqual('Bb4'); 307 | expect(new Note('B').getScientificName()).toEqual('B4'); 308 | expect(new Note('B#').getScientificName()).toEqual('B#4'); 309 | 310 | expect(new Note('A0').getScientificName()).toEqual('A0'); 311 | expect(new Note('A#0').getScientificName()).toEqual('A#0'); 312 | expect(new Note('C1').getScientificName()).toEqual('C1'); 313 | expect(new Note('C#1').getScientificName()).toEqual('C#1'); 314 | expect(new Note('D2').getScientificName()).toEqual('D2'); 315 | expect(new Note('Db2').getScientificName()).toEqual('Db2'); 316 | expect(new Note('E3').getScientificName()).toEqual('E3'); 317 | expect(new Note('Eb3').getScientificName()).toEqual('Eb3'); 318 | expect(new Note('F4').getScientificName()).toEqual('F4'); 319 | expect(new Note('F#4').getScientificName()).toEqual('F#4'); 320 | expect(new Note('G5').getScientificName()).toEqual('G5'); 321 | expect(new Note('Gb5').getScientificName()).toEqual('Gb5'); 322 | expect(new Note('A6').getScientificName()).toEqual('A6'); 323 | expect(new Note('A#6').getScientificName()).toEqual('A#6'); 324 | expect(new Note('B7').getScientificName()).toEqual('B7'); 325 | expect(new Note('Bb7').getScientificName()).toEqual('Bb7'); 326 | }); 327 | }); 328 | 329 | describe('#getName', () => { 330 | it('returns the name of the note', () => { 331 | expect(new Note('C').getName()).toEqual('C'); 332 | expect(new Note('C#').getName()).toEqual('C#'); 333 | expect(new Note('Db').getName()).toEqual('Db'); 334 | expect(new Note('D').getName()).toEqual('D'); 335 | expect(new Note('D#').getName()).toEqual('D#'); 336 | expect(new Note('Eb').getName()).toEqual('Eb'); 337 | expect(new Note('E').getName()).toEqual('E'); 338 | expect(new Note('E#').getName()).toEqual('E#'); 339 | expect(new Note('Fb').getName()).toEqual('Fb'); 340 | expect(new Note('F').getName()).toEqual('F'); 341 | expect(new Note('F#').getName()).toEqual('F#'); 342 | expect(new Note('Gb').getName()).toEqual('Gb'); 343 | expect(new Note('G').getName()).toEqual('G'); 344 | expect(new Note('G#').getName()).toEqual('G#'); 345 | expect(new Note('Ab').getName()).toEqual('Ab'); 346 | expect(new Note('A').getName()).toEqual('A'); 347 | expect(new Note('A#').getName()).toEqual('A#'); 348 | expect(new Note('Bb').getName()).toEqual('Bb'); 349 | expect(new Note('B').getName()).toEqual('B'); 350 | expect(new Note('B#').getName()).toEqual('B#'); 351 | 352 | expect(new Note('A0').getName()).toEqual('A'); 353 | expect(new Note('C1').getName()).toEqual('C'); 354 | expect(new Note('D2').getName()).toEqual('D'); 355 | expect(new Note('E3').getName()).toEqual('E'); 356 | expect(new Note('F4').getName()).toEqual('F'); 357 | expect(new Note('G5').getName()).toEqual('G'); 358 | expect(new Note('A6').getName()).toEqual('A'); 359 | expect(new Note('B7').getName()).toEqual('B'); 360 | }); 361 | }); 362 | 363 | describe('#getFrequency', () => { 364 | it('returns the frequency', () => { 365 | expect(new Note('A0').getFrequency()).toEqual(27.5); 366 | expect(new Note('Bb0').getFrequency()).toEqual(29.13524); 367 | expect(new Note('B0').getFrequency()).toEqual(30.86771); 368 | expect(new Note('C1').getFrequency()).toEqual(32.7032); 369 | expect(new Note('C#1').getFrequency()).toEqual(34.64783); 370 | expect(new Note('D2').getFrequency()).toEqual(73.41619); 371 | expect(new Note('Db2').getFrequency()).toEqual(69.29566); 372 | expect(new Note('E3').getFrequency()).toEqual(164.81378); 373 | expect(new Note('Eb3').getFrequency()).toEqual(155.56349); 374 | expect(new Note('F4').getFrequency()).toEqual(349.22823); 375 | expect(new Note('F#4').getFrequency()).toEqual(369.99442); 376 | expect(new Note('A4').getFrequency()).toEqual(440.0); 377 | expect(new Note('G5').getFrequency()).toEqual(783.99087); 378 | expect(new Note('Gb5').getFrequency()).toEqual(739.98885); 379 | expect(new Note('A6').getFrequency()).toEqual(1760.0); 380 | expect(new Note('Ab6').getFrequency()).toEqual(1661.21879); 381 | expect(new Note('B7').getFrequency()).toEqual(3951.06641); 382 | expect(new Note('Bb7').getFrequency()).toEqual(3729.31009); 383 | }); 384 | }); 385 | 386 | describe('#matches', () => { 387 | describe('when the MIDI values match', () => { 388 | it('returns true', () => { 389 | expect(new Note('C').matches(new Note('C'))).toBeTruthy(); 390 | expect(new Note('C#').matches(new Note('Db'))).toBeTruthy(); 391 | expect(new Note('F#').matches(new Note('Gb'))).toBeTruthy(); 392 | }); 393 | }); 394 | 395 | describe('when the MIDI values do not match', () => { 396 | it('returns false', () => { 397 | expect(new Note('C').matches(new Note('D'))).toBeFalsy(); 398 | }); 399 | }); 400 | }); 401 | 402 | describe('#equals', () => { 403 | describe('when the scientific names match', () => { 404 | it('returns true', () => { 405 | expect(new Note('C').equals(new Note('C'))).toBeTruthy(); 406 | expect(new Note('C#6').equals(new Note('C#6'))).toBeTruthy(); 407 | expect(new Note('B𝄫3').equals(new Note('B𝄫3'))).toBeTruthy(); 408 | }); 409 | }); 410 | 411 | describe('when the scientific names do not match', () => { 412 | it('returns false', () => { 413 | expect(new Note('C').equals(new Note('D'))).toBeFalsy(); 414 | expect(new Note('C#').equals(new Note('Db'))).toBeFalsy(); 415 | expect(new Note('C#6').equals(new Note('Db6'))).toBeFalsy(); 416 | }); 417 | }); 418 | }); 419 | -------------------------------------------------------------------------------- /src/__tests__/octave.test.ts: -------------------------------------------------------------------------------- 1 | import { Octave } from '../octave'; 2 | 3 | describe('#new', () => { 4 | describe('with a valid number', () => { 5 | it('returns an octave', () => { 6 | expect(new Octave('0').value).toEqual(0); 7 | expect(new Octave(0).value).toEqual(0); 8 | 9 | expect(new Octave('4').value).toEqual(4); 10 | expect(new Octave(4).value).toEqual(4); 11 | 12 | expect(new Octave('8').value).toEqual(8); 13 | expect(new Octave(8).value).toEqual(8); 14 | }); 15 | }); 16 | 17 | describe('with an invalid number', () => { 18 | it('returns the middle octave', () => { 19 | expect(new Octave('-1')).toEqual(Octave.middle()); 20 | expect(new Octave(-1)).toEqual(Octave.middle()); 21 | 22 | expect(new Octave('9')).toEqual(Octave.middle()); 23 | expect(new Octave(9)).toEqual(Octave.middle()); 24 | }); 25 | }); 26 | 27 | describe('with no value', () => { 28 | it('returns the middle octave', () => { 29 | expect(new Octave()).toEqual(Octave.middle()); 30 | }); 31 | }); 32 | }); 33 | 34 | describe('#toString', () => { 35 | it('returns the octave value', () => { 36 | expect(new Octave(3).toString()).toEqual('3'); 37 | }); 38 | }); 39 | 40 | describe('.middle', () => { 41 | it('returns the 4th octave', () => { 42 | expect(Octave.middle()).toEqual(new Octave(4)); 43 | }); 44 | }); 45 | 46 | describe('.fromMidi', () => { 47 | it('returns the octave from the midi value', () => { 48 | expect(Octave.fromMidi(21).equals(new Octave(0))).toBeTruthy(); 49 | expect(Octave.fromMidi(23).equals(new Octave(0))).toBeTruthy(); 50 | expect(Octave.fromMidi(24).equals(new Octave(1))).toBeTruthy(); 51 | expect(Octave.fromMidi(25).equals(new Octave(1))).toBeTruthy(); 52 | expect(Octave.fromMidi(35).equals(new Octave(1))).toBeTruthy(); 53 | expect(Octave.fromMidi(36).equals(new Octave(2))).toBeTruthy(); 54 | expect(Octave.fromMidi(37).equals(new Octave(2))).toBeTruthy(); 55 | expect(Octave.fromMidi(47).equals(new Octave(2))).toBeTruthy(); 56 | expect(Octave.fromMidi(48).equals(new Octave(3))).toBeTruthy(); 57 | expect(Octave.fromMidi(49).equals(new Octave(3))).toBeTruthy(); 58 | expect(Octave.fromMidi(59).equals(new Octave(3))).toBeTruthy(); 59 | expect(Octave.fromMidi(60).equals(new Octave(4))).toBeTruthy(); 60 | expect(Octave.fromMidi(61).equals(new Octave(4))).toBeTruthy(); 61 | expect(Octave.fromMidi(71).equals(new Octave(4))).toBeTruthy(); 62 | expect(Octave.fromMidi(72).equals(new Octave(5))).toBeTruthy(); 63 | expect(Octave.fromMidi(73).equals(new Octave(5))).toBeTruthy(); 64 | expect(Octave.fromMidi(83).equals(new Octave(5))).toBeTruthy(); 65 | expect(Octave.fromMidi(84).equals(new Octave(6))).toBeTruthy(); 66 | expect(Octave.fromMidi(85).equals(new Octave(6))).toBeTruthy(); 67 | expect(Octave.fromMidi(95).equals(new Octave(6))).toBeTruthy(); 68 | expect(Octave.fromMidi(96).equals(new Octave(7))).toBeTruthy(); 69 | expect(Octave.fromMidi(97).equals(new Octave(7))).toBeTruthy(); 70 | expect(Octave.fromMidi(107).equals(new Octave(7))).toBeTruthy(); 71 | expect(Octave.fromMidi(108).equals(new Octave(8))).toBeTruthy(); 72 | }); 73 | }); 74 | 75 | describe('.all', () => { 76 | it('returns all the possible octaves', () => { 77 | expect(Octave.all()).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/__tests__/utils/inputSanitization.test.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../../note'; 2 | import { InputSanitization } from '../../utils'; 3 | 4 | describe('#call', () => { 5 | describe('with a string', () => { 6 | describe('when the string is empty', () => { 7 | it('throws an error', () => { 8 | expect(() => new InputSanitization('').call()).toThrow(); 9 | }); 10 | }); 11 | 12 | it('converts the string to an array of notes', () => { 13 | expect(new InputSanitization('C E G').call()).toEqual([[new Note('C'), new Note('E'), new Note('G')]]); 14 | }); 15 | }); 16 | 17 | describe('with an array of Notes', () => { 18 | describe('when the array is empty', () => { 19 | it('throws an error', () => { 20 | expect(() => new InputSanitization([]).call()).toThrow(); 21 | }); 22 | }); 23 | 24 | it('returns the array of notes', () => { 25 | const C = Note.fromMidi(60); 26 | const E = Note.fromMidi(64); 27 | const G = Note.fromMidi(67); 28 | 29 | expect(new InputSanitization([C, E, G]).call()).toEqual([[C, E, G]]); 30 | }); 31 | }); 32 | 33 | describe('when some notes are duplicated', () => { 34 | it('removes the duplications', () => { 35 | expect(new InputSanitization('C E G C').call()).toEqual([[new Note('C'), new Note('E'), new Note('G')]]); 36 | expect(new InputSanitization('D F# A F#').call()).toEqual([[new Note('D'), new Note('F#'), new Note('A')]]); 37 | expect(new InputSanitization('D F# Gb A').call()).toEqual([[new Note('D'), new Note('F#'), new Note('A')]]); 38 | }); 39 | }); 40 | 41 | describe('when some notes have the same letter but in different octaves', () => { 42 | it('keeps the note with the lowest octave', () => { 43 | expect(new InputSanitization('D F# A D5').call()).toEqual([[new Note('D'), new Note('F#'), new Note('A')]]); 44 | expect(new InputSanitization('D F# A D5 Gb5').call()).toEqual([[new Note('D'), new Note('F#'), new Note('A')]]); 45 | }); 46 | }); 47 | 48 | describe('when the notes are not sorted by MIDI value', () => { 49 | it('sorts the notes by MIDI value', () => { 50 | expect(new InputSanitization('C5 B4 A4 G4 F4 E4 D4').call()).toEqual([ 51 | [ 52 | new Note('D4'), 53 | new Note('E4'), 54 | new Note('F4'), 55 | new Note('G4'), 56 | new Note('A4'), 57 | new Note('B4'), 58 | new Note('C5'), 59 | ], 60 | ]); 61 | }); 62 | }); 63 | 64 | describe('when some extraneous notes are included', () => { 65 | it('filters them out', () => { 66 | expect(new InputSanitization('C E G D2').call()).toEqual([[new Note('C'), new Note('E'), new Note('G')]]); 67 | expect(new InputSanitization('G2 C# E A').call()).toEqual([[new Note('C#'), new Note('E'), new Note('A')]]); 68 | expect(new InputSanitization('C# E A E5').call()).toEqual([[new Note('C#'), new Note('E'), new Note('A')]]); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/__tests__/utils/invertArray.test.ts: -------------------------------------------------------------------------------- 1 | import { invertArray } from '../../utils'; 2 | 3 | describe('invertArray', () => { 4 | const arr = [1, 2, 3, 4, 5]; 5 | 6 | it('inverts the order', () => { 7 | expect(invertArray(arr, 0)).toEqual([1, 2, 3, 4, 5]); 8 | expect(invertArray(arr, 1)).toEqual([2, 3, 4, 5, 1]); 9 | expect(invertArray(arr, 2)).toEqual([3, 4, 5, 1, 2]); 10 | expect(invertArray(arr, 3)).toEqual([4, 5, 1, 2, 3]); 11 | expect(invertArray(arr, 4)).toEqual([5, 1, 2, 3, 4]); 12 | expect(invertArray(arr, 5)).toEqual([1, 2, 3, 4, 5]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/accidental.ts: -------------------------------------------------------------------------------- 1 | import { Flat, DoubleFlat, Sharp, DoubleSharp, Natural } from './accidentals'; 2 | 3 | type Accidentals = 'b' | '𝄫' | '#' | '𝄪' | '♮'; 4 | 5 | export abstract class Accidental { 6 | static accidentals = [DoubleFlat.value, Flat.value, Natural.value, Sharp.value, DoubleSharp.value]; 7 | 8 | public static flat() { 9 | return new Flat(); 10 | } 11 | 12 | public static doubleFlat() { 13 | return new DoubleFlat(); 14 | } 15 | 16 | public static sharp() { 17 | return new Sharp(); 18 | } 19 | 20 | public static doubleSharp() { 21 | return new DoubleSharp(); 22 | } 23 | 24 | public static natural() { 25 | return new Natural(); 26 | } 27 | 28 | public static for(value?: string) { 29 | const accidental = this.parse(value); 30 | switch (accidental) { 31 | case Sharp.value: 32 | return new Sharp(); 33 | case DoubleSharp.value: 34 | return new DoubleSharp(); 35 | case Flat.value: 36 | return new Flat(); 37 | case DoubleFlat.value: 38 | return new DoubleFlat(); 39 | case Natural.value: 40 | default: 41 | return new Natural(); 42 | } 43 | } 44 | 45 | private static parse(value?: string): Accidentals { 46 | if (!value || !this.isValid(value)) { 47 | return Natural.value as Accidentals; 48 | } 49 | 50 | return value as Accidentals; 51 | } 52 | 53 | private static isValid(value: string) { 54 | return this.accidentals.includes(value); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/accidentals/IAccidental.ts: -------------------------------------------------------------------------------- 1 | export interface IAccidental { 2 | getValue(): string; 3 | getKeyIndex(): number; 4 | } 5 | -------------------------------------------------------------------------------- /src/accidentals/doubleFlat.ts: -------------------------------------------------------------------------------- 1 | import { IAccidental } from '.'; 2 | 3 | export class DoubleFlat implements IAccidental { 4 | static value = '𝄫'; 5 | 6 | public getValue() { 7 | return DoubleFlat.value; 8 | } 9 | 10 | public getKeyIndex(): number { 11 | return -2; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/accidentals/doubleSharp.ts: -------------------------------------------------------------------------------- 1 | import { IAccidental } from '.'; 2 | 3 | export class DoubleSharp implements IAccidental { 4 | static value = '𝄪'; 5 | 6 | public getValue() { 7 | return DoubleSharp.value; 8 | } 9 | 10 | public getKeyIndex(): number { 11 | return 2; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/accidentals/flat.ts: -------------------------------------------------------------------------------- 1 | import { IAccidental } from '.'; 2 | 3 | export class Flat implements IAccidental { 4 | static value = 'b'; 5 | 6 | public getValue() { 7 | return Flat.value; 8 | } 9 | 10 | public getKeyIndex(): number { 11 | return -1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/accidentals/index.ts: -------------------------------------------------------------------------------- 1 | export * from './doubleFlat'; 2 | export * from './doubleFlat'; 3 | export * from './doubleSharp'; 4 | export * from './flat'; 5 | export * from './IAccidental'; 6 | export * from './natural'; 7 | export * from './sharp'; 8 | -------------------------------------------------------------------------------- /src/accidentals/natural.ts: -------------------------------------------------------------------------------- 1 | import { IAccidental } from '.'; 2 | 3 | export class Natural implements IAccidental { 4 | static value = '♮'; 5 | 6 | public getValue() { 7 | return ''; 8 | } 9 | 10 | public getKeyIndex(): number { 11 | return 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/accidentals/sharp.ts: -------------------------------------------------------------------------------- 1 | import { IAccidental } from '.'; 2 | 3 | export class Sharp implements IAccidental { 4 | static value = '#'; 5 | 6 | public getValue() { 7 | return Sharp.value; 8 | } 9 | 10 | public getKeyIndex(): number { 11 | return 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/chord.ts: -------------------------------------------------------------------------------- 1 | import { BaseChord, InvertedChord, strategies } from './chords'; 2 | import { Note } from './note'; 3 | import { InputSanitization } from './utils'; 4 | 5 | export abstract class Chord { 6 | /** 7 | * Creates a chord from the notes 8 | * @example 9 | * // With a string 10 | * Chord.for('C E G')?.getName(); // C 11 | * 12 | * // With notes 13 | * const C = Note.fromMidi(60); 14 | * const E = Note.fromMidi(64); 15 | * const G = Note.fromMidi(67); 16 | * Chord.for([C, E, G])?.getName(); // C 17 | */ 18 | public static for(input: string | Note[]): BaseChord | undefined { 19 | const groups = new InputSanitization(input).call(); 20 | 21 | return groups.map((group) => this.findChord(group)).filter(Boolean)[0]; 22 | } 23 | 24 | private static findChord(notes: Note[]) { 25 | return this.getChord(notes) || this.getInvertedChord(notes); 26 | } 27 | 28 | private static getChord(notes: Note[]): BaseChord | undefined { 29 | for (const Strategy of strategies) { 30 | const chord = new Strategy(notes); 31 | if (chord.isMatch()) { 32 | return chord; 33 | } 34 | } 35 | } 36 | 37 | private static getInvertedChord(notes: Note[]): BaseChord | undefined { 38 | for (const Strategy of strategies) { 39 | const chord = new InvertedChord(Strategy, notes); 40 | if (chord.isMatch()) { 41 | return chord; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/chords/augmentedChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { Note } from '../note'; 3 | import { BaseChord } from './baseChord'; 4 | 5 | export class AugmentedChord extends BaseChord { 6 | public static isMatch(notes: Note[]) { 7 | return new AugmentedChord(notes).isMatch(); 8 | } 9 | 10 | public getName(): string { 11 | return `${this.root().getName()}aug`; 12 | } 13 | 14 | public isMatch() { 15 | return ( 16 | this.isTriad() && 17 | Interval.between(this.root(), this.second()).isMajor3rd() && 18 | Interval.between(this.root(), this.third()).isAugmented5th() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chords/augmentedMajorSeventhChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { AugmentedChord } from './augmentedChord'; 3 | import { BaseChord } from './baseChord'; 4 | 5 | export class AugmentedMajorSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}maj+7`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | AugmentedChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMajor7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/augmentedSeventhChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { AugmentedChord } from './augmentedChord'; 3 | import { BaseChord } from './baseChord'; 4 | 5 | export class AugmentedSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}+7`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | AugmentedChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMinor7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/baseChord.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | 3 | export abstract class BaseChord { 4 | constructor(public notes: Note[]) {} 5 | 6 | public getNotes(): Note[] { 7 | return this.notes; 8 | } 9 | 10 | public root() { 11 | return this.getNotes()[0]; 12 | } 13 | 14 | public second() { 15 | return this.getNotes()[1]; 16 | } 17 | 18 | public third() { 19 | return this.getNotes()[2]; 20 | } 21 | 22 | public fourth() { 23 | return this.getNotes()[3]; 24 | } 25 | 26 | public isTriad(): boolean { 27 | return this.getNotes().length === 3; 28 | } 29 | 30 | public getTriad(): Note[] { 31 | return this.getNotes().slice(0, 3); 32 | } 33 | 34 | public isTetrad(): boolean { 35 | return this.getNotes().length === 4; 36 | } 37 | 38 | public abstract isMatch(): boolean; 39 | public abstract getName(): string; 40 | } 41 | -------------------------------------------------------------------------------- /src/chords/diminishedChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { Note } from '../note'; 3 | import { BaseChord } from './baseChord'; 4 | 5 | export class DiminishedChord extends BaseChord { 6 | public static isMatch(notes: Note[]) { 7 | return new DiminishedChord(notes).isMatch(); 8 | } 9 | 10 | public getName(): string { 11 | return `${this.root().getName()}dim`; 12 | } 13 | 14 | public isMatch() { 15 | return ( 16 | this.isTriad() && 17 | Interval.between(this.root(), this.second()).isMinor3rd() && 18 | Interval.between(this.root(), this.third()).isDiminished5th() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chords/diminishedSeventhChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { DiminishedChord } from './diminishedChord'; 4 | 5 | export class DiminishedSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}dim7`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | DiminishedChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isDiminished7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/dominantSeventhChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { MajorChord } from './majorChord'; 4 | 5 | export class DominantSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}7`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | MajorChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMinor7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/halfDiminishedSeventhChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { DiminishedChord } from './diminishedChord'; 4 | 5 | export class HalfDiminishedSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}ø7`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | DiminishedChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMinor7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/index.ts: -------------------------------------------------------------------------------- 1 | export * from './augmentedChord'; 2 | export * from './baseChord'; 3 | export * from './dominantSeventhChord'; 4 | export * from './diminishedChord'; 5 | export * from './diminishedSeventhChord'; 6 | export * from './majorChord'; 7 | export * from './majorSeventhChord'; 8 | export * from './minorChord'; 9 | export * from './minorSeventhChord'; 10 | export * from './invertedChord'; 11 | export * from './suspendedChord'; 12 | export * from './suspendedSecondChord'; 13 | export * from './strategies'; 14 | -------------------------------------------------------------------------------- /src/chords/invertedChord.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { invertArray } from '../utils'; 3 | import { BaseChord } from './baseChord'; 4 | 5 | export type Strategy = new (notes: Note[]) => BaseChord; 6 | 7 | export class InvertedChord extends BaseChord { 8 | DecoratedClass: Strategy; 9 | baseNote: Note; 10 | chord: BaseChord; 11 | 12 | constructor(DecoratedClass: Strategy, notes: Note[]) { 13 | super(notes); 14 | 15 | this.DecoratedClass = DecoratedClass; 16 | this.chord = new DecoratedClass(notes); 17 | this.baseNote = this.root(); 18 | } 19 | 20 | public getName(): string { 21 | return `${this.chord.getName()}/${this.baseNote.getName()}`; 22 | } 23 | 24 | public getNotes(): Note[] { 25 | return this.chord.getNotes(); 26 | } 27 | 28 | public isMatch() { 29 | let index = 1; 30 | do { 31 | const invertedChord = new this.DecoratedClass(invertArray(this.chord.getNotes(), index)); 32 | 33 | if (invertedChord.isMatch()) { 34 | this.chord = invertedChord; 35 | return true; 36 | } 37 | } while (index++ < this.chord.getNotes().length); 38 | 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/chords/majorChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { Note } from '../note'; 3 | import { BaseChord } from './baseChord'; 4 | 5 | export class MajorChord extends BaseChord { 6 | public static isMatch(notes: Note[]) { 7 | return new MajorChord(notes).isMatch(); 8 | } 9 | 10 | public getName(): string { 11 | return this.root().getName(); 12 | } 13 | 14 | public isMatch() { 15 | return ( 16 | this.isTriad() && 17 | Interval.between(this.root(), this.second()).isMajor3rd() && 18 | Interval.between(this.root(), this.third()).isPerfect5th() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chords/majorSeventhChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { MajorChord } from './majorChord'; 4 | 5 | export class MajorSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}maj7`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | MajorChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMajor7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/minorChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { Note } from '../note'; 3 | import { BaseChord } from './baseChord'; 4 | 5 | export class MinorChord extends BaseChord { 6 | public static isMatch(notes: Note[]) { 7 | return new MinorChord(notes).isMatch(); 8 | } 9 | 10 | public getName(): string { 11 | return `${this.root().getName()}m`; 12 | } 13 | 14 | public isMatch() { 15 | return ( 16 | this.isTriad() && 17 | Interval.between(this.root(), this.second()).isMinor3rd() && 18 | Interval.between(this.root(), this.third()).isPerfect5th() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chords/minorMajorSeventh.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { MinorChord } from './minorChord'; 4 | 5 | export class MinorMajorSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}m7+`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | MinorChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMajor7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/minorSeventhChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { MinorChord } from './minorChord'; 4 | 5 | export class MinorSeventhChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}m7`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | MinorChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMinor7th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/minorSixthChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { MinorChord } from './minorChord'; 4 | 5 | export class MinorSixthChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}m6`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | MinorChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMajor6th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/sixthChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | import { MajorChord } from './majorChord'; 4 | 5 | export class SixthChord extends BaseChord { 6 | public getName(): string { 7 | return `${this.root().getName()}6`; 8 | } 9 | 10 | public isMatch() { 11 | return ( 12 | this.isTetrad() && 13 | MajorChord.isMatch(this.getTriad()) && 14 | Interval.between(this.root(), this.fourth()).isMajor6th() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/chords/strategies.ts: -------------------------------------------------------------------------------- 1 | import { AugmentedChord } from './augmentedChord'; 2 | import { DiminishedChord } from './diminishedChord'; 3 | import { DiminishedSeventhChord } from './diminishedSeventhChord'; 4 | import { DominantSeventhChord } from './dominantSeventhChord'; 5 | import { HalfDiminishedSeventhChord } from './halfDiminishedSeventhChord'; 6 | import { MajorChord } from './majorChord'; 7 | import { MajorSeventhChord } from './majorSeventhChord'; 8 | import { SixthChord } from './sixthChord'; 9 | import { MinorChord } from './minorChord'; 10 | import { MinorSeventhChord } from './minorSeventhChord'; 11 | import { SuspendedChord } from './suspendedChord'; 12 | import { SuspendedSecondChord } from './suspendedSecondChord'; 13 | import { MinorSixthChord } from './minorSixthChord'; 14 | import { MinorMajorSeventhChord } from './minorMajorSeventh'; 15 | import { AugmentedSeventhChord } from './augmentedSeventhChord'; 16 | import { AugmentedMajorSeventhChord } from './augmentedMajorSeventhChord'; 17 | 18 | export const strategies = [ 19 | MajorChord, 20 | MinorChord, 21 | SuspendedChord, 22 | SuspendedSecondChord, 23 | AugmentedChord, 24 | DiminishedChord, 25 | SixthChord, 26 | MinorSixthChord, 27 | DominantSeventhChord, 28 | AugmentedSeventhChord, 29 | MajorSeventhChord, 30 | AugmentedMajorSeventhChord, 31 | MinorSeventhChord, 32 | MinorMajorSeventhChord, 33 | HalfDiminishedSeventhChord, 34 | DiminishedSeventhChord, 35 | ]; 36 | -------------------------------------------------------------------------------- /src/chords/suspendedChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | 4 | export class SuspendedChord extends BaseChord { 5 | public getName(): string { 6 | return `${this.root().getName()}sus`; 7 | } 8 | 9 | public isMatch() { 10 | return ( 11 | this.isTriad() && 12 | Interval.between(this.root(), this.second()).isPerfect4th() && 13 | Interval.between(this.root(), this.third()).isPerfect5th() 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/chords/suspendedSecondChord.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { BaseChord } from './baseChord'; 3 | 4 | export class SuspendedSecondChord extends BaseChord { 5 | public getName(): string { 6 | return `${this.root().getName()}sus2`; 7 | } 8 | 9 | public isMatch() { 10 | return ( 11 | this.isTriad() && 12 | Interval.between(this.root(), this.second()).isMajor2nd() && 13 | Interval.between(this.root(), this.third()).isPerfect5th() 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/definedInterval.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from './interval'; 2 | import { Note } from './note'; 3 | 4 | export class DefinedInterval { 5 | private semitones: number; 6 | 7 | constructor(public note1: Note, public note2: Note) { 8 | this.semitones = this.note2.minus(this.note1); 9 | } 10 | 11 | public getSemitones() { 12 | return this.semitones; 13 | } 14 | 15 | public isWithinSemitones(semitones: number) { 16 | return this.semitones <= semitones; 17 | } 18 | 19 | public isNone() { 20 | return this.semitones === Interval.isNone(); 21 | } 22 | 23 | public isMajor2nd() { 24 | return this.semitones === Interval.major2nd(); 25 | } 26 | 27 | public isMajor3rd() { 28 | return this.semitones === Interval.major3rd(); 29 | } 30 | 31 | public isMajor6th() { 32 | return this.semitones === Interval.major6th(); 33 | } 34 | 35 | public isMajor7th() { 36 | return this.semitones === Interval.major7th(); 37 | } 38 | 39 | public isMinor2nd() { 40 | return this.semitones === 1; 41 | } 42 | 43 | public isMinor3rd() { 44 | return this.semitones === 3; 45 | } 46 | 47 | public isMinor6th() { 48 | return this.semitones === 8; 49 | } 50 | 51 | public isMinor7th() { 52 | return this.semitones === 10; 53 | } 54 | 55 | public isPerfect4th() { 56 | return this.semitones === Interval.perfect4th(); 57 | } 58 | 59 | public isPerfect5th() { 60 | return this.semitones === Interval.perfect5th(); 61 | } 62 | 63 | public isAugmented4th() { 64 | return this.semitones === 6; 65 | } 66 | 67 | public isAugmented5th() { 68 | return this.semitones === 8; 69 | } 70 | 71 | public isDiminished4th() { 72 | return this.semitones === 4; 73 | } 74 | 75 | public isDiminished5th() { 76 | return this.semitones === 6; 77 | } 78 | 79 | public isDiminished7th() { 80 | return this.semitones === 9; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './accidental'; 2 | export * from './accidentals'; 3 | export * from './chord'; 4 | export * from './chords'; 5 | export * from './definedInterval'; 6 | export * from './interval'; 7 | export * from './keySignature'; 8 | export * from './keySignatures'; 9 | export * from './name'; 10 | export * from './note'; 11 | export * from './octave'; 12 | export * from './utils'; 13 | -------------------------------------------------------------------------------- /src/interval.ts: -------------------------------------------------------------------------------- 1 | import { DefinedInterval } from './definedInterval'; 2 | import { Note } from './note'; 3 | 4 | export class Interval { 5 | public static between(note1: string | Note, note2: string | Note) { 6 | return new DefinedInterval(this.parse(note1), this.parse(note2)); 7 | } 8 | 9 | public static isNone() { 10 | return 0; 11 | } 12 | 13 | public static major2nd() { 14 | return 2; 15 | } 16 | 17 | public static major3rd() { 18 | return 4; 19 | } 20 | 21 | public static perfect4th() { 22 | return 5; 23 | } 24 | 25 | public static perfect5th() { 26 | return 7; 27 | } 28 | 29 | public static major6th() { 30 | return 9; 31 | } 32 | 33 | public static major7th() { 34 | return 11; 35 | } 36 | 37 | public static octave() { 38 | return 12; 39 | } 40 | 41 | private static parse(note: string | Note) { 42 | if (typeof note === 'string') { 43 | return new Note(note); 44 | } 45 | 46 | return note; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/keySignature.ts: -------------------------------------------------------------------------------- 1 | import { 2 | KeySignatureOfA, 3 | KeySignatureOfAb, 4 | KeySignatureOfB, 5 | KeySignatureOfBb, 6 | KeySignatureOfC, 7 | KeySignatureOfD, 8 | KeySignatureOfDb, 9 | KeySignatureOfE, 10 | KeySignatureOfEb, 11 | KeySignatureOfF, 12 | KeySignatureOfFSharp, 13 | KeySignatureOfG, 14 | KeySignatureOfGb, 15 | } from './keySignatures'; 16 | import { Note } from './note'; 17 | import { Octave } from './octave'; 18 | 19 | type KeySignatureProps = 'C' | 'Db' | 'D' | 'Eb' | 'E' | 'F' | 'F#' | 'Gb' | 'G' | 'Ab' | 'A' | 'Bb' | 'B'; 20 | 21 | export abstract class KeySignature { 22 | public static for(key: KeySignatureProps) { 23 | switch (key) { 24 | case 'C': 25 | return new KeySignatureOfC(); 26 | case 'Db': 27 | return new KeySignatureOfDb(); 28 | case 'D': 29 | return new KeySignatureOfD(); 30 | case 'Eb': 31 | return new KeySignatureOfEb(); 32 | case 'E': 33 | return new KeySignatureOfE(); 34 | case 'F': 35 | return new KeySignatureOfF(); 36 | case 'F#': 37 | return new KeySignatureOfFSharp(); 38 | case 'Gb': 39 | return new KeySignatureOfGb(); 40 | case 'G': 41 | return new KeySignatureOfG(); 42 | case 'Ab': 43 | return new KeySignatureOfAb(); 44 | case 'A': 45 | return new KeySignatureOfA(); 46 | case 'Bb': 47 | return new KeySignatureOfBb(); 48 | case 'B': 49 | return new KeySignatureOfB(); 50 | } 51 | } 52 | 53 | public abstract getNotes(): Note[]; 54 | 55 | public normalize(note: Note) { 56 | for (const keyNote of this.getNotes()) { 57 | for (const octave of Octave.all()) { 58 | const noteWithDifferentOctave = keyNote.setOctave(octave); 59 | 60 | if (noteWithDifferentOctave.matches(note)) { 61 | return noteWithDifferentOctave; 62 | } 63 | } 64 | } 65 | 66 | return note; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/keySignatures/baseKeySignature.ts: -------------------------------------------------------------------------------- 1 | import { BaseChord } from '../chords'; 2 | import { Interval } from '../interval'; 3 | import { Note } from '../note'; 4 | import { Octave } from '../octave'; 5 | 6 | type Degrees = 'tonic' | 'supertonic' | 'mediant' | 'subdominant' | 'dominant' | 'submediant' | 'leading-tone'; 7 | 8 | export abstract class BaseKeySignature { 9 | public abstract getNotes(): Note[]; 10 | 11 | public root() { 12 | return this.getNotes()[0]; 13 | } 14 | 15 | public normalize(note: Note) { 16 | for (const keyNote of this.getNotes()) { 17 | for (const octave of Octave.all()) { 18 | const noteWithDifferentOctave = keyNote.setOctave(octave); 19 | 20 | if (noteWithDifferentOctave.matches(note)) { 21 | return noteWithDifferentOctave; 22 | } 23 | } 24 | } 25 | 26 | return note; 27 | } 28 | 29 | public isInKey(note: Note) { 30 | for (const keyNote of this.getNotes()) { 31 | for (const octave of Octave.all()) { 32 | if (keyNote.setOctave(octave).equals(note)) { 33 | return true; 34 | } 35 | } 36 | } 37 | 38 | return false; 39 | } 40 | 41 | public getDegree(chord: BaseChord | undefined): undefined | Degrees { 42 | if (!chord) { 43 | return undefined; 44 | } 45 | 46 | const semitones = Interval.between(this.root(), chord.root()).getSemitones(); 47 | 48 | switch (semitones) { 49 | case Interval.isNone(): 50 | case Interval.octave(): 51 | return 'tonic'; 52 | case Interval.major2nd(): 53 | return 'supertonic'; 54 | case Interval.major3rd(): 55 | return 'mediant'; 56 | case Interval.perfect4th(): 57 | return 'subdominant'; 58 | case Interval.perfect5th(): 59 | return 'dominant'; 60 | case Interval.major6th(): 61 | return 'submediant'; 62 | case Interval.major7th(): 63 | return 'leading-tone'; 64 | default: 65 | return undefined; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/keySignatures/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keySignatureOfA'; 2 | export * from './keySignatureOfAb'; 3 | export * from './keySignatureOfB'; 4 | export * from './keySignatureOfBb'; 5 | export * from './keySignatureOfC'; 6 | export * from './keySignatureOfD'; 7 | export * from './keySignatureOfDb'; 8 | export * from './keySignatureOfE'; 9 | export * from './keySignatureOfEb'; 10 | export * from './keySignatureOfF'; 11 | export * from './keySignatureOfFSharp'; 12 | export * from './keySignatureOfG'; 13 | export * from './keySignatureOfGb'; 14 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfA.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfA extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('A4'), 8 | new Note('B4'), 9 | new Note('C#5'), 10 | new Note('D5'), 11 | new Note('E5'), 12 | new Note('F#5'), 13 | new Note('G#5'), 14 | new Note('A5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfAb.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfAb extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('Ab4'), 8 | new Note('Bb4'), 9 | new Note('C5'), 10 | new Note('Db5'), 11 | new Note('Eb5'), 12 | new Note('F5'), 13 | new Note('G5'), 14 | new Note('Ab5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfB.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfB extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('B4'), 8 | new Note('C#5'), 9 | new Note('D#5'), 10 | new Note('E5'), 11 | new Note('F#5'), 12 | new Note('G#5'), 13 | new Note('A#5'), 14 | new Note('B5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfBb.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfBb extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('Bb3'), 8 | new Note('C4'), 9 | new Note('D4'), 10 | new Note('Eb4'), 11 | new Note('F4'), 12 | new Note('G4'), 13 | new Note('A4'), 14 | new Note('Bb4'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfC.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfC extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('C4'), 8 | new Note('D4'), 9 | new Note('E4'), 10 | new Note('F4'), 11 | new Note('G4'), 12 | new Note('A4'), 13 | new Note('B4'), 14 | new Note('C5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfD.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfD extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('D4'), 8 | new Note('E4'), 9 | new Note('F#4'), 10 | new Note('G4'), 11 | new Note('A4'), 12 | new Note('B4'), 13 | new Note('C#5'), 14 | new Note('D5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfDb.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfDb extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('Db4'), 8 | new Note('Eb4'), 9 | new Note('F4'), 10 | new Note('Gb4'), 11 | new Note('Ab4'), 12 | new Note('Bb4'), 13 | new Note('C5'), 14 | new Note('Db5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfE.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfE extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('E4'), 8 | new Note('F#4'), 9 | new Note('G#4'), 10 | new Note('A4'), 11 | new Note('B4'), 12 | new Note('C#5'), 13 | new Note('D#5'), 14 | new Note('E5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfEb.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfEb extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('Eb4'), 8 | new Note('F4'), 9 | new Note('G4'), 10 | new Note('Ab4'), 11 | new Note('Bb4'), 12 | new Note('C5'), 13 | new Note('D5'), 14 | new Note('Eb5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfF.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfF extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('F4'), 8 | new Note('G4'), 9 | new Note('A4'), 10 | new Note('Bb4'), 11 | new Note('C5'), 12 | new Note('D5'), 13 | new Note('E5'), 14 | new Note('F5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfFSharp.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfFSharp extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('F#4'), 8 | new Note('G#4'), 9 | new Note('A#4'), 10 | new Note('B4'), 11 | new Note('C#5'), 12 | new Note('D#5'), 13 | new Note('E#5'), 14 | new Note('F#5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfG.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfG extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('G4'), 8 | new Note('A4'), 9 | new Note('B4'), 10 | new Note('C5'), 11 | new Note('D5'), 12 | new Note('E5'), 13 | new Note('F#5'), 14 | new Note('G5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/keySignatures/keySignatureOfGb.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '../note'; 2 | import { BaseKeySignature } from './baseKeySignature'; 3 | 4 | export class KeySignatureOfGb extends BaseKeySignature { 5 | public getNotes(): Note[] { 6 | return [ 7 | new Note('Gb4'), 8 | new Note('Ab4'), 9 | new Note('Bb4'), 10 | new Note('Cb5'), 11 | new Note('Db5'), 12 | new Note('Eb5'), 13 | new Note('F5'), 14 | new Note('Gb5'), 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/name.ts: -------------------------------------------------------------------------------- 1 | import { Note } from './note'; 2 | 3 | type NameProps = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G'; 4 | 5 | export class Name { 6 | static values = ['C', 'D', 'E', 'F', 'G', 'A', 'B']; 7 | static valuesWithFlats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; 8 | 9 | static fromMidi(value: number) { 10 | return this.valuesWithFlats[(value - new Note('C1').getMidiValue()) % 12]; 11 | } 12 | 13 | value: NameProps; 14 | 15 | constructor(value: string) { 16 | if (!this.isValid(value)) { 17 | this.value = 'C'; 18 | } else { 19 | this.value = value as NameProps; 20 | } 21 | } 22 | 23 | public isC() { 24 | return this.value === 'C'; 25 | } 26 | 27 | public isD() { 28 | return this.value === 'D'; 29 | } 30 | 31 | public isE() { 32 | return this.value === 'E'; 33 | } 34 | 35 | public isF() { 36 | return this.value === 'F'; 37 | } 38 | 39 | public isG() { 40 | return this.value === 'G'; 41 | } 42 | 43 | public isA() { 44 | return this.value === 'A'; 45 | } 46 | 47 | public isB() { 48 | return this.value === 'B'; 49 | } 50 | 51 | public getKeyIndex() { 52 | switch (this.value) { 53 | case 'C': 54 | return 0; 55 | case 'D': 56 | return 2; 57 | case 'E': 58 | return 4; 59 | case 'F': 60 | return 5; 61 | case 'G': 62 | return 7; 63 | case 'A': 64 | return 9; 65 | case 'B': 66 | return 11; 67 | } 68 | } 69 | 70 | private isValid(value: string) { 71 | return Name.values.includes(value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/note.ts: -------------------------------------------------------------------------------- 1 | import { Accidental } from './accidental'; 2 | import { IAccidental } from './accidentals/IAccidental'; 3 | import { Name } from './name'; 4 | import { Octave, OctaveProps } from './octave'; 5 | 6 | export class Note { 7 | public name: Name; 8 | public accidental: IAccidental; 9 | public octave: Octave; 10 | 11 | public static fromMidi(value: number) { 12 | if (value < 21 || value > 108) { 13 | throw new Error('Invalid Midi note'); 14 | } 15 | 16 | if (21 <= value && value <= 23) { 17 | switch (value) { 18 | case 21: 19 | return new Note('A0'); 20 | case 22: 21 | return new Note('Bb0'); 22 | case 23: 23 | return new Note('B0'); 24 | } 25 | } 26 | 27 | return new Note(`${Note.getNameFromMidi(value)}${Octave.fromMidi(value)}`); 28 | } 29 | 30 | private static getNameFromMidi(value: number) { 31 | const notes = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; 32 | return notes[(value - new Note('C1').getMidiValue()) % notes.length]; 33 | } 34 | 35 | constructor(value: string) { 36 | const result = this.parse(value); 37 | if (!result) { 38 | throw new Error('Invalid note'); 39 | } 40 | 41 | const [, name, accidental, octave] = result; 42 | this.name = new Name(name); 43 | this.accidental = Accidental.for(accidental); 44 | this.octave = new Octave(octave); 45 | } 46 | 47 | public getName() { 48 | return `${this.name.value}${this.accidental.getValue()}`; 49 | } 50 | 51 | public getScientificName() { 52 | return `${this.getName()}${this.getOctave()}`; 53 | } 54 | 55 | public getOctave() { 56 | return this.octave.value; 57 | } 58 | 59 | public setOctave(octave: OctaveProps) { 60 | try { 61 | return new Note(`${this.getName()}${octave}`); 62 | } catch { 63 | return this; 64 | } 65 | } 66 | 67 | public getFrequency() { 68 | return parseFloat((Math.pow(2, (this.getKeyNumber() - 49) / 12) * 440).toFixed(5)); 69 | } 70 | 71 | public getKeyNumber() { 72 | if (this.octave.isSubContra()) { 73 | switch (this.getName()) { 74 | case 'A': 75 | case 'Bbb': 76 | return 1; 77 | case 'A#': 78 | case 'Bb': 79 | return 2; 80 | case 'B': 81 | case 'A𝄪': 82 | return 3; 83 | case 'B#': 84 | return 4; 85 | } 86 | } 87 | 88 | return 4 + (this.getOctave() - 1) * 12 + this.getKeyIndex(); 89 | } 90 | 91 | public getKeyIndex() { 92 | return this.name.getKeyIndex() + this.accidental.getKeyIndex(); 93 | } 94 | 95 | public getMidiValue() { 96 | if (this.octave.isSubContra()) { 97 | switch (this.getName()) { 98 | case 'A': 99 | case 'Bbb': 100 | return 21; 101 | case 'A#': 102 | case 'Bb': 103 | return 22; 104 | case 'B': 105 | case 'A𝄪': 106 | return 23; 107 | case 'B#': 108 | return 24; 109 | } 110 | } 111 | 112 | return 24 + (this.getOctave() - 1) * 12 + this.getKeyIndex(); 113 | } 114 | 115 | // TODO: What does it mean to minus two notes? This needs to be more descriptive 116 | public minus(note: Note) { 117 | if (this.getKeyNumber() < note.getKeyNumber()) { 118 | return this.getKeyNumber() - note.getKeyNumber() + 12; 119 | } 120 | 121 | return this.getKeyNumber() - note.getKeyNumber(); 122 | } 123 | 124 | public equals(note: Note) { 125 | return this.getScientificName() === note.getScientificName(); 126 | } 127 | 128 | public matches(note: Note) { 129 | return this.getMidiValue() === note.getMidiValue(); 130 | } 131 | 132 | public isLessThan(note: Note) { 133 | return this.getMidiValue() < note.getMidiValue(); 134 | } 135 | 136 | public isGreaterThan(note: Note) { 137 | return this.getMidiValue() > note.getMidiValue(); 138 | } 139 | 140 | private parse(value: string) { 141 | const result = /([A-G]){1}(𝄫|b|#|𝄪)?(\d)?/g.exec(value); 142 | if (!this.isValid(result)) { 143 | return null; 144 | } 145 | 146 | return result; 147 | } 148 | 149 | private isValid(value: null | RegExpExecArray) { 150 | if (!value) { 151 | return false; 152 | } 153 | 154 | const [, _name, , _octave] = value; 155 | const name = new Name(_name); 156 | const octave = new Octave(_octave); 157 | 158 | if (octave.isFiveLine() && !name.isC()) { 159 | return false; 160 | } 161 | 162 | if (octave.isSubContra() && !name.isA() && !name.isB()) { 163 | return false; 164 | } 165 | 166 | return true; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/octave.ts: -------------------------------------------------------------------------------- 1 | import { Note } from './note'; 2 | 3 | type MiddleOctave = 4; 4 | export type OctaveProps = 0 | 1 | 2 | 3 | MiddleOctave | 5 | 6 | 7 | 8 | 9; 5 | 6 | export class Octave { 7 | static MIN = 0; 8 | static MAX = 8; 9 | static MIDDLE = 4; 10 | 11 | public static middle() { 12 | return new Octave(this.MIDDLE); 13 | } 14 | 15 | public static fromMidi(value: number) { 16 | if (21 <= value && value <= 23) { 17 | return new Octave(0); 18 | } 19 | 20 | return new Octave(Math.ceil((value - new Note('C1').getMidiValue() + 1) / 12)); 21 | } 22 | 23 | public static all() { 24 | return [...Array(this.MAX - this.MIN + 1).keys()] as OctaveProps[]; 25 | } 26 | 27 | value: OctaveProps; 28 | 29 | constructor(value?: string | number) { 30 | this.value = this.parse(value); 31 | } 32 | 33 | public isSubContra() { 34 | return this.value === 0; 35 | } 36 | 37 | public isFiveLine() { 38 | return this.value === 8; 39 | } 40 | 41 | public toString() { 42 | return this.value.toString(); 43 | } 44 | 45 | public equals(octave: Octave) { 46 | return this.value === octave.value; 47 | } 48 | 49 | private parse(value?: string | number): OctaveProps { 50 | let result: number = Octave.MIDDLE; 51 | 52 | if (value === undefined) { 53 | return result as OctaveProps; 54 | } 55 | 56 | if (typeof value === 'string') { 57 | result = parseInt(value, 10); 58 | } else { 59 | result = value; 60 | } 61 | 62 | if (!this.isValid(result)) { 63 | result = Octave.MIDDLE; 64 | } 65 | 66 | return result as OctaveProps; 67 | } 68 | 69 | private isValid(value: number) { 70 | return Octave.MIN <= value && value <= Octave.MAX; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './inputSanitization'; 2 | export * from './invertArray'; 3 | -------------------------------------------------------------------------------- /src/utils/inputSanitization.ts: -------------------------------------------------------------------------------- 1 | import { Interval } from '../interval'; 2 | import { Note } from '../note'; 3 | 4 | type Group = Note[]; 5 | 6 | export class InputSanitization { 7 | private notes: Note[]; 8 | private groups: Group[] = []; 9 | 10 | public constructor(private input: string | Note[]) { 11 | this.notes = []; 12 | } 13 | 14 | public call(): Group[] { 15 | return this.parse().removeDuplicates().removeOctaveDuplicates().sortByMidiValue().group().getValidGroups(); 16 | } 17 | 18 | private parse(): InputSanitization { 19 | if (Array.isArray(this.input)) { 20 | if (!this.input.length) { 21 | throw new Error('Input must not be an empty array'); 22 | } 23 | 24 | this.notes = this.input; 25 | } else { 26 | if (!this.input) { 27 | throw new Error('Input must not be an empty string'); 28 | } 29 | 30 | this.notes = this.input.split(' ').map((note) => new Note(note)); 31 | } 32 | 33 | return this; 34 | } 35 | 36 | private removeDuplicates() { 37 | this.notes = Object.values( 38 | this.notes.reduce<{ [midiValue: number]: Note }>((map, note) => { 39 | if (map[note.getMidiValue()] !== undefined) { 40 | return map; 41 | } 42 | 43 | return { ...map, [note.getMidiValue()]: note }; 44 | }, {}), 45 | ); 46 | 47 | return this; 48 | } 49 | 50 | private removeOctaveDuplicates() { 51 | this.notes = Object.values( 52 | this.notes.reduce<{ [keyIndex: number]: Note }>((map, note) => { 53 | const existingNote = map[note.getKeyIndex()]; 54 | if (existingNote !== undefined && note.isGreaterThan(existingNote)) { 55 | return map; 56 | } 57 | 58 | return { ...map, [note.getKeyIndex()]: note }; 59 | }, {}), 60 | ); 61 | 62 | return this; 63 | } 64 | 65 | private sortByMidiValue() { 66 | this.notes.sort((a, b) => a.getMidiValue() - b.getMidiValue()); 67 | 68 | return this; 69 | } 70 | 71 | private group() { 72 | this.groups = [[this.notes[0]]]; 73 | 74 | this.notes.slice(1).forEach((note) => { 75 | if (this.isInCurrentGroup(note)) { 76 | this.addToGroup(note); 77 | } else { 78 | this.createNewGroup(note); 79 | } 80 | }); 81 | 82 | return this; 83 | } 84 | 85 | private createNewGroup(note: Note) { 86 | this.groups.push([note]); 87 | } 88 | 89 | private addToGroup(note: Note) { 90 | this.currentGroup().push(note); 91 | } 92 | 93 | private isInCurrentGroup(note: Note) { 94 | return Interval.between(this.currentNote(), note).isWithinSemitones(5); 95 | } 96 | 97 | private currentNote() { 98 | return this.currentGroup()[this.currentGroup().length - 1]; 99 | } 100 | 101 | private currentGroup() { 102 | return this.groups[this.groups.length - 1]; 103 | } 104 | 105 | private getValidGroups() { 106 | return this.groups.filter((g) => g.length >= 3); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/utils/invertArray.ts: -------------------------------------------------------------------------------- 1 | export function invertArray(array: T[], index: number): T[] { 2 | const res = []; 3 | let i = 0; 4 | 5 | while (i < array.length) { 6 | res.push(array[(index + i) % array.length]); 7 | i++; 8 | } 9 | 10 | return res; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./lib", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"] 3 | } 4 | --------------------------------------------------------------------------------