UNPKG

12tet

Version:

Music theory library for generating and working with chords, modes, intervals, etc.

253 lines (252 loc) 9.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.chord = exports.isChord = exports.chordNumerals = exports.isChordNumeral = exports.chordDegreesToModeDegrees = exports.chordDegreeToModeDegree = exports.chordDegrees = exports.isChordDegree = exports.alteredChordDegrees = exports.isAlteredChordDegree = exports.standardChordDegrees = exports.isStandardChordDegree = exports.chordExtensions = exports.isChordExtension = exports.chordAdditions = exports.isChordAddition = exports.chordBases = exports.isChordBase = exports.diatonicChordBases = exports.isDiatonicChordBase = void 0; const interval_1 = require("../interval"); const utils_1 = require("../utils"); const note_1 = require("../note"); const mode_1 = require("../mode"); const key_1 = require("../key"); const DIATONIC_CHORD_BASES = ['maj', 'min', 'dim']; function isDiatonicChordBase(chordType) { return DIATONIC_CHORD_BASES.includes(chordType); } exports.isDiatonicChordBase = isDiatonicChordBase; exports.diatonicChordBases = [...DIATONIC_CHORD_BASES]; const CHORD_BASES = [...DIATONIC_CHORD_BASES, 'dom', 'sus2', 'sus4', 'aug']; function isChordBase(chordType) { return CHORD_BASES.includes(chordType); } exports.isChordBase = isChordBase; exports.chordBases = [...CHORD_BASES]; const CHORD_ADDITIONS = ['2', '4', '6', '9', '11', '13']; function isChordAddition(chordAddition) { return CHORD_ADDITIONS.includes(chordAddition); } exports.isChordAddition = isChordAddition; exports.chordAdditions = [...CHORD_ADDITIONS]; const CHORD_EXTENSIONS = ['7', '9', '11', '13']; function isChordExtension(chordExtension) { return CHORD_EXTENSIONS.includes(chordExtension); } exports.isChordExtension = isChordExtension; exports.chordExtensions = [...CHORD_EXTENSIONS]; const STANDARD_CHORD_DEGREES = [...mode_1.modeDegrees, '9', '11', '13']; function isStandardChordDegree(degreeNumber) { return STANDARD_CHORD_DEGREES.includes(degreeNumber); } exports.isStandardChordDegree = isStandardChordDegree; exports.standardChordDegrees = [...STANDARD_CHORD_DEGREES]; const ALTERED_CHORD_DEGREES = [...mode_1.alteredModeDegrees, 'b9', '#9', 'b11', '#11', 'b13', '#13']; function isAlteredChordDegree(degreeNumber) { return ALTERED_CHORD_DEGREES.includes(degreeNumber); } exports.isAlteredChordDegree = isAlteredChordDegree; exports.alteredChordDegrees = [...ALTERED_CHORD_DEGREES]; const CHORD_DEGREES = [...STANDARD_CHORD_DEGREES, ...ALTERED_CHORD_DEGREES]; function isChordDegree(degreeNumber) { return CHORD_DEGREES.includes(degreeNumber); } exports.isChordDegree = isChordDegree; exports.chordDegrees = [...CHORD_DEGREES]; function chordDegreeToModeDegree(chordDegree) { switch (chordDegree) { case 'b9': return 'b2'; case '#9': return '#2'; case '9': return '2'; case 'b11': return 'b4'; case '#11': return '#4'; case '11': return '4'; case 'b13': return 'b6'; case '#13': return '#6'; case '13': return '6'; default: return chordDegree; } } exports.chordDegreeToModeDegree = chordDegreeToModeDegree; function chordDegreesToModeDegrees(chordDegrees) { const modeDegrees = chordDegrees.map(chordDegree => chordDegreeToModeDegree(chordDegree)); return modeDegrees.filter(mode_1.isModeDegree); } exports.chordDegreesToModeDegrees = chordDegreesToModeDegrees; // https://en.wikipedia.org/wiki/Roman_numeral_analysis#Modes // \u1d47 -> superscript b // \u2070 -> superscript 0 // \u2075 -> superscript 5 const CHORD_NUMERALS = [ 'I', 'i', 'i\u2070', 'II', '\u1d47II', 'ii', 'ii\u2070', '\u1d47III', 'iii', '\u1d47iii', 'iii\u2070', 'IV', 'iv', '#iv\u2070', 'V', '\u1d47V', 'v', 'v\u2070', '\u1d47VI', 'vi', 'vi\u2070', '\u1d47VII', 'vii', '\u1d47vii', 'vii\u2070' ]; function isChordNumeral(numeral) { return CHORD_NUMERALS.includes(numeral); } exports.isChordNumeral = isChordNumeral; exports.chordNumerals = [...CHORD_NUMERALS]; const chordDegreesByChordBase = { maj: ['1', '3', '5', '7', '9', '11', '13'], // b3, b7 min: ['1', 'b3', '5', 'b7', '9', '11', '13'], // b3, b5 dim: ['1', 'b3', 'b5', '7', '9', '11', '13'], // b7 dom: ['1', '3', '5', 'b7', '9', '11', '13'], // #5 aug: ['1', '3', '#5', '7', '9', '11', '13'], // 3 -> 2 sus2: ['1', '2', '5', '7', '9', '11', '13'], // 3 -> 4 sus4: ['1', '4', '5', '7', '9', '11', '13'] }; function isChord(chord) { if (!chord.root || !(0, note_1.isNote)(chord.root)) { return false; } if (!chord.base || !isChordBase(chord.base)) { return false; } if (chord.extension && !isChordExtension(chord.extension)) { return false; } for (let i = 0; i < chord.additions.length; i++) { if (!isChordAddition(chord.additions[i])) { return false; } } for (let i = 0; i < chord.alterations.length; i++) { if (!isAlteredChordDegree(chord.alterations[i])) { return false; } } if (chord.slash && !isChordDegree(chord.slash)) { return false; } if (chord.slashNote && !(0, note_1.isNote)(chord.slashNote)) { return false; } for (let i = 0; i < chord.intervals.length; i++) { if (!(0, interval_1.isInterval)(chord.intervals[i])) { return false; } } for (let i = 0; i < chord.degrees.length; i++) { if (!isChordDegree(chord.degrees[i])) { return false; } } for (let i = 0; i < chord.notes.length; i++) { if (!(0, note_1.isNote)(chord.notes[i])) { return false; } } if (!chord.name || typeof chord.name !== "string") { return false; } return true; } exports.isChord = isChord; // Strip b or # function degreeNumber(degree) { if (degree[0] !== '#' && degree[0] !== 'b') { return parseInt(degree); } else { return parseInt(degree.slice(1, degree.length)); } } function generateChordName(tonic, type) { let name = `${tonic}${type.base}${type.extension || ''}`; if (type.alterations) { type.alterations.forEach(alteration => { name = name.concat(alteration); }); } if (type.additions) { type.additions.forEach(addition => { name = name.concat(`add${addition}`); }); } if (type.slash) { const slashNote = (0, key_1.key)(tonic, 'Ionian').noteByDegree[chordDegreeToModeDegree(type.slash)]; name = name.concat(`/${slashNote}`); } return name; } function insertDegree(degrees, degree) { const degreesCopy = (0, utils_1.getShallowCopy)(degrees); // Start at the end and work backwards for (let i = degreesCopy.length - 1; i >= 0; i--) { const chordDegree = degreesCopy[i]; if (degreeNumber(chordDegree) < degreeNumber(degree)) { degreesCopy.splice(i + 1, 0, degree); break; } } return degreesCopy; } function chord(tonic, type) { const chordKey = (0, key_1.key)(tonic, 'Ionian'); const extensionIndex = type.extension ? Math.ceil(parseInt(type.extension) / 2) : 3; let chordDegrees = chordDegreesByChordBase[type.base].slice(0, extensionIndex); if (type.additions) { type.additions.forEach(addition => { chordDegrees = insertDegree(chordDegrees, addition); }); } if (type.alterations) { type.alterations.forEach(alteration => { const unalteredIndex = chordDegrees.findIndex(degree => degree === alteration[1]); if (unalteredIndex) { chordDegrees = (0, utils_1.removeArrayElement)(chordDegrees, unalteredIndex); } chordDegrees = insertDegree(chordDegrees, alteration); }); } if (type.slash) { let slashDegreeIndex = chordDegrees.findIndex(degree => degree === type.slash); // Insert our slash degree, if it doesn't already exist if (slashDegreeIndex === -1) { chordDegrees = insertDegree(chordDegrees, type.slash); } slashDegreeIndex = chordDegrees.findIndex(degree => degree === type.slash); if (slashDegreeIndex) { chordDegrees = (0, utils_1.rotateArray)(chordDegrees, slashDegreeIndex); } else { // This should never happen console.error('There was a problem finding the slash degree index.'); } } const modeDegrees = chordDegreesToModeDegrees(chordDegrees); const notes = modeDegrees.map(degree => chordKey.noteByDegree[degree]); const intervals = []; for (let i = 0; i < notes.length; i++) { // For the sake of chords, the tonic is always a perfect unison if (notes[i] === tonic) { intervals.push((0, interval_1.interval)(0).shortName); } else { intervals.push((0, interval_1.interval)((0, interval_1.getIntervalBetweenNotes)(tonic, notes[i])).shortName); } } return { ...type, root: tonic, notes: notes, name: generateChordName(tonic, type), intervals: intervals, degrees: chordDegrees, slash: type.slash, slashNote: type.slash ? chordKey.noteByDegree[chordDegreeToModeDegree(type.slash)] : undefined }; } exports.chord = chord;