UNPKG

@tonaljs/chord-detect

Version:

Detect chord name based on note names

1 lines 5.64 kB
{"version":3,"sources":["../index.ts"],"sourcesContent":["import { all, ChordType } from \"@tonaljs/chord-type\";\nimport { modes } from \"@tonaljs/pcset\";\nimport { note } from \"@tonaljs/pitch-note\";\n\ninterface FoundChord {\n readonly weight: number;\n readonly name: string;\n}\n\nconst namedSet = (notes: string[]) => {\n const pcToName = notes.reduce<Record<number, string>>((record, n) => {\n const chroma = note(n).chroma;\n if (chroma !== undefined) {\n record[chroma] = record[chroma] || note(n).name;\n }\n return record;\n }, {});\n\n return (chroma: number) => pcToName[chroma];\n};\n\ntype DetectOptions = {\n assumePerfectFifth: boolean;\n};\nexport function detect(\n source: string[],\n options: Partial<DetectOptions> = {},\n): string[] {\n const notes = source.map((n) => note(n).pc).filter((x) => x);\n if (note.length === 0) {\n return [];\n }\n\n const found: FoundChord[] = findMatches(notes, 1, options);\n\n return found\n .filter((chord) => chord.weight)\n .sort((a, b) => b.weight - a.weight)\n .map((chord) => chord.name);\n}\n\n/* tslint:disable:no-bitwise */\nconst BITMASK = {\n // 3m 000100000000\n // 3M 000010000000\n anyThirds: 384,\n // 5P 000000010000\n perfectFifth: 16,\n // 5d 000000100000\n // 5A 000000001000\n nonPerfectFifths: 40,\n anySeventh: 3,\n};\n\nconst testChromaNumber = (bitmask: number) => (chromaNumber: number) =>\n Boolean(chromaNumber & bitmask);\nconst hasAnyThird = testChromaNumber(BITMASK.anyThirds);\nconst hasPerfectFifth = testChromaNumber(BITMASK.perfectFifth);\nconst hasAnySeventh = testChromaNumber(BITMASK.anySeventh);\nconst hasNonPerfectFifth = testChromaNumber(BITMASK.nonPerfectFifths);\n\nfunction hasAnyThirdAndPerfectFifthAndAnySeventh(chordType: ChordType) {\n const chromaNumber = parseInt(chordType.chroma, 2);\n return (\n hasAnyThird(chromaNumber) &&\n hasPerfectFifth(chromaNumber) &&\n hasAnySeventh(chromaNumber)\n );\n}\n\nfunction withPerfectFifth(chroma: string): string {\n const chromaNumber = parseInt(chroma, 2);\n return hasNonPerfectFifth(chromaNumber)\n ? chroma\n : (chromaNumber | 16).toString(2);\n}\n\n/* tslint:enable:no-bitwise */\n\ntype FindMatchesOptions = {\n assumePerfectFifth: boolean;\n};\nfunction findMatches(\n notes: string[],\n weight: number,\n options: Partial<FindMatchesOptions>,\n): FoundChord[] {\n const tonic = notes[0];\n const tonicChroma = note(tonic).chroma;\n const noteName = namedSet(notes);\n // we need to test all chromas to get the correct baseNote\n const allModes = modes(notes, false);\n\n const found: FoundChord[] = [];\n allModes.forEach((mode, index) => {\n const modeWithPerfectFifth =\n options.assumePerfectFifth && withPerfectFifth(mode);\n // some chords could have the same chroma but different interval spelling\n const chordTypes = all().filter((chordType) => {\n if (\n options.assumePerfectFifth &&\n hasAnyThirdAndPerfectFifthAndAnySeventh(chordType)\n ) {\n return chordType.chroma === modeWithPerfectFifth;\n }\n return chordType.chroma === mode;\n });\n\n chordTypes.forEach((chordType) => {\n const chordName = chordType.aliases[0];\n const baseNote = noteName(index);\n const isInversion = index !== tonicChroma;\n if (isInversion) {\n found.push({\n weight: 0.5 * weight,\n name: `${baseNote}${chordName}/${tonic}`,\n });\n } else {\n found.push({ weight: 1 * weight, name: `${baseNote}${chordName}` });\n }\n });\n });\n\n return found;\n}\n\n/** @deprecated */\nexport default { detect };\n"],"mappings":";AAAA,SAAS,WAAsB;AAC/B,SAAS,aAAa;AACtB,SAAS,YAAY;AAOrB,IAAM,WAAW,CAAC,UAAoB;AACpC,QAAM,WAAW,MAAM,OAA+B,CAAC,QAAQ,MAAM;AACnE,UAAM,SAAS,KAAK,CAAC,EAAE;AACvB,QAAI,WAAW,QAAW;AACxB,aAAO,MAAM,IAAI,OAAO,MAAM,KAAK,KAAK,CAAC,EAAE;AAAA,IAC7C;AACA,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,WAAmB,SAAS,MAAM;AAC5C;AAKO,SAAS,OACd,QACA,UAAkC,CAAC,GACzB;AACV,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC;AAC3D,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAsB,YAAY,OAAO,GAAG,OAAO;AAEzD,SAAO,MACJ,OAAO,CAAC,UAAU,MAAM,MAAM,EAC9B,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,IAAI,CAAC,UAAU,MAAM,IAAI;AAC9B;AAGA,IAAM,UAAU;AAAA;AAAA;AAAA,EAGd,WAAW;AAAA;AAAA,EAEX,cAAc;AAAA;AAAA;AAAA,EAGd,kBAAkB;AAAA,EAClB,YAAY;AACd;AAEA,IAAM,mBAAmB,CAAC,YAAoB,CAAC,iBAC7C,QAAQ,eAAe,OAAO;AAChC,IAAM,cAAc,iBAAiB,QAAQ,SAAS;AACtD,IAAM,kBAAkB,iBAAiB,QAAQ,YAAY;AAC7D,IAAM,gBAAgB,iBAAiB,QAAQ,UAAU;AACzD,IAAM,qBAAqB,iBAAiB,QAAQ,gBAAgB;AAEpE,SAAS,wCAAwC,WAAsB;AACrE,QAAM,eAAe,SAAS,UAAU,QAAQ,CAAC;AACjD,SACE,YAAY,YAAY,KACxB,gBAAgB,YAAY,KAC5B,cAAc,YAAY;AAE9B;AAEA,SAAS,iBAAiB,QAAwB;AAChD,QAAM,eAAe,SAAS,QAAQ,CAAC;AACvC,SAAO,mBAAmB,YAAY,IAClC,UACC,eAAe,IAAI,SAAS,CAAC;AACpC;AAOA,SAAS,YACP,OACA,QACA,SACc;AACd,QAAM,QAAQ,MAAM,CAAC;AACrB,QAAM,cAAc,KAAK,KAAK,EAAE;AAChC,QAAM,WAAW,SAAS,KAAK;AAE/B,QAAM,WAAW,MAAM,OAAO,KAAK;AAEnC,QAAM,QAAsB,CAAC;AAC7B,WAAS,QAAQ,CAAC,MAAM,UAAU;AAChC,UAAM,uBACJ,QAAQ,sBAAsB,iBAAiB,IAAI;AAErD,UAAM,aAAa,IAAI,EAAE,OAAO,CAAC,cAAc;AAC7C,UACE,QAAQ,sBACR,wCAAwC,SAAS,GACjD;AACA,eAAO,UAAU,WAAW;AAAA,MAC9B;AACA,aAAO,UAAU,WAAW;AAAA,IAC9B,CAAC;AAED,eAAW,QAAQ,CAAC,cAAc;AAChC,YAAM,YAAY,UAAU,QAAQ,CAAC;AACrC,YAAM,WAAW,SAAS,KAAK;AAC/B,YAAM,cAAc,UAAU;AAC9B,UAAI,aAAa;AACf,cAAM,KAAK;AAAA,UACT,QAAQ,MAAM;AAAA,UACd,MAAM,GAAG,QAAQ,GAAG,SAAS,IAAI,KAAK;AAAA,QACxC,CAAC;AAAA,MACH,OAAO;AACL,cAAM,KAAK,EAAE,QAAQ,IAAI,QAAQ,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,CAAC;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AACT;AAGA,IAAO,uBAAQ,EAAE,OAAO;","names":[]}