From a3b20309ea6e81b88de6d9a74f580b121c9c1f5a Mon Sep 17 00:00:00 2001 From: Kilian McMahon Date: Mon, 6 Nov 2023 22:05:15 +0100 Subject: [PATCH 1/2] feat: add support for a bunch of extensions --- .../chords/chordInfoToBaseIntervals.ts | 48 +++++++++++++++++++ src/public/getChord/createAddNotes.ts | 3 ++ src/public/getChord/getChord.test.ts | 24 ++++++++++ src/readers/chords/readAddInfo.ts | 9 ++++ src/readers/chords/readChord.test.ts | 19 ++++++-- src/readers/chords/readChordQuality.ts | 13 +++-- 6 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/converters/chords/chordInfoToBaseIntervals.ts b/src/converters/chords/chordInfoToBaseIntervals.ts index aea1360..810e79c 100644 --- a/src/converters/chords/chordInfoToBaseIntervals.ts +++ b/src/converters/chords/chordInfoToBaseIntervals.ts @@ -20,11 +20,38 @@ const seventhMap: Record = { 'half-diminished': ['m7'], }; +const ninthMap: Record = { + major: ['M7', 'M9'], + dominant: ['m7', 'M9'], + minor: ['m7', 'M9'], +}; + +const eleventhMap: Record = { + major: ['M7', 'M9', 'P11'], + dominant: ['m7', 'M9', 'P11'], + minor: ['m7', 'M9', 'P11'], +}; + +const thirteenthMap: Record = { + major: ['M7', 'M9', 'M13'], + dominant: ['m7', 'M9', 'P11', 'M13'], + minor: ['m7', 'M9', 'M13'], +}; + export const chordInfoToBaseIntervals = ( chordInfo: ChordInfo ): IntervalShorthand[] => { const { type, quality } = chordInfo; + // second handled in createAddNotes as they're aliases + + if (type === 'fourth') return ['P4', 'm7', 'm10']; + if (type === 'fifth') return ['P5', 'P8']; + + if (type === 'sixth') { + return [...qualityMap[quality === undefined ? 'dominant' : quality], 'M6']; + } + if (type === 'seventh') { return [ ...qualityMap[quality === undefined ? 'dominant' : quality], @@ -32,6 +59,27 @@ export const chordInfoToBaseIntervals = ( ]; } + if (type === 'ninth') { + return [ + ...qualityMap[quality === undefined ? 'dominant' : quality], + ...ninthMap[quality === undefined ? 'dominant' : quality], + ]; + } + + if (type === 'eleventh') { + return [ + ...qualityMap[quality === undefined ? 'dominant' : quality], + ...eleventhMap[quality === undefined ? 'dominant' : quality], + ]; + } + + if (type === 'thirteenth') { + return [ + ...qualityMap[quality === undefined ? 'dominant' : quality], + ...thirteenthMap[quality === undefined ? 'dominant' : quality], + ]; + } + if (quality === 'major') return qualityMap[quality]; if (quality === 'minor') return qualityMap[quality]; if (quality === 'diminished') return qualityMap[quality]; diff --git a/src/public/getChord/createAddNotes.ts b/src/public/getChord/createAddNotes.ts index 5e389ee..a173091 100644 --- a/src/public/getChord/createAddNotes.ts +++ b/src/public/getChord/createAddNotes.ts @@ -3,6 +3,9 @@ import { ChordInfo } from '../../readers/chords/readChord.js'; import { transposeNote } from '../transposeNote.js'; export const createAddNotes = (chord: ChordInfo) => { + if (chord.type === 'second') { + return transposeNote(chord.chordRoot, 'M2'); + } if (!chord.isAddChord) return undefined; const intervalSymbol = /(4|11)/.test(String(chord.addDegree)) ? 'P' : 'M'; diff --git a/src/public/getChord/getChord.test.ts b/src/public/getChord/getChord.test.ts index 700781d..f90c8ba 100644 --- a/src/public/getChord/getChord.test.ts +++ b/src/public/getChord/getChord.test.ts @@ -191,6 +191,30 @@ describe('chord', () => { }); }); + describe.only('non-seventh extended chords', () => { + const addChords = [ + { chordName: 'C2', notes: ['C', 'D', 'E', 'G'] }, + { chordName: 'C4', notes: ['C', 'F', 'Bb', 'Eb'] }, + { chordName: 'C5', notes: ['C', 'G', 'C'] }, + { chordName: 'C6', notes: ['C', 'E', 'G', 'A'] }, + { chordName: 'Cm6', notes: ['C', 'Eb', 'G', 'A'] }, + { chordName: 'C6/9', notes: ['C', 'E', 'G', 'A', 'D'] }, + { chordName: 'C9', notes: ['C', 'E', 'G', 'Bb', 'D'] }, + { chordName: 'Cm9', notes: ['C', 'Eb', 'G', 'Bb', 'D'] }, + { chordName: 'Cmaj9', notes: ['C', 'E', 'G', 'B', 'D'] }, + { chordName: 'C11', notes: ['C', 'E', 'G', 'Bb', 'D', 'F'] }, + { chordName: 'Cm11', notes: ['C', 'Eb', 'G', 'Bb', 'D', 'F'] }, + { chordName: 'C13', notes: ['C', 'E', 'G', 'Bb', 'D', 'F', 'A'] }, + { chordName: 'Cm13', notes: ['C', 'Eb', 'G', 'Bb', 'D', 'A'] }, + { chordName: 'Cmaj13', notes: ['C', 'E', 'G', 'B', 'D', 'A'] }, + ]; + it.each(addChords)('$chordName', ({ chordName, notes }) => { + expect( + getChord(chordName)?.notes?.map((note) => note?.name) + ).toStrictEqual(notes); + }); + }); + describe('add chords', () => { const addChords = [ { chordName: 'Cadd2', notes: ['C', 'D', 'E', 'G'] }, diff --git a/src/readers/chords/readAddInfo.ts b/src/readers/chords/readAddInfo.ts index 3de117b..364a5c9 100644 --- a/src/readers/chords/readAddInfo.ts +++ b/src/readers/chords/readAddInfo.ts @@ -6,6 +6,15 @@ export const readAddInfo = ( const regex = /add(2|4|9|11|13)/; const match = input.match(regex)?.slice(1, 2).at(0); + const sixNineRegex = /6\/9/; + + if (sixNineRegex.test(input)) { + return { + isAddChord: true, + addDegree: 9, + }; + } + if (match === undefined) { return { isAddChord: false, diff --git a/src/readers/chords/readChord.test.ts b/src/readers/chords/readChord.test.ts index 3c2de85..34c9993 100644 --- a/src/readers/chords/readChord.test.ts +++ b/src/readers/chords/readChord.test.ts @@ -117,13 +117,26 @@ describe('readChord', () => { }); }); + it('six-nine chords', () => { + expect(readChord('B6/9')).toStrictEqual({ + ...empty, + input: 'B6/9', + chordRoot: 'B', + rootNote: 'B', + quality: 'major', + type: 'sixth', + isAddChord: true, + addDegree: 9, + }); + }); + it('ninth chords', () => { expect(readChord('C9')).toStrictEqual({ ...empty, input: 'C9', chordRoot: 'C', rootNote: 'C', - quality: 'major', + quality: 'dominant', type: 'ninth', }); }); @@ -134,7 +147,7 @@ describe('readChord', () => { input: 'C11', chordRoot: 'C', rootNote: 'C', - quality: 'major', + quality: 'dominant', type: 'eleventh', }); }); @@ -145,7 +158,7 @@ describe('readChord', () => { input: 'C13', chordRoot: 'C', rootNote: 'C', - quality: 'major', + quality: 'dominant', type: 'thirteenth', }); }); diff --git a/src/readers/chords/readChordQuality.ts b/src/readers/chords/readChordQuality.ts index 387ef3b..193432c 100644 --- a/src/readers/chords/readChordQuality.ts +++ b/src/readers/chords/readChordQuality.ts @@ -17,12 +17,17 @@ export const readChordQuality = ( const regex = new RegExp(`${noteRegex.source}(${chordQualityRegex.source})?`); const quality = input.match(regex)?.slice(3, 4)[0]; - if (quality === undefined && type === 'seventh') return 'dominant'; + const hasQuality = quality !== undefined; + const hasType = type !== undefined; if ( - quality === undefined || - majorChordIdentifiers.some((i) => quality === i) - ) { + !hasQuality && + hasType && + ['seventh', 'ninth', 'eleventh', 'thirteenth'].includes(type) + ) + return 'dominant'; + + if (!hasQuality || majorChordIdentifiers.some((i) => quality === i)) { return 'major'; } From 001d49ac4361df24fd23e5d383a3f3c99a6d3d01 Mon Sep 17 00:00:00 2001 From: Kilian McMahon Date: Mon, 6 Nov 2023 22:08:40 +0100 Subject: [PATCH 2/2] chore: add changeset --- .changeset/shaggy-birds-argue.md | 5 +++++ src/public/getChord/getChord.test.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/shaggy-birds-argue.md diff --git a/.changeset/shaggy-birds-argue.md b/.changeset/shaggy-birds-argue.md new file mode 100644 index 0000000..0123fd3 --- /dev/null +++ b/.changeset/shaggy-birds-argue.md @@ -0,0 +1,5 @@ +--- +"@kilmc/music-fns": minor +--- + +Add support for 2nd, 4th, 5th, 6th, 9th, 11th and 13th chords diff --git a/src/public/getChord/getChord.test.ts b/src/public/getChord/getChord.test.ts index f90c8ba..d2d5fa6 100644 --- a/src/public/getChord/getChord.test.ts +++ b/src/public/getChord/getChord.test.ts @@ -191,7 +191,7 @@ describe('chord', () => { }); }); - describe.only('non-seventh extended chords', () => { + describe('non-seventh extended chords', () => { const addChords = [ { chordName: 'C2', notes: ['C', 'D', 'E', 'G'] }, { chordName: 'C4', notes: ['C', 'F', 'Bb', 'Eb'] },