From 97fad34d40853885aa8d8c7edd14af9cec0ef058 Mon Sep 17 00:00:00 2001 From: Kim Jeong-won Date: Mon, 26 Feb 2024 23:38:20 +0900 Subject: [PATCH 1/4] feat: find chord from pitches, and display using chord notation --- src/lib/notation/ChordNotation.svelte | 32 +- src/lib/notation/PitchNotation.svelte | 9 +- .../chord/ChordExtensionNotation.svelte | 17 + .../chord/ChordQualityNotation.svelte | 11 + .../notation/chord/ChordRootNotation.svelte | 9 + .../chord/ChordTensionNotation .svelte | 15 + src/lib/notation/chord/chord-map.ts | 65 +++ src/routes/tools/chord-finder/+page.svelte | 24 + src/routes/tools/chord-finder/Board.svelte | 46 ++ src/utils/music/chords.ts | 469 ++++++++++++++++-- src/utils/music/font.ts | 466 +++++++++++++++++ test/music/chords.test.ts | 7 + 12 files changed, 1113 insertions(+), 57 deletions(-) create mode 100644 src/lib/notation/chord/ChordExtensionNotation.svelte create mode 100644 src/lib/notation/chord/ChordQualityNotation.svelte create mode 100644 src/lib/notation/chord/ChordRootNotation.svelte create mode 100644 src/lib/notation/chord/ChordTensionNotation .svelte create mode 100644 src/lib/notation/chord/chord-map.ts create mode 100644 src/routes/tools/chord-finder/+page.svelte create mode 100644 src/routes/tools/chord-finder/Board.svelte create mode 100644 src/utils/music/font.ts create mode 100644 test/music/chords.test.ts diff --git a/src/lib/notation/ChordNotation.svelte b/src/lib/notation/ChordNotation.svelte index 1ce6094..8825e4d 100644 --- a/src/lib/notation/ChordNotation.svelte +++ b/src/lib/notation/ChordNotation.svelte @@ -1,12 +1,34 @@ - - {chordNotations.default.short} +{#if !needsToSwitch}{/if}{#if needsToSwitch}{/if}{#if bass !== undefined && bass !== root}{stringifyFinaleJazzChordSigns([ + '/' + ])}{/if} diff --git a/src/lib/notation/PitchNotation.svelte b/src/lib/notation/PitchNotation.svelte index 6cb43b9..97d011b 100644 --- a/src/lib/notation/PitchNotation.svelte +++ b/src/lib/notation/PitchNotation.svelte @@ -1,11 +1,12 @@ -{chordNotations.default.short}{octave} +{chordNotation}{octave} diff --git a/src/lib/notation/chord/ChordExtensionNotation.svelte b/src/lib/notation/chord/ChordExtensionNotation.svelte new file mode 100644 index 0000000..36a0a8a --- /dev/null +++ b/src/lib/notation/chord/ChordExtensionNotation.svelte @@ -0,0 +1,17 @@ + + +{#if extension !== undefined && extension !== null}{#if extension === '7, maj7'}{stringifyFinaleJazzChordSigns( + ['7'] + )},{stringifyFinaleJazzChordSigns([ + 'Major', + '7' + ])}{:else if extension === 'b6'}{stringifyFinaleJazzChordSigns(['Flat'])}{stringifyFinaleJazzChordSigns(['6'])}{:else}{stringifyFinaleJazzChordSigns( + chordExtensionToFinaleJazzChordSignMap[extension] + )}{/if}{/if} diff --git a/src/lib/notation/chord/ChordQualityNotation.svelte b/src/lib/notation/chord/ChordQualityNotation.svelte new file mode 100644 index 0000000..dc496d6 --- /dev/null +++ b/src/lib/notation/chord/ChordQualityNotation.svelte @@ -0,0 +1,11 @@ + + +{#if quality !== undefined && quality !== 'major'}{stringifyFinaleJazzChordSigns([ + chordQualityToFinaleJazzChordSignMap[quality] + ])}{/if} diff --git a/src/lib/notation/chord/ChordRootNotation.svelte b/src/lib/notation/chord/ChordRootNotation.svelte new file mode 100644 index 0000000..f98226f --- /dev/null +++ b/src/lib/notation/chord/ChordRootNotation.svelte @@ -0,0 +1,9 @@ + + +{stringifyFinaleJazzChordSigns(chordRootToFinaleJazzChordSignMap[root])} diff --git a/src/lib/notation/chord/ChordTensionNotation .svelte b/src/lib/notation/chord/ChordTensionNotation .svelte new file mode 100644 index 0000000..f16fc34 --- /dev/null +++ b/src/lib/notation/chord/ChordTensionNotation .svelte @@ -0,0 +1,15 @@ + + +{#if tensions.length > 0}{stringifyFinaleJazzChordSigns(['('])}{#each tensions as tension, idx}{#if idx !== 0}{','}{/if}{@const flat = tension === 13 || tension === 20}{@const sharp = + tension === 15 || tension === 18}{#if flat}{stringifyFinaleJazzChordSigns(['Flat'])}{/if}{#if sharp}{stringifyFinaleJazzChordSigns(['Sharp'])}{/if}{stringifyFinaleJazzChordSigns( + chordTensionToFinaleJazzChordSignMap[tension] + )}{/each}{stringifyFinaleJazzChordSigns([')'])}{/if} diff --git a/src/lib/notation/chord/chord-map.ts b/src/lib/notation/chord/chord-map.ts new file mode 100644 index 0000000..bdae4c5 --- /dev/null +++ b/src/lib/notation/chord/chord-map.ts @@ -0,0 +1,65 @@ +import type { ChordRoot, ChordExtension, ChordQuality } from '$/utils/music/chords'; +import type { finaleJazzChordCharacterMap } from '$/utils/music/font'; + +export const chordRootToFinaleJazzChordSignMap: Record< + ChordRoot, + (keyof typeof finaleJazzChordCharacterMap)[] +> = { + C: ['C'], + 'C#': ['C', 'Sharp'], + Db: ['D', 'Flat'], + D: ['D'], + 'D#': ['D', 'Sharp'], + Eb: ['E', 'Flat'], + E: ['E'], + F: ['F'], + 'F#': ['F', 'Sharp'], + Gb: ['G', 'Flat'], + G: ['G'], + 'G#': ['G', 'Sharp'], + Ab: ['A', 'Flat'], + A: ['A'], + 'A#': ['A', 'Sharp'], + Bb: ['B', 'Flat'], + B: ['B'] +}; + +export const chordExtensionToFinaleJazzChordSignMap: Record< + ChordExtension, + (keyof typeof finaleJazzChordCharacterMap)[] +> = { + '5': ['5'], + '6': ['6'], + '6/9': ['6/9'], + dim7: ['7'], + maj7: ['Major', '7'], + '7, maj7': ['7', 'Major', '7'], + '7': ['7'], + b6: ['Flat', '6'] +}; + +export const chordQualityToFinaleJazzChordSignMap: Record< + ChordQuality, + keyof typeof finaleJazzChordCharacterMap +> = { + major: 'Major', + minor: 'Minor', + aug: 'Augmented', + dim: 'Diminished', + 'half-dim': 'HalfDiminished', + sus2: 'SuspendedSecond', + sus4: 'SuspendedFourth' +}; + +export const chordTensionToFinaleJazzChordSignMap: Record< + number, + (keyof typeof finaleJazzChordCharacterMap)[] +> = { + 13: ['9'], + 14: ['9'], + 15: ['9'], + 17: ['11'], + 18: ['11'], + 20: ['13'], + 21: ['13'] +}; diff --git a/src/routes/tools/chord-finder/+page.svelte b/src/routes/tools/chord-finder/+page.svelte new file mode 100644 index 0000000..5cc6801 --- /dev/null +++ b/src/routes/tools/chord-finder/+page.svelte @@ -0,0 +1,24 @@ + + +
+ { + updateFingerPosition({ fret, line }); + }} + > + +
diff --git a/src/routes/tools/chord-finder/Board.svelte b/src/routes/tools/chord-finder/Board.svelte new file mode 100644 index 0000000..d149065 --- /dev/null +++ b/src/routes/tools/chord-finder/Board.svelte @@ -0,0 +1,46 @@ + + +{#if chords != undefined} + {#each chords.chords as chord} +
+ +
+ {/each} +{/if} +

+ {#each pitches as pitch} + + {/each} + + {stringifyFinaleJazzChordSigns(['MinorSixth'])} + {String.fromCodePoint(225 + 29)} + +

+{#if !isFingerboardHidden} + +{/if} diff --git a/src/utils/music/chords.ts b/src/utils/music/chords.ts index da29624..c90c399 100644 --- a/src/utils/music/chords.ts +++ b/src/utils/music/chords.ts @@ -1,4 +1,9 @@ -import type { PitchNote } from './pitch'; +import { numberingPitch, type Pitch, type PitchNote } from './pitch'; + +export type ChordRoot = PitchNote; +export type ChordBass = PitchNote; +export type ChordQuality = 'major' | 'minor' | 'sus2' | 'sus4' | 'dim' | 'aug' | 'half-dim'; +export type ChordExtension = '5' | 'b6' | '6' | 'dim7' | '7' | 'maj7' | '7, maj7' | '6/9'; export type ChordName = | 'dominantSeventh' @@ -8,55 +13,423 @@ export type ChordName = | 'diminishedSeventh' | 'none'; -export type ChordRoot = PitchNote; +export function identifyChordsFromPitches(pitches: Pitch[]) { + if (pitches.length === 0) { + return null; + } + const sortedPitchNumber = pitches.map((pitch) => numberingPitch(pitch)).toSorted(); + const bass = sortedPitchNumber[0]; + const bassNumber = ((bass % 12) + 12) % 12; + + const identifiedChords = []; + + const numberedNotes = [ + ...new Set(pitches.map((pitch) => ((numberingPitch(pitch) % 12) + 12) % 12)) + ].toSorted(); + console.log(numberedNotes); + for (const root of numberedNotes) { + const intervals = new Set( + numberedNotes.map((n) => (n >= root ? n - root : n - root + 12)).filter((n) => n !== 0) + ); + console.log([...intervals]); + identifiedChords.push(identifyChordFromIntervals(bassNumber, root, intervals)); + } + return { chords: identifiedChords, bass: bass }; +} + +function identifyChordFromIntervals(bass: number, root: number, intervals: Set) { + const bassInterval = bass >= root ? bass - root : bass - root + 12; + /** + * chord tones + * - third: only one, else will be tensions + * - fifth: only one, else will be tensions + * - seventh: only one (but might be duplicated in worst case), + * 6th can be tensions but 7th cannot. + * - tensions: others + */ + let third: null | 2 | 3 | 4 | 5 = null; + let fifth: null | 6 | 7 | 8 = null; + let seventh: null | 8 | 9 | 10 | 11 | -1 = null; + const tensions = new Set<13 | 14 | 15 | 17 | 18 | 20 | 21>(); + + // b2 to b9 + if (intervals.has(1)) { + intervals.delete(1); + tensions.add(13); + } -const chordRootNotationMap: { [key in ChordRoot]: number[] } = { - C: [38], - 'C#': [38, 6], - Db: [39, 4], - D: [39], - 'D#': [39, 6], - Eb: [40, 4], - E: [40], - F: [41], - 'F#': [41, 6], - Gb: [42, 4], - G: [42], - 'G#': [42, 6], - Ab: [36, 4], - A: [36], - 'A#': [36, 6], - Bb: [37, 4], - B: [37] -}; - -const chordNameNotationMap: { - [key in Exclude]: number[][]; -} = { - dominantSeventh: [[26]], - majorSeventh: [[183, 26], [45], [186]], - minorSeventh: [[199, 26], [80, 26], [203]], - halfDiminishedSeventh: [[199, 26, 149, 24], [158], [157], [207], [106], [80, 123]], - diminishedSeventh: [[150], [152]] -}; -export function getChordNotations(chordRoot: ChordRoot, chordName: ChordName) { - const chordRootNotation = String.fromCharCode( - ...chordRootNotationMap[chordRoot].map((code) => code + 29) - ); - const chordNameNotations = - chordName === 'none' - ? [''] - : chordNameNotationMap[chordName].map((chordInCodes) => - String.fromCharCode(...chordInCodes.map((code) => code + 29)) - ); - const chordNotations = chordNameNotations.map( - (chordNameNotation) => `${chordRootNotation}${chordNameNotation}` - ); + // find 3rd + const thirdPriority: (2 | 3 | 4 | 5)[] = [4, 3, 5, 2]; + for (const [i, p] of thirdPriority.entries()) { + if (intervals.has(p)) { + intervals.delete(p); + third = p; + for (let j = i + 1; j < thirdPriority.length; j++) { + const q = thirdPriority[j]; + if (intervals.has(q)) { + intervals.delete(q); + tensions.add((q + 12) as 14 | 15 | 17); + } + } + break; + } + } + + // now we found 3rd! it's very critical to determine its quality + if (third !== bassInterval) { + console.log(third, bassInterval); + intervals.delete(bassInterval); + intervals.delete(bassInterval + 12); + } + + // find exact 5th + if (intervals.has(7)) { + intervals.delete(7); + fifth = 7; + if (intervals.has(6)) { + intervals.delete(6); + tensions.add(18); + } + if (intervals.has(8)) { + intervals.delete(8); + tensions.add(20); + } + } + + // find 7th, excluding 6th + if (intervals.has(10)) { + intervals.delete(10); + seventh = 10; + if (intervals.has(11)) { + intervals.delete(11); + seventh = -1; + } + } else if (intervals.has(11)) { + seventh = 11; + intervals.delete(11); + } + // mark base chord + let quality: ChordQuality = 'major'; + let extension: null | ChordExtension = null; + switch (third) { + case 2: // sus2 + quality = 'sus2'; + switch (seventh) { + case 10: // dominant 7th + extension = '7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case 11: // major 7th + extension = 'maj7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case -1: // sadly duplicated 7th + extension = '7, maj7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + default: // extension: b6, 6(dim7), or empty + if (intervals.has(9)) { + // 6 + intervals.delete(9); + seventh = 9; + extension = '6'; + if (intervals.has(8)) { + // b6 -> b13 + intervals.delete(8); + tensions.add(20); + } + if (tensions.has(14)) { + extension = '6/9'; + tensions.delete(14); + } + } else if (intervals.has(8)) { + // b6 + intervals.delete(8); + seventh = 8; + extension = 'b6'; + } + break; + } + break; + case 3: // minor 3rd + quality = 'minor'; + switch (seventh) { + case 10: // dominant 7th + extension = '7'; + if (intervals.has(6)) { + // b5 -> half-diminished + intervals.delete(6); + quality = 'half-dim'; + fifth = 6; + } + if (intervals.has(8)) { + // #5 -> b13 + intervals.delete(8); + tensions.add(20); + } + break; + case 11: // major 7th + extension = 'maj7'; + if (intervals.has(8)) { + // #5 -> hmm, it's strange m#5 + intervals.delete(8); + fifth = 8; + } + if (intervals.has(6)) { + // b5 -> #11 + intervals.delete(6); + tensions.add(18); + } + break; + case -1: // sadly duplicated 7th + extension = '7, maj7'; + if (intervals.has(6)) { + // b5 -> #11 + intervals.delete(6); + tensions.add(18); + } + if (intervals.has(8)) { + // #5 -> b13 + intervals.delete(8); + tensions.add(20); + } + break; + default: // extension: b6, 6(dim7), or empty + // check diminished first + if (intervals.has(6)) { + // b5 -> diminished + intervals.delete(6); + quality = 'dim'; + if (intervals.has(9)) { + // 6 -> dim7 + intervals.delete(9); + seventh = 9; + extension = 'dim7'; + if (intervals.has(8)) { + // b6 -> b13 + intervals.delete(8); + tensions.add(20); + } + } else if (intervals.has(8)) { + // b6 + intervals.delete(8); + seventh = 8; + extension = 'b6'; + } + } + // else becomes minor + else { + if (intervals.has(9)) { + // 6 + intervals.delete(9); + seventh = 9; + extension = '6'; + if (intervals.has(8)) { + // b6 -> b13 + intervals.delete(8); + tensions.add(20); + } + if (tensions.has(14)) { + extension = '6/9'; + tensions.delete(14); + } + } else if (intervals.has(8)) { + // b6 + intervals.delete(8); + seventh = 8; + extension = 'b6'; + } + } + break; + } + break; + case 4: // major 3rd + quality = 'major'; + switch (seventh) { + case 10: // dominant 7th + extension = '7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case 11: // major 7th + extension = 'maj7'; + if (intervals.has(8)) { + // #5 -> hmm, it's strange m#5 + intervals.delete(8); + fifth = 8; + } + [6, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case -1: // sadly duplicated 7th + extension = '7, maj7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + default: // extension: b6, 6(dim7), or empty + if (intervals.has(9)) { + // 6 + intervals.delete(9); + seventh = 9; + extension = '6'; + if (intervals.has(8)) { + // b6 -> b13 + intervals.delete(8); + tensions.add(20); + } + if (tensions.has(14)) { + extension = '6/9'; + tensions.delete(14); + } + } else if (intervals.has(8)) { + // b6 + intervals.delete(8); + seventh = 8; + extension = 'b6'; + } + break; + } + break; + case 5: // sus4 + quality = 'sus4'; + console.log('seventh:', seventh, fifth); + switch (seventh) { + case 10: // dominant 7th + extension = '7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case 11: // major 7th + extension = 'maj7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case -1: // sadly duplicated 7th + extension = '7, maj7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + default: // extension: b6, 6, or empty + if (intervals.has(9)) { + // 6 + intervals.delete(9); + seventh = 9; + extension = '6'; + if (tensions.has(14)) { + extension = '6/9'; + tensions.delete(14); + } + if (intervals.has(8)) { + intervals.delete(8); + tensions.add(20); + } + } else if (intervals.has(8)) { + // #5 -> b6 + intervals.delete(8); + seventh = 8; + extension = 'b6'; + } + break; + } + break; + default: + switch (seventh) { + case 10: // dominant 7th + extension = '7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case 11: // major 7th + extension = 'maj7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + case -1: // sadly duplicated 7th + extension = '7, maj7'; + [6, 8, 9].forEach((n) => { + if (intervals.has(n)) { + intervals.delete(n); + tensions.add((n + 12) as 18 | 20 | 21); + } + }); + break; + default: // extension: b6, 6, or empty + if (intervals.has(8)) { + // #5 -> augmented + intervals.delete(8); + fifth = 8; + quality = 'aug'; + } else if (intervals.has(9)) { + // 6 + intervals.delete(9); + seventh = 9; + extension = '6'; + } + if (fifth === 7 && intervals.size === 0) { + extension = '5'; + } + break; + } + break; + } return { - chordNotations, - default: { - short: chordNotations[1] ?? chordNotations[0], - long: chordNotations[0] + tones: { + third, + fifth, + seventh, + tensions: [...tensions] + }, + symbols: { + root, + quality, + extension } }; } diff --git a/src/utils/music/font.ts b/src/utils/music/font.ts new file mode 100644 index 0000000..c95c3c5 --- /dev/null +++ b/src/utils/music/font.ts @@ -0,0 +1,466 @@ +// export const finaleJazzCharacterMap = { +// '': 0, +// '': 1, +// '': 2, +// '': 3, +// '': 4, +// '': 5, +// '': 6, +// '': 7, +// '': 8, +// '': 9, +// '': 10, +// '': 11, +// '': 12, +// '': 13, +// '': 14, +// '': 15, +// '': 16, +// '': 17, +// '': 18, +// '': 19, +// '': 20, +// '': 21, +// '': 22, +// '': 23, +// '': 24, +// '': 25, +// '': 26, +// '': 27, +// '': 28, +// '': 29, +// '': 30, +// '': 31, +// '': 32, +// '': 33, +// '': 34, +// '': 35, +// '': 36, +// '': 37, +// '': 38, +// '': 39, +// '': 40, +// '': 41, +// '': 42, +// '': 43, +// '': 44, +// '': 45, +// '': 46, +// '': 47, +// '': 48, +// '': 49, +// '': 50, +// '': 51, +// '': 52, +// '': 53, +// '': 54, +// '': 55, +// '': 56, +// '': 57, +// '': 58, +// '': 59, +// '': 60, +// '': 61, +// '': 62, +// '': 63, +// '': 64, +// '': 65, +// '': 66, +// '': 67, +// '': 68, +// '': 69, +// '': 70, +// '': 71, +// '': 72, +// '': 73, +// '': 74, +// '': 75, +// '': 76, +// '': 77, +// '': 78, +// '': 79, +// '': 80, +// '': 81, +// '': 82, +// '': 83, +// '': 84, +// '': 85, +// '': 86, +// '': 87, +// '': 88, +// '': 89, +// '': 90, +// '': 91, +// '': 92, +// '': 93, +// '': 94, +// '': 95, +// '': 96, +// '': 97, +// '': 98, +// '': 99, +// '': 100, +// '': 101, +// '': 102, +// '': 103, +// '': 104, +// '': 105, +// '': 106, +// '': 107, +// '': 108, +// '': 109, +// '': 110, +// '': 111, +// '': 112, +// '': 113, +// '': 114, +// '': 115, +// '': 116, +// '': 117, +// '': 118, +// '': 119, +// '': 120, +// '': 121, +// '': 122, +// '': 123, +// '': 124, +// '': 125, +// '': 126, +// '': 127, +// '': 128, +// '': 129, +// '': 130, +// '': 131, +// '': 132, +// '': 133, +// '': 134, +// '': 135, +// '': 136, +// '': 137, +// '': 138, +// '': 139, +// '': 140, +// '': 141, +// '': 142, +// '': 143, +// '': 144, +// '': 145, +// '': 146, +// '': 147, +// '': 148, +// '': 149, +// '': 150, +// '': 151, +// '': 152, +// '': 153, +// '': 154, +// '': 155, +// '': 156, +// '': 157, +// '': 158, +// '': 159, +// '': 160, +// '': 161, +// '': 162, +// '': 163, +// '': 164, +// '': 165, +// '': 166, +// '': 167, +// '': 168, +// '': 169, +// '': 170, +// '': 171, +// '': 172, +// '': 173, +// '': 174, +// '': 175, +// '': 176, +// '': 177, +// '': 178, +// '': 179, +// '': 180, +// '': 181, +// '': 182, +// '': 183, +// '': 184, +// '': 185, +// '': 186, +// '': 187, +// '': 188, +// '': 189, +// '': 190, +// '': 191, +// '': 192, +// '': 193, +// '': 194, +// '': 195, +// '': 196, +// '': 197, +// '': 198, +// '': 199, +// '': 200, +// '': 201, +// '': 202, +// '': 203, +// '': 204, +// '': 205, +// '': 206, +// '': 207, +// '': 208, +// '': 209, +// '': 210, +// '': 211, +// '': 212, +// '': 213, +// '': 214, +// '': 215, +// '': 216, +// '': 217, +// '': 218, +// '': 219, +// '': 220, +// '': 221, +// '': 222, +// '': 223, +// '': 224, +// '': 225, +// '': 226, +// '': 227, +// '': 228, +// '': 229, +// '': 230, +// '': 231, +// '': 232, +// '': 233, +// '': 234, +// '': 235, +// '': 236, +// '': 237, +// '': 238, +// '': 239, +// '': 240, +// '': 241, +// '': 242, +// '': 243, +// '': 244, +// '': 245, +// '': 246, +// '': 247, +// '': 248, +// '': 249, +// '': 250, +// '': 251, +// '': 252, +// '': 253, +// '': 254, +// '': 255 +// }; + +export const finaleJazzChordCharacterMap = { + Flat: 4, + Natural: 5, + Sharp: 6, + '(': 11, + ')': 12, + Augmented: 14, + Minor: 16, + '/': 18, + '0': 19, + '1': 20, + '2': 21, + '3': 22, + '4': 23, + '5': 24, + '6': 25, + '7': 26, + '8': 27, + '9': 28, + '11': 29, + '13': 30, + '<': 31, + '>': 33, + Major: 35, + A: 36, + B: 37, + C: 38, + D: 39, + E: 40, + F: 41, + G: 42, + '6/9': 43, + MajorSixth: 44, + MajorSeventh: 45, + MajorNinth: 46, + MajorThirteenth: 47, + 'Major6/9': 48, + MajorSeventhSharpEleven: 49, + MajorNinthSharpEleven: 50, + 'Major6/9SharpEleven': 51, + '6/9SharpEleven': 52, + MajorThirteenthSharpEleven: 53, + MajorSeventhFlatFive: 54, + MajorSeventhSharpFive: 55, + MajorNinthFlatFive: 56, + MajorNinthSharpFive: 57, + Suspended: 58, + SuspendedFourth: 59, + SuspenedFourthFlatNine: 60, + SeventhSuspended: 61, + NinthSuspended: 62, + SeventhSuspendedFourth: 63, + SeventhSuspendedFourthFlatNine: 64, + SeventhSuspendedFourthFlatNineAddThird: 65, + SuspendedSecond: 66, + Lydian: 67, + a: 68, + b: 69, + c: 70, + d: 71, + e: 72, + f: 73, + g: 74, + h: 75, + i: 76, + j: 77, + k: 78, + l: 79, + m: 80, + n: 81, + o: 82, + p: 83, + q: 84, + r: 85, + s: 86, + t: 87, + u: 88, + v: 89, + w: 90, + x: 91, + y: 92, + z: 93, + MinorSixth: 101, + 'Minor6/9': 102, + MinorSeventh: 103, + MinorNinth: 104, + MinorEleventh: 105, + MinorThirteenth: 106, + MinorSeventhFlatFive: 107, + MinorNinthFlatFive: 108, + MinorEleventhFlatFive: 109, + MinorThirteenthSharpEleven: 110, + MinorSeventhFlatNineFlatFive: 111, + MinorEleventhFlatNineFlatFive: 112, + MinorMajorSeventh: 113, + MinorNinthMajorSeventh: 114, + MinorEleventhMajorSeventh: 115, + MinorNinthMajorSeventhSharpEleven: 116, + 'Minor6/9SharpEleven': 117, + MinorAddSecond: 118, + AugmentedSeventhFlatNine: 119, + SeventhFlatNine: 120, + SeventhSharpNine: 121, + SeventhFlatNineFlatThirteen: 122, + SeventhSharpNineFlatThirteen: 123, + SeventhFlatFive: 124, + SeventhSharpEleven: 125, + SeventhSharpFive: 126, + SeventhFlatNineFlatFive: 127, + SeventhSharpNineFlatFive: 128, + SeventhSharpNineFlatNine: 129, + SeventhSharpElevenFlatNine: 130, + SeventhSharpElevenSharpNine: 131, + NinthFlatFive: 132, + NinthSharpEleven: 133, + NinthSharpFive: 134, + ThirteenthFlatFive: 135, + ThirteenthFlatNine: 136, + ThirteenthSharpNine: 137, + ThirteenthFlatNineFlatFive: 138, + ThirteenSharpEleven: 139, + SeventhSharpNineSharpFive: 140, + AugmentedSeventhSharpNine: 141, + AugmentedSeventh: 142, + AugmentedNinth: 143, + AugmentedMajorSeventh: 144, + AugmentedMajorNinth: 145, + AugmentedLong: 146, + SeventhSharpElevenSharpNinth: 147, + DiminishedLong: 150, + DiminishedSeventhLong: 151, + Diminished: 152, + DiminishedSeventh: 153, + DiminishedSeventhFlatNine: 154, + DiminishedSeventhSharpNine: 155, + DiminishedMajorSeventhLong: 156, + DiminishedMajorSeventh: 157, + HalfDiminished: 158, + HalfDiminishedSeventh: 159, + MajorTriad: 161, + MajorTriadFlatFive: 162, + Pedal: 163, + AlteredDominantSeventh: 164, + OmitFifth: 165, + OmitThird: 166, + ParenthesisFlatNine: 168, + ParenthesisMajorSeventh: 170, + ParenthesisFlatFive: 171, + ParenthesisSharpFive: 172, + ParenthesisSharpNine: 174, + ParenthesisSharpEleven: 175, + ParenthesisFlatNineFlatFive: 176, + ParenthesisSharpNineFlatFive: 177, + ParenthesisFlatNineFlatThirteen: 178, + ParenthesisSharpNineFlatThirteen: 179, + ParenthesisSharpNineFlatNine: 180, + ParenthesisSharpElevenFlatNine: 181, + ParenthesisSharpElevenSharpNine: 182, + ParenthesisMajorSeventhSharpEleven: 183, + MajorLong: 184, + MajorShort: 185, + MajorSixthShort: 186, + MajorSeventhShort: 187, + MajorNinthShort: 188, + MajorThirteenthShort: 189, + 'Major6/9Short': 190, + MajorSeventhSharpElevenShort: 191, + MajorNinthSharpElevenShort: 192, + 'Major6/9SharpElevenShort': 193, + MajorThirteenthSharpElevenShort: 194, + MajorSeventhFlatFiveShort: 195, + MajorSeventhSharpFiveShort: 196, + MajorNinthFlatFiveShort: 197, + MajorNinthSharpFiveShort: 198, + MinorLong: 200, + MinorShort: 201, + MinorSixthShort: 202, + 'Minort6/9Short': 203, + MinorSeventhShort: 204, + MinorNinthShort: 205, + MinorEleventhShort: 206, + MinorThirteenthShort: 207, + MinorSeventhFlatFiveShort: 208, + MinorNinthFlatFiveShort: 209, + MinorEleventhFlatFiveShort: 210, + MinorThirteenthSharpElevenShort: 211, + MinorSeventhFlatNineFlatFiveShort: 212, + MinorEleventhFlatNineFlatFiveShort: 213, + MinorMajorSeventhShort: 214, + MinorNinthMajorSeventhShort: 215, + MinorEleventhMajorSeventhShort: 216, + MinorNinthMajorSeventhSharpElevenShort: 217, + 'Minor6/9SharpElevenShort': 218, + MinorAddSecondShort: 219, + AddSecond: 220, + AddThird: 221, + AddNinth: 222, + AddEleventh: 223, + NoThird: 224, + NoFifth: 225 +}; + +export function stringifyFinaleJazzChordSigns( + signNames: (keyof typeof finaleJazzChordCharacterMap)[] +) { + return String.fromCodePoint(...signNames.map((name) => finaleJazzChordCharacterMap[name] + 29)); +} diff --git a/test/music/chords.test.ts b/test/music/chords.test.ts new file mode 100644 index 0000000..2630610 --- /dev/null +++ b/test/music/chords.test.ts @@ -0,0 +1,7 @@ +import { describe, it } from 'vitest'; + +describe('hi', () => { + it('hi', () => { + console.log('hi') + }); +}); From 7de862f79ab1d434e49c113e7a17cba5410da32b Mon Sep 17 00:00:00 2001 From: Kim Jeong-won Date: Tue, 27 Feb 2024 00:44:50 +0900 Subject: [PATCH 2/4] feat: sort chord by (my personal) complexity score --- src/utils/music/chords.ts | 88 +++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/src/utils/music/chords.ts b/src/utils/music/chords.ts index c90c399..d73aa30 100644 --- a/src/utils/music/chords.ts +++ b/src/utils/music/chords.ts @@ -13,6 +13,25 @@ export type ChordName = | 'diminishedSeventh' | 'none'; +export type ChordThirdTonePitch = 2 | 3 | 4 | 5; +export type ChordFifthTonePitch = 6 | 7 | 8; +/** -1 means that there are both 7th and major 7th */ +export type ChordSeventhTonePitch = 8 | 9 | 10 | 11 | -1; +export type ChordTensionPitch = 13 | 14 | 15 | 17 | 18 | 20 | 21; +export type ChordDetail = { + tones: { + third: ChordThirdTonePitch | null; + fifth: ChordFifthTonePitch | null; + seventh: ChordSeventhTonePitch | null; + tensions: ChordTensionPitch[]; + }; + symbols: { + root: number; + quality: ChordQuality; + extension: ChordExtension | null; + }; +}; + export function identifyChordsFromPitches(pitches: Pitch[]) { if (pitches.length === 0) { return null; @@ -34,10 +53,17 @@ export function identifyChordsFromPitches(pitches: Pitch[]) { console.log([...intervals]); identifiedChords.push(identifyChordFromIntervals(bassNumber, root, intervals)); } - return { chords: identifiedChords, bass: bass }; + identifiedChords.sort( + (a, b) => scoreChordComplexity(a, bassNumber) - scoreChordComplexity(b, bassNumber) + ); + return { chords: identifiedChords, bass: bassNumber }; } -function identifyChordFromIntervals(bass: number, root: number, intervals: Set) { +function identifyChordFromIntervals( + bass: number, + root: number, + intervals: Set +): ChordDetail { const bassInterval = bass >= root ? bass - root : bass - root + 12; /** * chord tones @@ -47,10 +73,10 @@ function identifyChordFromIntervals(bass: number, root: number, intervals: Set(); + let third: null | ChordThirdTonePitch = null; + let fifth: null | ChordFifthTonePitch = null; + let seventh: null | ChordSeventhTonePitch = null; + const tensions = new Set(); // b2 to b9 if (intervals.has(1)) { @@ -59,7 +85,7 @@ function identifyChordFromIntervals(bass: number, root: number, intervals: Set { + if (![1, 2, 3, 5, 6, 8, 9].includes(interval)) { + throw Error('interval is strange'); + } else { + tensions.add((interval + 12) as ChordTensionPitch); + } + }); return { tones: { third, @@ -433,3 +466,42 @@ function identifyChordFromIntervals(bass: number, root: number, intervals: Set Date: Tue, 27 Feb 2024 00:47:10 +0900 Subject: [PATCH 3/4] feat: change notation method for chord tension --- src/lib/notation/chord/ChordTensionNotation .svelte | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/notation/chord/ChordTensionNotation .svelte b/src/lib/notation/chord/ChordTensionNotation .svelte index f16fc34..7f63fd9 100644 --- a/src/lib/notation/chord/ChordTensionNotation .svelte +++ b/src/lib/notation/chord/ChordTensionNotation .svelte @@ -4,12 +4,16 @@ export let tensions: number[] = []; -{#if tensions.length > 0}{stringifyFinaleJazzChordSigns(['('])}{#each tensions as tension, idx}{#if idx !== 0}{','} 0}{#if tensions.length === 1}add{:else}{stringifyFinaleJazzChordSigns(['('])}{/if}{#each tensions as tension, idx}{#if idx !== 0}{','}{/if}{@const flat = tension === 13 || tension === 20}{@const sharp = tension === 15 || tension === 18}{#if flat}{stringifyFinaleJazzChordSigns(['Flat'])}{/if}{#if sharp}{stringifyFinaleJazzChordSigns(['Sharp'])}{/if}{stringifyFinaleJazzChordSigns( chordTensionToFinaleJazzChordSignMap[tension] - )}{/each}{stringifyFinaleJazzChordSigns([')'])}{/if} + )}{/each}{#if tensions.length > 1}{stringifyFinaleJazzChordSigns([')'])}{/if}{/if} From 7a00973a32e4fb8a0f77cf4db51df1b05fb09bd9 Mon Sep 17 00:00:00 2001 From: Kim Jeong-won Date: Tue, 27 Feb 2024 01:59:26 +0900 Subject: [PATCH 4/4] feat: rearrange chord-finder page --- src/routes/tools/chord-finder/+page.svelte | 14 ++-- src/routes/tools/chord-finder/Board.svelte | 79 ++++++++++++++-------- src/utils/music/chords.ts | 3 - 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/routes/tools/chord-finder/+page.svelte b/src/routes/tools/chord-finder/+page.svelte index 5cc6801..6f27179 100644 --- a/src/routes/tools/chord-finder/+page.svelte +++ b/src/routes/tools/chord-finder/+page.svelte @@ -14,11 +14,13 @@
- { - updateFingerPosition({ fret, line }); - }} - > +
+ { + updateFingerPosition({ fret, line }); + }} + > +
diff --git a/src/routes/tools/chord-finder/Board.svelte b/src/routes/tools/chord-finder/Board.svelte index d149065..721a117 100644 --- a/src/routes/tools/chord-finder/Board.svelte +++ b/src/routes/tools/chord-finder/Board.svelte @@ -1,46 +1,69 @@ -{#if chords != undefined} - {#each chords.chords as chord} -
- +
+ {#if chords != undefined} + {@const primaryChord = chords.chords.at(0)} +
+ {#if primaryChord} +
+ +
+ {/if} +
+ {#each chords.chords.slice(1) as chord, idx} + {#if idx === 0} +
+ +
+ {:else} +
+ +
+ {/if} + {/each} +
- {/each} -{/if} -

+ {/if} +

+ + diff --git a/src/utils/music/chords.ts b/src/utils/music/chords.ts index d73aa30..da2b253 100644 --- a/src/utils/music/chords.ts +++ b/src/utils/music/chords.ts @@ -45,12 +45,10 @@ export function identifyChordsFromPitches(pitches: Pitch[]) { const numberedNotes = [ ...new Set(pitches.map((pitch) => ((numberingPitch(pitch) % 12) + 12) % 12)) ].toSorted(); - console.log(numberedNotes); for (const root of numberedNotes) { const intervals = new Set( numberedNotes.map((n) => (n >= root ? n - root : n - root + 12)).filter((n) => n !== 0) ); - console.log([...intervals]); identifiedChords.push(identifyChordFromIntervals(bassNumber, root, intervals)); } identifiedChords.sort( @@ -103,7 +101,6 @@ function identifyChordFromIntervals( // now we found 3rd! it's very critical to determine its quality if (third !== bassInterval) { - console.log(third, bassInterval); intervals.delete(bassInterval); intervals.delete(bassInterval + 12); }