12tet
Version:
Music theory library for generating and working with chords, modes, intervals, etc.
253 lines (252 loc) • 9.42 kB
JavaScript
;
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;