tonal-chord
Version:
Music chords creation and manipulation
194 lines (176 loc) • 5.85 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var tonalNote = require('tonal-note');
var tonalDistance = require('tonal-distance');
var tonalDictionary = require('tonal-dictionary');
var tonalPcset = require('tonal-pcset');
/**
* [](https://www.npmjs.com/package/tonal-chord)
* [](https://www.npmjs.com/browse/keyword/tonal)
*
* `tonal-chord` is a collection of functions to manipulate musical chords
*
* This is part of [tonal](https://www.npmjs.com/package/tonal) music theory library.
*
* @example
* // es6
* import * as Chord from "tonal-chord"
* // es5
* const Chord = require("tonal-chord")
*
* @example
* Chord.notes("CMaj7") // => ["C", "E", "G", "B"]
*
* @module Chord
*/
/**
* Return the available chord names
*
* @function
* @param {boolean} aliases - true to include aliases
* @return {Array} the chord names
*
* @example
* Chord.names() // => ["maj7", ...]
*/
var names = tonalDictionary.chord.names;
var NO_CHORD = Object.freeze({
name: null,
names: [],
intervals: [],
chroma: null,
setnum: null
});
var properties = function (name) {
var intervals = tonalDictionary.chord(name);
if (!intervals) { return NO_CHORD; }
var s = { intervals: intervals, name: name };
s.chroma = tonalPcset.chroma(intervals);
s.setnum = parseInt(s.chroma, 2);
s.names = tonalDictionary.chord.names(s.chroma);
return s;
};
var memo = function (fn, cache) {
if ( cache === void 0 ) cache = {};
return function (str) { return cache[str] || (cache[str] = fn(str)); };
};
/**
* Get chord properties. It returns an object with:
*
* - name: the chord name
* - names: a list with all possible names (includes the current)
* - intervals: an array with the chord intervals
* - chroma: chord croma (see pcset)
* - setnum: chord chroma number
*
* @function
* @param {string} name - the chord name (without tonic)
* @return {Object} an object with the properties or a object with all properties
* set to null if not valid chord name
*/
var props = memo(properties);
/**
* Get chord intervals. It always returns an array
*
* @function
* @param {string} name - the chord name (optionally a tonic and type)
* @return {Array<String>} a list of intervals or null if the type is not known
*/
var intervals = function (name) { return props(tokenize(name)[1]).intervals; };
/**
* Get the chord notes of a chord. This function accepts either a chord name
* (for example: "Cmaj7") or a list of notes.
*
* It always returns an array, even if the chord is not found.
*
* @function
* @param {string} nameOrTonic - name of the chord or the tonic (if the second parameter is present)
* @param {string} [name] - (Optional) name if the first parameter is the tonic
* @return {Array} an array of notes or an empty array
*
* @example
* Chord.notes("Cmaj7") // => ["C", "E", "G", "B"]
* Chord.notes("C", "maj7") // => ["C", "E", "G", "B"]
*/
function notes(nameOrTonic, name) {
if (name) { return props(name).intervals.map(tonalDistance.transpose(nameOrTonic)); }
var ref = tokenize(nameOrTonic);
var tonic = ref[0];
var type = ref[1];
return props(type).intervals.map(tonalDistance.transpose(tonic));
}
/**
* Check if a given name correspond to a chord in the dictionary
*
* @function
* @param {string} name
* @return {Boolean}
* @example
* Chord.exists("CMaj7") // => true
* Chord.exists("Maj7") // => true
* Chord.exists("Ablah") // => false
*/
var exists = function (name) { return tonalDictionary.chord(tokenize(name)[1]) !== undefined; };
/**
* Get all chords names that are a superset of the given one
* (has the same notes and at least one more)
*
* @function
* @param {string} name
* @return {Array} a list of chord names
*/
var supersets = function (name) {
if (!intervals(name).length) { return []; }
var isSuperset = tonalPcset.isSupersetOf(intervals(name));
return tonalDictionary.chord.names().filter(function (name) { return isSuperset(tonalDictionary.chord(name)); });
};
/**
* Find all chords names that are a subset of the given one
* (has less notes but all from the given chord)
*
* @function
* @param {string} name
* @return {Array} a list of chord names
*/
var subsets = function (name) {
var isSubset = tonalPcset.isSubsetOf(intervals(name));
return tonalDictionary.chord.names().filter(function (name) { return isSubset(tonalDictionary.chord(name)); });
};
// 6, 64, 7, 9, 11 and 13 are consider part of the chord
// (see https://github.com/danigb/tonal/issues/55)
var NUM_TYPES = /^(6|64|7|9|11|13)$/;
/**
* Tokenize a chord name. It returns an array with the tonic and chord type
* If not tonic is found, all the name is considered the chord name.
*
* This function does NOT check if the chord type exists or not. It only tries
* to split the tonic and chord type.
*
* @function
* @param {string} name - the chord name
* @return {Array} an array with [tonic, type]
* @example
* Chord.tokenize("Cmaj7") // => [ "C", "maj7" ]
* Chord.tokenize("C7") // => [ "C", "7" ]
* Chord.tokenize("mMaj7") // => [ "", "mMaj7" ]
* Chord.tokenize("Cnonsense") // => [ "C", "nonsense" ]
*/
function tokenize(name) {
var p = tonalNote.tokenize(name);
if (p[0] === "") { return ["", name]; }
// aug is augmented (see https://github.com/danigb/tonal/issues/55)
if (p[0] === "A" && p[3] === "ug") { return ["", "aug"]; }
if (NUM_TYPES.test(p[2])) {
return [p[0] + p[1], p[2] + p[3]];
} else {
return [p[0] + p[1] + p[2], p[3]];
}
}
exports.names = names;
exports.props = props;
exports.intervals = intervals;
exports.notes = notes;
exports.exists = exists;
exports.supersets = supersets;
exports.subsets = subsets;
exports.tokenize = tokenize;