tonal-key
Version:
Conversion between key numbers and note names
304 lines (277 loc) • 9.31 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var tonalArray = require('tonal-array');
var tonalNote = require('tonal-note');
var tonalDistance = require('tonal-distance');
var tonalRomanNumeral = require('tonal-roman-numeral');
var arguments$1 = arguments;
var MODES = "major dorian phrygian lydian mixolydian minor locrian ionian aeolian".split(
" "
);
var NUMS = [0, 1, 2, 3, 4, 5, 6, 0, 5];
var NOTES = "C D E F G A B".split(" ");
var TRIADS = ["", "m", "m", "", "", "m", "dim"];
var SEVENTHS = "Maj7 m7 m7 Maj7 7 m7 m7b5".split(" ");
var FIFTHS = [0, 2, 4, -1, 1, 3, 5, 0, 3];
var modenum = function (mode) { return NUMS[MODES.indexOf(mode)]; };
/**
* Get a list of valid mode names. The list of modes will be always in
* increasing order (ionian to locrian)
*
* @function
* @param {Boolean} alias - true to get aliases names
* @return {Array} an array of strings
* @example
* Key.modes() // => [ "ionian", "dorian", "phrygian", "lydian",
* // "mixolydian", "aeolian", "locrian" ]
* Key.modes(true) // => [ "ionian", "dorian", "phrygian", "lydian",
* // "mixolydian", "aeolian", "locrian", "major", "minor" ]
*/
var modeNames = function (aliases) { return aliases === true ? MODES.slice() : MODES.slice(0, 7); };
/**
* Create a major key from alterations
*
* @function
* @param {Integer} alt - the alteration number (positive sharps, negative flats)
* @return {Key} the key object
* @example
* Key.fromAlter(2) // => "D major"
*/
var fromAlter = function (i) { return tonalDistance.trFifths("C", i) + " major"; };
var names = function (alt) {
if ( alt === void 0 ) alt = 4;
alt = Math.abs(alt);
var result = [];
for (var i = -alt; i <= alt; i++) { result.push(fromAlter(i)); }
return result;
};
var NO_KEY = Object.freeze({
name: null,
tonic: null,
mode: null,
modenum: null,
intervals: [],
scale: [],
alt: null,
acc: null
});
var properties = function (name) {
var p = tokenize(name);
if (p[0] === null) { return NO_KEY; }
var k = { tonic: p[0], mode: p[1] };
k.name = k.tonic + " " + k.mode;
k.modenum = modenum(k.mode);
var cs = tonalArray.rotate(k.modenum, NOTES);
k.alt = tonalDistance.fifths("C", k.tonic) - FIFTHS[MODES.indexOf(k.mode)];
k.acc = tonalNote.altToAcc(k.alt);
k.intervals = cs.map(tonalDistance.interval(cs[0]));
k.scale = k.intervals.map(tonalDistance.transpose(k.tonic));
return Object.freeze(k);
};
var memo = function (fn, cache) {
if ( cache === void 0 ) cache = {};
return function (str) { return cache[str] || (cache[str] = fn(str)); };
};
/**
* Return the a key properties object with the following information:
*
* - name {string}: name
* - tonic {string}: key tonic
* - mode {string}: key mode
* - modenum {Number}: mode number (0 major, 1 dorian, ...)
* - intervals {Array}: the scale intervals
* - scale {Array}: the scale notes
* - acc {string}: accidentals of the key signature
* - alt {Number}: alteration number (a numeric representation of accidentals)
*
* @function
* @param {string} name - the key name
* @return {Object} the key properties object or null if not a valid key
*
* @example
* Key.props("C3 dorian") // => { tonic: "C", mode: "dorian", ... }
*/
var props = memo(properties);
/**
* Get scale of a key
*
* @function
* @param {string|Object} key
* @return {Array} the key scale
*
* @example
* Key.scale("A major") // => [ "A", "B", "C#", "D", "E", "F#", "G#" ]
* Key.scale("Bb minor") // => [ "Bb", "C", "Db", "Eb", "F", "Gb", "Ab" ]
* Key.scale("C dorian") // => [ "C", "D", "Eb", "F", "G", "A", "Bb" ]
* Key.scale("E mixolydian") // => [ "E", "F#", "G#", "A", "B", "C#", "D" ]
*/
var scale = function (str) { return props(str).scale; };
/**
* Get a list of key scale degrees in roman numerals
* @param {string} keyName
* @return {Array}
* @example
* Key.degrees("C major") => ["I", "ii", "iii", "IV", "V", "vi", "vii"]
*/
var degrees = function (str) {
var p = props(str);
if (p.name === null) { return []; }
var chords = tonalArray.rotate(p.modenum, SEVENTHS);
return chords.map(function (chord, i) {
return tonalRomanNumeral.fromDegree(i + 1, chord[0] !== "m");
});
};
/**
* Get a list of the altered notes of a given Key. The notes will be in
* the same order than in the key signature.
*
* @function
* @param {string} key - the key name
* @return {Array}
*
* @example
* Key.alteredNotes("Eb major") // => [ "Bb", "Eb", "Ab" ]
*/
var alteredNotes = function (name) {
var alt = props(name).alt;
if (alt === null) { return null; }
return alt === 0
? []
: alt > 0
? tonalArray.range(1, alt).map(tonalDistance.trFifths("B"))
: tonalArray.range(-1, alt).map(tonalDistance.trFifths("F"));
};
/**
* Get a lead-sheet symbols for a given key name
*
* This function is currified (so can be partially applied)
*
* From http://openmusictheory.com/triads.html
*
* A lead-sheet symbol begins with a capital letter (and, if necessary,
* an accidental) denoting the root of the chord.
* That letter is followed by information about a chord’s quality:
*
* - major triad: no quality symbol is added
* - minor triad: lower-case “m”
* - diminished triad: lower-case “dim” or a degree sign “°”
* - augmented triad: lower-case “aug” or a plus sign “+”
*
* @param {Array<string>} symbols - an array of symbols in major scale order
* @param {string} keyName - the name of the key you want the symbols for
* @param {Array<string>} [degrees] - the list of degrees. By default from 1 to 7 in ascending order
* @return {function}
* @see Key.chords
* @see Key.triads
*
* @example
* const chords = Key.leadsheetSymbols(["M", "m", "m", "M", "7", "m", "dim"])
* chords("D dorian") //=> ["Dm", "Em", "FM", "G7", "Am", "Bdim", "CM"]
* chords("D dorian", ['ii', 'V']) //=> [Em", "G7"]
*/
function leadsheetSymbols(symbols, keyName, degrees) {
if (arguments.length === 1) { return function (n, d) { return leadsheetSymbols(symbols, n, d); }; }
var p = props(keyName);
if (!p.name) { return []; }
var names = tonalArray.rotate(p.modenum, symbols);
var chords = p.scale.map(function (tonic, i) { return tonic + names[i]; });
if (!degrees) { return chords; }
return degrees.map(tonalRomanNumeral.decimal).map(function (n) { return chords[n - 1]; });
}
/**
* Get key seventh chords
*
* @function
* @param {string} name - the key name
* @param {Array<number|string>} [degrees] - can be numbers or roman numerals
* @return {Array<string>} seventh chord names
*
* @example
* Key.chords("A major") // => ["AMaj7", "Bm7", "C#m7", "DMaj7", ..,]
* Key.chords("A major", ['I', 'IV', 'V']) // => ["AMaj7", "DMaj7", "E7"]
* Key.chords("A major", [5, 4, 1]) // => ["E7", "DMaj7", AMaj7"]
*/
var chords = leadsheetSymbols(SEVENTHS);
/**
* Get key triads
*
* @function
* @param {string} name - the key name
* @param {Array<string|number>} [degrees]
* @return {Array<string>} triad names
*
* @example
* Key.triads("A major") // => ["AM", "Bm", "C#m", "DM", "E7", "F#m", "G#mb5"]
* Key.triads("A major", ['I', 'IV', 'V']) // => ["AMaj7", "DMaj7", "E7"]
* Key.triads("A major", [1, 4, 5]) // => ["AMaj7", "DMaj7", "E7"]
*/
var triads = leadsheetSymbols(TRIADS);
/**
* Get secondary dominant key chords
*
* @function
* @param {string} name - the key name
* @return {Array}
*
* @example
* Key.secDomChords("A major") // => ["E7", "F#7", ...]
*/
var secDomChords = function (name) {
var p = props(name);
if (!p.name) { return []; }
return p.scale.map(function (t) { return tonalDistance.transpose(t, "P5") + "7"; });
};
/**
* Get relative of a key. Two keys are relative when the have the same
* key signature (for example C major and A minor)
*
* It can be partially applied.
*
* @function
* @param {string} mode - the relative destination
* @param {string} key - the key source
*
* @example
* Key.relative("dorian", "B major") // => "C# dorian"
* // partial application
* var minor = Key.relative("minor")
* minor("C major") // => "A minor"
* minor("E major") // => "C# minor"
*/
var relative = function (mode, key) {
if (arguments$1.length === 1) { return function (key) { return relative(mode, key); }; }
var num = modenum(mode.toLowerCase());
if (num === undefined) { return null; }
var k = props(key);
if (k.name === null) { return null; }
return tonalDistance.trFifths(k.tonic, FIFTHS[num] - FIFTHS[k.modenum]) + " " + mode;
};
/**
* Split the key name into its components (pitch class tonic and mode name)
*
* @function
* @param {string} name
* @return {Array} an array in the form [tonic, key]
*
* @example
* Key.tokenize("C major") // => ["C", "major"]
*/
var tokenize = function (name) {
var p = tonalNote.tokenize(name);
p[3] = p[3].toLowerCase();
if (p[0] === "" || MODES.indexOf(p[3]) === -1) { return [null, null]; }
return [p[0] + p[1], p[3]];
};
exports.modeNames = modeNames;
exports.fromAlter = fromAlter;
exports.names = names;
exports.props = props;
exports.scale = scale;
exports.degrees = degrees;
exports.alteredNotes = alteredNotes;
exports.leadsheetSymbols = leadsheetSymbols;
exports.chords = chords;
exports.triads = triads;
exports.secDomChords = secDomChords;
exports.relative = relative;
exports.tokenize = tokenize;