UNPKG

cxchord

Version:
366 lines (345 loc) 14 kB
import { ChordInstance } from "./CxChordInst"; import { ChordMap, ChordMapEntry, ChordMapTable, Conflicts, RootNoteNames } from "./Interfaces"; import * as _ from "lodash" // export const rootNoteNames: RootNoteNames = { sharp: ["C", "C#", "D","D#","E","F","F#","G","G#","A", "A#", "B" ], flat: ["C", "Db", "D","Eb","E","F","Gb","G","Ab","A", "Bb", "B" ] } export const noteNames = ["C", "C#", "D","D#","E","F","F#","G","G#","A", "A#", "B", "c", "c#", "d","d#","e","f","f#","g","g#","a", "a#", "b", "C", "Db", "D","Eb","E","F","Gb","G","Ab","A", "Bb", "B", "c", "db", "d","eb","e","f","gb","g","ab","a", "bb", "b" ] export function getNoteNumber ( note: string ): number { let octave = 5 const _oct = note.replace(/^[A-Ga-g]+[#b]*/,'') if ( ! _.isEmpty(_oct) ) { const _octave = _oct.replace(/[^0-9]+/g, '') if ( _octave.match(/^[0-9]0{0,1}/) ) octave = parseInt( _octave ) else throw Error ("getNoteNumber: Illegal octave number. Legal octaves are 0-10"); } const noteName = note.replace(/[0-9]/g, '') const noteNum = noteNames.indexOf(noteName) % 12 if ( noteNum < 0 ) throw Error ("getNoteNumber: Unknown note name"); else return noteNum + ( octave * 12 ) } export function getNoteName( note = 0, flatOrSharp = "flat" ): string { // For positive bass notes assume an actual bass has been provided // For negative bass notes assume that a ChorMap type root note has been provided note = note % 12 const noteName = rootNoteNames[flatOrSharp][ ( Math.abs(note < 0 ? note + 12 : note) ) % 12 ] return noteName } export function getExtName( nameWithCommas : string ) : string { const extName = nameWithCommas.replace( /,/g, "").replace( /-1/g, "-no-root" ).replace( /-5/g, "-no-fifth" ) return extName } export function getChordName( nameWithCommas : string, root = 0 , bass = 255, flatOrSharp = "flat" ): string { let chordName = getNoteName(root, flatOrSharp ) const extName = getExtName(nameWithCommas) chordName += extName // console.debug("chordName(1):'" + chordName + "'") const group = _.isUndefined( chordMap[nameWithCommas] ) ? 2 : chordMap[nameWithCommas].group bass = bass == 255 ? root : bass if ( bass !== root && group != GR.rootLess ) { const bassName = getNoteName(bass, flatOrSharp) chordName += "/" + bassName } return chordName } export function isNoRootChord( chordSym: string ): boolean { return _.isUndefined(chordSym) ? false : ( chordSym.indexOf("-1") > 0 ) } export function getKnockouts(key: string ): number[] { let _key = key if ( _.isUndefined( knockouts[_key]) ) { _key = _key.split(',')[0] } return _.isUndefined( knockouts[_key]) ? [] : knockouts[_key] } export enum GR { shell = 1, standard = 2 , altered = 4, extented = 8, rootLess = 16, reduced = 32, cluster = 64 , passing = 128 } export const chordMap: ChordMap = { "Maj" : {notes: [0,4,7], root: 0, inv:0, group: GR.standard }, "Maj,7" : {notes: [0,4,7,11], root: 0, inv:0, group: GR.standard }, "Maj,7,9" : {notes: [0,4,7,11,14], root: 0, inv:0, group: GR.extented }, "Maj,7,9,#11" : {notes: [0,4,7,11,14,18], root: 0, inv:0, group: GR.extented }, "Maj,7,9,#11,13" : {notes: [0,4,7,11,14,18,21], root: 0, inv:0, group: GR.extented }, "Maj,6" : {notes: [0,4,7,9], root: 0, inv:0, group: GR.standard }, "Maj,6,9" : {notes: [0,4,7,9,14], root: 0, inv:0, group: GR.extented }, "Maj,add2" : {notes: [0,2,4,7], root: 0, inv:0, group: GR.cluster }, "Maj,add9" : {notes: [0,4,7,14], root: 0, inv:0, group: GR.extented }, "5" : {notes: [0,7], root: 0, inv:0, group: GR.shell }, // Minor "Min" : {notes: [0,3,7], root: 0, inv:0, group: GR.standard }, "Min,7" : {notes: [0,3,7,10], root: 0, inv:0, group: GR.standard }, "Min,7,9" : {notes: [0,3,7,10], root: 0, inv:0, group: GR.altered }, "Min,7,b5" : {notes: [0,3,6,10], root: 0, inv:0, group: GR.standard }, "Min,6" : {notes: [0,3,7,9], root: 0, inv:0, group: GR.standard }, "Min,6,9" : {notes: [0,3,7,9,14], root: 0, inv:0, group: GR.extented }, "Min,M7" : {notes: [0,3,7,11], root: 0, inv:0, group: GR.standard }, // Dominant "Dom,7" : {notes: [0,4,7,10], root: 0, inv:0, group: GR.altered }, "Dom,7,9" : {notes: [0,4,7,10,14], root: 0, inv:0, group: GR.extented }, "Dom,7,#5" : {notes: [0,4,8,10], root: 0, inv:0, group: GR.altered }, "Dom,7,b5" : {notes: [0,4,6,10], root: 0, inv:0, group: GR.altered }, "Dom,7,sus4" : {notes: [0,5,7,10], root: 0, inv:0, group: GR.altered }, "Dom,7,sus2" : {notes: [0,2,7,10], root: 0, inv:0, group: GR.altered }, "Dim" : {notes: [0,3,6], root: 0, inv:0, group: GR.passing }, "Dim,7" : {notes: [0,3,6,9], root: 0, inv:0, group: GR.passing }, "Dim,7(HW)" : {notes: [0,3,6,9], root: 0, inv:0, group: GR.passing }, "Dim,7(WH)" : {notes: [0,3,6,9], root: 0, inv:0, group: GR.passing }, // "Dimø" : {notes: [0,3,6,10], root: 0, inv:0, group: 2 }, "Maj,#5" : {notes: [0,4,8], root: 0, inv:0, group: GR.altered }, "Sus2" : {notes: [0,2,7], root: 0, inv:0, group: GR.altered }, "Sus4" : {notes: [0,5,7], root: 0, inv:0, group: GR.altered }, // "Quater" : {notes: [0,5,10], root: 0, inv:0, group: 3 }, // Jazz reduced chords "Maj,7,-5" : {notes: [0,4,11], root: 0, inv:0, group: GR.reduced }, "Maj,6,-5" : {notes: [0,4,9], root: 0, inv:0, group: GR.reduced }, "Min,6,-5" : {notes: [0,3,9], root: 0, inv:0, group: GR.reduced }, "Min,7,-5" : {notes: [0,3,10], root: 0, inv:0, group: GR.reduced }, // Jazz block chords // Major A and B Voicings "Maj,6,9,-1(A)" : {notes: [0,3,5,10], root: -4, inv:0, group: GR.rootLess }, "Maj,6,9,-1(B)" : {notes: [0,5,7,10], root: -9, inv:0, group: GR.rootLess }, "Maj,7,9,-1(A)" : {notes: [0,3,7,10], root: -4, inv:0, group: GR.rootLess }, "Maj,7,9,-1(B)" : {notes: [0,3,5,8], root: -11, inv:0, group: GR.rootLess }, // Minor A and B Voicings "Min,6,9,-1(A)" : {notes: [0,4,6,11], root: -3, inv:0, group: GR.rootLess }, // "Min,6,9,-1(B)" : {notes: [0,4,5,9], root: -10, inv:0, group: GR.rootLess }, // TODO - Check this one "Min,7,9,-1(A)" : {notes: [0,4,7,11], root: -3, inv:0, group: GR.rootLess }, "Min,7,9,-1(B)" : {notes: [0,4,5,9], root: -10, inv:0, group: GR.rootLess }, // "Min,7,b5(A)" : {notes: [0,3,7,9], root: -3, inv:0, group: GR.standard }, // "Min,7,b5(B)" : {notes: [0,2,5,8], root: -10, inv:0, group: GR.standard }, // "MinCluster,7,9,-1" : {notes: [0,4,5,9], root: -10, inv:0, group: GR.rootLess }, // Dominant A and B Voicings "Dom,7,9,-1(A)" : {notes: [0,4,6,11], root: -10, inv:0, group: GR.rootLess }, "Dom,7,9,-1(B)" : {notes: [0,5,6,10], root: -4, inv:0, group: GR.rootLess }, // "Dom,7,b9,-1(A)" : {notes: [0,3,6,10], root: -10, inv:0, group: GR.rootLess }, "Dom,7,b9,b13,-1(A)": {notes: [0,5,6,10], root: -4, inv:0, group: GR.rootLess }, "Dom,7,b9,b13,-1(B)": {notes: [0,4,6,9], root: -4, inv:0, group: GR.rootLess }, "Dom,7,b9,-1" : {notes: [0,3,6,9], root: -4, inv:0, group: GR.rootLess }, "Dom,7,#9,-1" : {notes: [0,3,6,11], root: -4, inv:0, group: GR.rootLess }, // "MinCluster" : {notes: [0,2,3,7], root: 0, inv:0, group: 3 }, // "MajCluster" : {notes: [0,2,4,7], root: 0, inv:0, group: 3 }, // "Dom7Cluster,-1" : {notes: [0,4,6,9], root: -4, inv:0, group: GR.rootLess } } // Note: Extension don't live with a half or whole step between each other // except for (mostly altered) dominant chords export const extensions: {[key:string]: number[] } = { "Maj" : [14,18,21], "Maj,7" : [14,18,21], "Min" : [14,17,21,13,18], "Min,M7" : [14,17,21,18], // "Dim7HW" : [14,17,20,23], // "Dim7WH" : [14,17,20,23], // "Half-Dim7": [14,17,20,21], "Maj,#5" : [14,18], "Dom,7" : [13,14,18,21], "Dom,7,#5" : [13,14,15,18,19], "Dom,7,b5" : [13,14,15,19,20,21] // TODO: Add the rest later } export const mustHave: {[key:string]: number[] } = { // Major "Maj" : [4], "Maj,7" : [4,11], "Maj,7,9" : [4,11], "Maj,7,9,#11" : [4,11], "Maj,7,9,#11,13": [4,11], "Maj,6" : [4,9], "Maj,6,9" : [4,9], "Maj,6,9,-1" : [4,9], // "Maj,7,b5" : [0,4,6,11], "Maj,add2" : [2,4], "Maj,add9" : [4,14], // "Maj-add9no5":[4,14], "Maj,#5" : [0,4,8], // Sus Types "Sus2" : [0,2,7], "Sus4" : [0,5,7], // Minor "Min" : [3], "Min,6" : [3,9], "Min,6,9" : [3,9], "Min,6,9,-1" : [3,9], "Min,7" : [3,10], "Min,7,9" : [3,10], "Min,7,9,-1" : [3,10], "Min,7,b5" : [3,6,10], "Min,M7" : [3,11], // Dominant "Dom,7" : [4,10], "Dom,7,9" : [4,10], "Dom,7,#5" : [4,8,10], "Dom,7,b5" : [4,6,10], "Dom,7,sus4" : [5,10], "Dom,7,sus2" : [2,10], "Dim" : [0,3,6], "Dim,7(WH)" : [0,3,6,9], "Dim,7(HW)" : [0,3,6,9], // Jazz type no-root chords "Dom,7,9,-1" : [4,10], "Dom,7,b9,-1" : [4,10], "Dom,7,#9,-1" : [4,10], "Dom,7,9,13,-1,-5": [4,10,21], } export const conflicts: Conflicts = { // Each entry is one set off 2 conflciting notes // Major /* "Maj" : [[4,5],[10,11]], "Maj7" : [], "Maj79" : [], "Maj79#11" : [], "Maj79#1113": [], "Maj6" : [], "Maj69" : [], */ // "Maj,7,b5" : [[6,7]], /* "Maj-add2" : [], "Maj-add9" : [], "Maj-add9no5":[], */ "Maj,#5" : [[7,8]], // Minor "Min" : [[5,6], [7,8]], "Min,6" : [[5,6], [7,8]], "Min,6,9" : [[5,6], [7,8]], "Min,6,9,-1" : [[5,6], [7,8]], "Min,7" : [[5,6], [7,8]], "Min,7,9" : [[5,6], [7,8]], "Min,7,9,-1" : [[5,6], [7,8]], "Min,7,b5" : [[5,6], [7,8]], "Min,M7" : [[5,6], [7,8]], // "MinCluster,7,9,-1": [[5,6], [7,8]] // Dominant /* "Dom7" : [], "Dom7#5" : [], "Dom7b5" : [], "Dom7sus4" : [], "Dom7sus2" : [], // Jazz type no-root chords "Dom79-no root" : [], "Dom7b9-no root" : [], "Dom7#9-no root" : [], "Dim" : [], "Dim7WH" : [], "Dim7HW" : [] */ } export const knockouts: {[key:string]: number[] } = { // Is checked in two octaves // Major "Maj" : [1,3,5,8,10], "Maj,7" : [1,3,5,8,10], "Maj,7,9" : [1,3,5,8,10], "Maj,7,9,#11" : [1,3,5,8,10], "Maj,7,9,#11,13": [1,3,5,8,10], "Maj,6" : [1,3,5,8,10], "Maj,6,9" : [1,3,5,8,10], "Maj,7,b5" : [1,3,5,7,8,10], "Maj,add2" : [1,3,5,8,10,11,14], "Maj,add9" : [1,3,5,8,9,10,11], // "Maj,add9no5": [1,3,5,8,10], "Maj,#5" : [1,3,5,7,10], "5" : [1,2,3,4,5,6,8,9,10,11], // Minor "Min" : [1,4,6,8,11], "Min,6" : [1,4,6,8], "Min,6,9" : [1,4,6,8], "Min,7" : [1,4,6,8,11], "Min,7,9" : [1,4,6,8,11], "Min,7,b5" : [4,7,8,11], "Min,M7" : [1,4,6,8,10], "MinCluster,7,9,-1": [1,4,8], // Diminished "Dim" : [4,7,9,10,11], "Dim,7" : [1,2,4,5,7,8,10,11], "Dim,7(HW)" : [2,5,8,11], "Dim,7(WH)" : [1,4,7,10], // "Half-Dim7" : [4,7,11], // Dominant "Dom,7" : [5,11], "Dom,7,#5" : [5,11], "Dom,7,sus4" : [4,11], // "Dom7sus4#5": [4,11], "Dom,7,b5" : [5,7,11], // Quater types "Sus2" : [1,3,4,5,8], "Sus4" : [1,3,4,6,8], // "Quater" : [1,3,4,5,8,], // Jazz reduced chords "Maj,7,-5" : [1,3,5,6,7,8,10], "Maj,6,-5" : [1,3,5,6,7,8,10], "Min,6,-5" : [1,4,6,7,8], "Min,7,-5" : [1,4,6,7,8,11], // Jazz type no-root chords "Maj,6,9,-1(A)" : [0,1,3,5,6,8,10], "Maj,6,9,-1(B)" : [0,1,3,5,6,8,10], "Maj,7,9,-1(A)" : [0,1,3,5,6,8,10], "Maj,7,9,-1(B)" : [0,1,3,5,6,8,10], "Maj,7,9,-1" : [0,1,3,5,6,8,10], "Min,6,9,-1(A)" : [0,4,6,8,10,11], "Min,6,9,-1(B)" : [0,4,6,8,10,11], "Min,7,9,-1(A)" : [0,4,6,8,11], "Min,7,9,-1(B)" : [0,4,6,8,11], // "Min,7,b5(A)" : [0,4,7,8,11], // "Min,7,b5(B)" : [0,4,7,8,11], "Min,6,9,-1" : [1,4,8], "Min,7,9,-1" : [1,4,8], "Dom,7,9,-1(A)" : [0,1,3,5,8,11], "Dom,7,9,-1(B)" : [0,1,3,5,8,11], "Dom,7,b9,b13,-1(A)": [0,2,5,8,11], "Dom,7,b9,b13,-1(B)": [0,2,5,8,11], "Dom,7,9,13,-1,-5": [0,5,7,11], "Dom,7,9,-1" : [0,5,7,11], "Dom,7,b9,-1" : [0,5,7,11], "Dom,7,#9,-1" : [0,5,7,11], // "Dom7Cluster,-1": [0,5,7,11] } export class ChordForms { public chordMapWithInv: ChordMapTable = {} constructor() { this.buildInversions() } public getInvariants( chordName: string ): string[] { const invariants: string[] = [] if ( _.has(chordMap, chordName) ) { const chord:ChordMapEntry = chordMap[chordName] _.forIn(chordMap, function(value: ChordMapEntry, key: string) { if ( key !== chordName && _.isEqual(value.notes, chord.notes ) ) { invariants.push(key) } }); } return invariants; } public buildInversions() { const mapWithInv = this.chordMapWithInv // // Loop through the Chord hypothesis // _.forIn(chordMap, function(value: ChordMapEntry, key) { mapWithInv[key] = [] // // Loop through the inversions // const chord = new ChordInstance(value.notes) const interval = chord.getInversion(0) for( let i = 0 ; i < value.notes.length; i++) { const _notes = chord.getInversion(i) // // Calculate the new inverted root note // const rootNote = ( - ( Math.abs(value.root) + interval[i] ) ) % 12 // mapWithInv[key].push( { notes: _notes, root: ( rootNote <= 0 ? 0 : rootNote ) , inv: i, group: value.group } ) mapWithInv[key].push( { notes: _notes, root: rootNote , inv: i, group: value.group } ) } }) } }