Felix Roos b91eeb72f0 build
2022-02-06 21:00:29 +01:00

1723 lines
51 KiB
JavaScript

/**
* Fill a string with a repeated character
*
* @param character
* @param repetition
*/
const fillStr = (s, n) => Array(Math.abs(n) + 1).join(s);
function deprecate(original, alternative, fn) {
return function (...args) {
// tslint:disable-next-line
console.warn(`${original} is deprecated. Use ${alternative}.`);
return fn.apply(this, args);
};
}
function isNamed(src) {
return src !== null && typeof src === "object" && typeof src.name === "string"
? true
: false;
}
function isPitch(pitch) {
return pitch !== null &&
typeof pitch === "object" &&
typeof pitch.step === "number" &&
typeof pitch.alt === "number"
? true
: false;
}
// The number of fifths of [C, D, E, F, G, A, B]
const FIFTHS = [0, 2, 4, -1, 1, 3, 5];
// The number of octaves it span each step
const STEPS_TO_OCTS = FIFTHS.map((fifths) => Math.floor((fifths * 7) / 12));
function encode(pitch) {
const { step, alt, oct, dir = 1 } = pitch;
const f = FIFTHS[step] + 7 * alt;
if (oct === undefined) {
return [dir * f];
}
const o = oct - STEPS_TO_OCTS[step] - 4 * alt;
return [dir * f, dir * o];
}
// We need to get the steps from fifths
// Fifths for CDEFGAB are [ 0, 2, 4, -1, 1, 3, 5 ]
// We add 1 to fifths to avoid negative numbers, so:
// for ["F", "C", "G", "D", "A", "E", "B"] we have:
const FIFTHS_TO_STEPS = [3, 0, 4, 1, 5, 2, 6];
function decode(coord) {
const [f, o, dir] = coord;
const step = FIFTHS_TO_STEPS[unaltered(f)];
const alt = Math.floor((f + 1) / 7);
if (o === undefined) {
return { step, alt, dir };
}
const oct = o + 4 * alt + STEPS_TO_OCTS[step];
return { step, alt, oct, dir };
}
// Return the number of fifths as if it were unaltered
function unaltered(f) {
const i = (f + 1) % 7;
return i < 0 ? 7 + i : i;
}
const NoNote = { empty: true, name: "", pc: "", acc: "" };
const cache$1 = new Map();
const stepToLetter = (step) => "CDEFGAB".charAt(step);
const altToAcc = (alt) => alt < 0 ? fillStr("b", -alt) : fillStr("#", alt);
const accToAlt = (acc) => acc[0] === "b" ? -acc.length : acc.length;
/**
* Given a note literal (a note name or a note object), returns the Note object
* @example
* note('Bb4') // => { name: "Bb4", midi: 70, chroma: 10, ... }
*/
function note(src) {
const cached = cache$1.get(src);
if (cached) {
return cached;
}
const value = typeof src === "string"
? parse$1(src)
: isPitch(src)
? note(pitchName$1(src))
: isNamed(src)
? note(src.name)
: NoNote;
cache$1.set(src, value);
return value;
}
const REGEX$1 = /^([a-gA-G]?)(#{1,}|b{1,}|x{1,}|)(-?\d*)\s*(.*)$/;
/**
* @private
*/
function tokenizeNote(str) {
const m = REGEX$1.exec(str);
return [m[1].toUpperCase(), m[2].replace(/x/g, "##"), m[3], m[4]];
}
/**
* @private
*/
function coordToNote(noteCoord) {
return note(decode(noteCoord));
}
const mod = (n, m) => ((n % m) + m) % m;
const SEMI = [0, 2, 4, 5, 7, 9, 11];
function parse$1(noteName) {
const tokens = tokenizeNote(noteName);
if (tokens[0] === "" || tokens[3] !== "") {
return NoNote;
}
const letter = tokens[0];
const acc = tokens[1];
const octStr = tokens[2];
const step = (letter.charCodeAt(0) + 3) % 7;
const alt = accToAlt(acc);
const oct = octStr.length ? +octStr : undefined;
const coord = encode({ step, alt, oct });
const name = letter + acc + octStr;
const pc = letter + acc;
const chroma = (SEMI[step] + alt + 120) % 12;
const height = oct === undefined
? mod(SEMI[step] + alt, 12) - 12 * 99
: SEMI[step] + alt + 12 * (oct + 1);
const midi = height >= 0 && height <= 127 ? height : null;
const freq = oct === undefined ? null : Math.pow(2, (height - 69) / 12) * 440;
return {
empty: false,
acc,
alt,
chroma,
coord,
freq,
height,
letter,
midi,
name,
oct,
pc,
step,
};
}
function pitchName$1(props) {
const { step, alt, oct } = props;
const letter = stepToLetter(step);
if (!letter) {
return "";
}
const pc = letter + altToAcc(alt);
return oct || oct === 0 ? pc + oct : pc;
}
const NoInterval = { empty: true, name: "", acc: "" };
// shorthand tonal notation (with quality after number)
const INTERVAL_TONAL_REGEX = "([-+]?\\d+)(d{1,4}|m|M|P|A{1,4})";
// standard shorthand notation (with quality before number)
const INTERVAL_SHORTHAND_REGEX = "(AA|A|P|M|m|d|dd)([-+]?\\d+)";
const REGEX = new RegExp("^" + INTERVAL_TONAL_REGEX + "|" + INTERVAL_SHORTHAND_REGEX + "$");
/**
* @private
*/
function tokenizeInterval(str) {
const m = REGEX.exec(`${str}`);
if (m === null) {
return ["", ""];
}
return m[1] ? [m[1], m[2]] : [m[4], m[3]];
}
const cache = {};
/**
* Get interval properties. It returns an object with:
*
* - name: the interval name
* - num: the interval number
* - type: 'perfectable' or 'majorable'
* - q: the interval quality (d, m, M, A)
* - dir: interval direction (1 ascending, -1 descending)
* - simple: the simplified number
* - semitones: the size in semitones
* - chroma: the interval chroma
*
* @param {string} interval - the interval name
* @return {Object} the interval properties
*
* @example
* import { interval } from '@tonaljs/core'
* interval('P5').semitones // => 7
* interval('m3').type // => 'majorable'
*/
function interval(src) {
return typeof src === "string"
? cache[src] || (cache[src] = parse(src))
: isPitch(src)
? interval(pitchName(src))
: isNamed(src)
? interval(src.name)
: NoInterval;
}
const SIZES = [0, 2, 4, 5, 7, 9, 11];
const TYPES = "PMMPPMM";
function parse(str) {
const tokens = tokenizeInterval(str);
if (tokens[0] === "") {
return NoInterval;
}
const num = +tokens[0];
const q = tokens[1];
const step = (Math.abs(num) - 1) % 7;
const t = TYPES[step];
if (t === "M" && q === "P") {
return NoInterval;
}
const type = t === "M" ? "majorable" : "perfectable";
const name = "" + num + q;
const dir = num < 0 ? -1 : 1;
const simple = num === 8 || num === -8 ? num : dir * (step + 1);
const alt = qToAlt(type, q);
const oct = Math.floor((Math.abs(num) - 1) / 7);
const semitones = dir * (SIZES[step] + alt + 12 * oct);
const chroma = (((dir * (SIZES[step] + alt)) % 12) + 12) % 12;
const coord = encode({ step, alt, oct, dir });
return {
empty: false,
name,
num,
q,
step,
alt,
dir,
type,
simple,
semitones,
chroma,
coord,
oct,
};
}
/**
* @private
*
* forceDescending is used in the case of unison (#243)
*/
function coordToInterval(coord, forceDescending) {
const [f, o = 0] = coord;
const isDescending = f * 7 + o * 12 < 0;
const ivl = forceDescending || isDescending ? [-f, -o, -1] : [f, o, 1];
return interval(decode(ivl));
}
function qToAlt(type, q) {
return (q === "M" && type === "majorable") ||
(q === "P" && type === "perfectable")
? 0
: q === "m" && type === "majorable"
? -1
: /^A+$/.test(q)
? q.length
: /^d+$/.test(q)
? -1 * (type === "perfectable" ? q.length : q.length + 1)
: 0;
}
// return the interval name of a pitch
function pitchName(props) {
const { step, alt, oct = 0, dir } = props;
if (!dir) {
return "";
}
const calcNum = step + 1 + 7 * oct;
// this is an edge case: descending pitch class unison (see #243)
const num = calcNum === 0 ? step + 1 : calcNum;
const d = dir < 0 ? "-" : "";
const type = TYPES[step] === "M" ? "majorable" : "perfectable";
const name = d + num + altToQ(type, alt);
return name;
}
function altToQ(type, alt) {
if (alt === 0) {
return type === "majorable" ? "M" : "P";
}
else if (alt === -1 && type === "majorable") {
return "m";
}
else if (alt > 0) {
return fillStr("A", alt);
}
else {
return fillStr("d", type === "perfectable" ? alt : alt + 1);
}
}
/**
* Transpose a note by an interval.
*
* @param {string} note - the note or note name
* @param {string} interval - the interval or interval name
* @return {string} the transposed note name or empty string if not valid notes
* @example
* import { tranpose } from "@tonaljs/core"
* transpose("d3", "3M") // => "F#3"
* transpose("D", "3M") // => "F#"
* ["C", "D", "E", "F", "G"].map(pc => transpose(pc, "M3)) // => ["E", "F#", "G#", "A", "B"]
*/
function transpose(noteName, intervalName) {
const note$1 = note(noteName);
const interval$1 = interval(intervalName);
if (note$1.empty || interval$1.empty) {
return "";
}
const noteCoord = note$1.coord;
const intervalCoord = interval$1.coord;
const tr = noteCoord.length === 1
? [noteCoord[0] + intervalCoord[0]]
: [noteCoord[0] + intervalCoord[0], noteCoord[1] + intervalCoord[1]];
return coordToNote(tr).name;
}
/**
* Find the interval distance between two notes or coord classes.
*
* To find distance between coord classes, both notes must be coord classes and
* the interval is always ascending
*
* @param {Note|string} from - the note or note name to calculate distance from
* @param {Note|string} to - the note or note name to calculate distance to
* @return {string} the interval name or empty string if not valid notes
*
*/
function distance(fromNote, toNote) {
const from = note(fromNote);
const to = note(toNote);
if (from.empty || to.empty) {
return "";
}
const fcoord = from.coord;
const tcoord = to.coord;
const fifths = tcoord[0] - fcoord[0];
const octs = fcoord.length === 2 && tcoord.length === 2
? tcoord[1] - fcoord[1]
: -Math.floor((fifths * 7) / 12);
// If it's unison and not pitch class, it can be descending interval (#243)
const forceDescending = to.height === from.height &&
to.midi !== null &&
from.midi !== null &&
from.step > to.step;
return coordToInterval([fifths, octs], forceDescending).name;
}
// ascending range
function ascR(b, n) {
const a = [];
// tslint:disable-next-line:curly
for (; n--; a[n] = n + b)
;
return a;
}
// descending range
function descR(b, n) {
const a = [];
// tslint:disable-next-line:curly
for (; n--; a[n] = b - n)
;
return a;
}
/**
* Creates a numeric range
*
* @param {number} from
* @param {number} to
* @return {Array<number>}
*
* @example
* range(-2, 2) // => [-2, -1, 0, 1, 2]
* range(2, -2) // => [2, 1, 0, -1, -2]
*/
function range(from, to) {
return from < to ? ascR(from, to - from + 1) : descR(from, from - to + 1);
}
/**
* Rotates a list a number of times. It"s completly agnostic about the
* contents of the list.
*
* @param {Integer} times - the number of rotations
* @param {Array} collection
* @return {Array} the rotated collection
*
* @example
* rotate(1, [1, 2, 3]) // => [2, 3, 1]
*/
function rotate(times, arr) {
const len = arr.length;
const n = ((times % len) + len) % len;
return arr.slice(n, len).concat(arr.slice(0, n));
}
/**
* Return a copy of the collection with the null values removed
* @function
* @param {Array} collection
* @return {Array}
*
* @example
* compact(["a", "b", null, "c"]) // => ["a", "b", "c"]
*/
function compact(arr) {
return arr.filter((n) => n === 0 || n);
}
const EmptyPcset = {
empty: true,
name: "",
setNum: 0,
chroma: "000000000000",
normalized: "000000000000",
intervals: [],
};
// UTILITIES
const setNumToChroma = (num) => Number(num).toString(2);
const chromaToNumber = (chroma) => parseInt(chroma, 2);
const REGEX$2 = /^[01]{12}$/;
function isChroma(set) {
return REGEX$2.test(set);
}
const isPcsetNum = (set) => typeof set === "number" && set >= 0 && set <= 4095;
const isPcset = (set) => set && isChroma(set.chroma);
const cache$2 = { [EmptyPcset.chroma]: EmptyPcset };
/**
* Get the pitch class set of a collection of notes or set number or chroma
*/
function get(src) {
const chroma = isChroma(src)
? src
: isPcsetNum(src)
? setNumToChroma(src)
: Array.isArray(src)
? listToChroma(src)
: isPcset(src)
? src.chroma
: EmptyPcset.chroma;
return (cache$2[chroma] = cache$2[chroma] || chromaToPcset(chroma));
}
const IVLS = [
"1P",
"2m",
"2M",
"3m",
"3M",
"4P",
"5d",
"5P",
"6m",
"6M",
"7m",
"7M",
];
/**
* @private
* Get the intervals of a pcset *starting from C*
* @param {Set} set - the pitch class set
* @return {IntervalName[]} an array of interval names or an empty array
* if not a valid pitch class set
*/
function chromaToIntervals(chroma) {
const intervals = [];
for (let i = 0; i < 12; i++) {
// tslint:disable-next-line:curly
if (chroma.charAt(i) === "1")
intervals.push(IVLS[i]);
}
return intervals;
}
/**
* Given a a list of notes or a pcset chroma, produce the rotations
* of the chroma discarding the ones that starts with "0"
*
* This is used, for example, to get all the modes of a scale.
*
* @param {Array|string} set - the list of notes or pitchChr of the set
* @param {boolean} normalize - (Optional, true by default) remove all
* the rotations that starts with "0"
* @return {Array<string>} an array with all the modes of the chroma
*
* @example
* Pcset.modes(["C", "D", "E"]).map(Pcset.intervals)
*/
function modes(set, normalize = true) {
const pcs = get(set);
const binary = pcs.chroma.split("");
return compact(binary.map((_, i) => {
const r = rotate(i, binary);
return normalize && r[0] === "0" ? null : r.join("");
}));
}
/**
* Create a function that test if a collection of notes is a
* subset of a given set
*
* The function is curryfied.
*
* @param {PcsetChroma|NoteName[]} set - the superset to test against (chroma or
* list of notes)
* @return{function(PcsetChroma|NoteNames[]): boolean} a function accepting a set
* to test against (chroma or list of notes)
* @example
* const inCMajor = Pcset.isSubsetOf(["C", "E", "G"])
* inCMajor(["e6", "c4"]) // => true
* inCMajor(["e6", "c4", "d3"]) // => false
*/
function isSubsetOf(set) {
const s = get(set).setNum;
return (notes) => {
const o = get(notes).setNum;
// tslint:disable-next-line: no-bitwise
return s && s !== o && (o & s) === o;
};
}
/**
* Create a function that test if a collection of notes is a
* superset of a given set (it contains all notes and at least one more)
*
* @param {Set} set - an array of notes or a chroma set string to test against
* @return {(subset: Set): boolean} a function that given a set
* returns true if is a subset of the first one
* @example
* const extendsCMajor = Pcset.isSupersetOf(["C", "E", "G"])
* extendsCMajor(["e6", "a", "c4", "g2"]) // => true
* extendsCMajor(["c6", "e4", "g3"]) // => false
*/
function isSupersetOf(set) {
const s = get(set).setNum;
return (notes) => {
const o = get(notes).setNum;
// tslint:disable-next-line: no-bitwise
return s && s !== o && (o | s) === o;
};
}
//// PRIVATE ////
function chromaRotations(chroma) {
const binary = chroma.split("");
return binary.map((_, i) => rotate(i, binary).join(""));
}
function chromaToPcset(chroma) {
const setNum = chromaToNumber(chroma);
const normalizedNum = chromaRotations(chroma)
.map(chromaToNumber)
.filter((n) => n >= 2048)
.sort()[0];
const normalized = setNumToChroma(normalizedNum);
const intervals = chromaToIntervals(chroma);
return {
empty: false,
name: "",
setNum,
chroma,
normalized,
intervals,
};
}
function listToChroma(set) {
if (set.length === 0) {
return EmptyPcset.chroma;
}
let pitch;
const binary = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < set.length; i++) {
pitch = note(set[i]);
// tslint:disable-next-line: curly
if (pitch.empty)
pitch = interval(set[i]);
// tslint:disable-next-line: curly
if (!pitch.empty)
binary[pitch.chroma] = 1;
}
return binary.join("");
}
/**
* @private
* Chord List
* Source: https://en.wikibooks.org/wiki/Music_Theory/Complete_List_of_Chord_Patterns
* Format: ["intervals", "full name", "abrv1 abrv2"]
*/
const CHORDS = [
// ==Major==
["1P 3M 5P", "major", "M ^ "],
["1P 3M 5P 7M", "major seventh", "maj7 Δ ma7 M7 Maj7 ^7"],
["1P 3M 5P 7M 9M", "major ninth", "maj9 Δ9 ^9"],
["1P 3M 5P 7M 9M 13M", "major thirteenth", "maj13 Maj13 ^13"],
["1P 3M 5P 6M", "sixth", "6 add6 add13 M6"],
["1P 3M 5P 6M 9M", "sixth/ninth", "6/9 69 M69"],
["1P 3M 6m 7M", "major seventh flat sixth", "M7b6 ^7b6"],
[
"1P 3M 5P 7M 11A",
"major seventh sharp eleventh",
"maj#4 Δ#4 Δ#11 M7#11 ^7#11 maj7#11",
],
// ==Minor==
// '''Normal'''
["1P 3m 5P", "minor", "m min -"],
["1P 3m 5P 7m", "minor seventh", "m7 min7 mi7 -7"],
[
"1P 3m 5P 7M",
"minor/major seventh",
"m/ma7 m/maj7 mM7 mMaj7 m/M7 -Δ7 mΔ -^7",
],
["1P 3m 5P 6M", "minor sixth", "m6 -6"],
["1P 3m 5P 7m 9M", "minor ninth", "m9 -9"],
["1P 3m 5P 7M 9M", "minor/major ninth", "mM9 mMaj9 -^9"],
["1P 3m 5P 7m 9M 11P", "minor eleventh", "m11 -11"],
["1P 3m 5P 7m 9M 13M", "minor thirteenth", "m13 -13"],
// '''Diminished'''
["1P 3m 5d", "diminished", "dim ° o"],
["1P 3m 5d 7d", "diminished seventh", "dim7 °7 o7"],
["1P 3m 5d 7m", "half-diminished", "m7b5 ø -7b5 h7 h"],
// ==Dominant/Seventh==
// '''Normal'''
["1P 3M 5P 7m", "dominant seventh", "7 dom"],
["1P 3M 5P 7m 9M", "dominant ninth", "9"],
["1P 3M 5P 7m 9M 13M", "dominant thirteenth", "13"],
["1P 3M 5P 7m 11A", "lydian dominant seventh", "7#11 7#4"],
// '''Altered'''
["1P 3M 5P 7m 9m", "dominant flat ninth", "7b9"],
["1P 3M 5P 7m 9A", "dominant sharp ninth", "7#9"],
["1P 3M 7m 9m", "altered", "alt7"],
// '''Suspended'''
["1P 4P 5P", "suspended fourth", "sus4 sus"],
["1P 2M 5P", "suspended second", "sus2"],
["1P 4P 5P 7m", "suspended fourth seventh", "7sus4 7sus"],
["1P 5P 7m 9M 11P", "eleventh", "11"],
[
"1P 4P 5P 7m 9m",
"suspended fourth flat ninth",
"b9sus phryg 7b9sus 7b9sus4",
],
// ==Other==
["1P 5P", "fifth", "5"],
["1P 3M 5A", "augmented", "aug + +5 ^#5"],
["1P 3m 5A", "minor augmented", "m#5 -#5 m+"],
["1P 3M 5A 7M", "augmented seventh", "maj7#5 maj7+5 +maj7 ^7#5"],
[
"1P 3M 5P 7M 9M 11A",
"major sharp eleventh (lydian)",
"maj9#11 Δ9#11 ^9#11",
],
// ==Legacy==
["1P 2M 4P 5P", "", "sus24 sus4add9"],
["1P 3M 5A 7M 9M", "", "maj9#5 Maj9#5"],
["1P 3M 5A 7m", "", "7#5 +7 7+ 7aug aug7"],
["1P 3M 5A 7m 9A", "", "7#5#9 7#9#5 7alt"],
["1P 3M 5A 7m 9M", "", "9#5 9+"],
["1P 3M 5A 7m 9M 11A", "", "9#5#11"],
["1P 3M 5A 7m 9m", "", "7#5b9 7b9#5"],
["1P 3M 5A 7m 9m 11A", "", "7#5b9#11"],
["1P 3M 5A 9A", "", "+add#9"],
["1P 3M 5A 9M", "", "M#5add9 +add9"],
["1P 3M 5P 6M 11A", "", "M6#11 M6b5 6#11 6b5"],
["1P 3M 5P 6M 7M 9M", "", "M7add13"],
["1P 3M 5P 6M 9M 11A", "", "69#11"],
["1P 3m 5P 6M 9M", "", "m69 -69"],
["1P 3M 5P 6m 7m", "", "7b6"],
["1P 3M 5P 7M 9A 11A", "", "maj7#9#11"],
["1P 3M 5P 7M 9M 11A 13M", "", "M13#11 maj13#11 M13+4 M13#4"],
["1P 3M 5P 7M 9m", "", "M7b9"],
["1P 3M 5P 7m 11A 13m", "", "7#11b13 7b5b13"],
["1P 3M 5P 7m 13M", "", "7add6 67 7add13"],
["1P 3M 5P 7m 9A 11A", "", "7#9#11 7b5#9 7#9b5"],
["1P 3M 5P 7m 9A 11A 13M", "", "13#9#11"],
["1P 3M 5P 7m 9A 11A 13m", "", "7#9#11b13"],
["1P 3M 5P 7m 9A 13M", "", "13#9"],
["1P 3M 5P 7m 9A 13m", "", "7#9b13"],
["1P 3M 5P 7m 9M 11A", "", "9#11 9+4 9#4"],
["1P 3M 5P 7m 9M 11A 13M", "", "13#11 13+4 13#4"],
["1P 3M 5P 7m 9M 11A 13m", "", "9#11b13 9b5b13"],
["1P 3M 5P 7m 9m 11A", "", "7b9#11 7b5b9 7b9b5"],
["1P 3M 5P 7m 9m 11A 13M", "", "13b9#11"],
["1P 3M 5P 7m 9m 11A 13m", "", "7b9b13#11 7b9#11b13 7b5b9b13"],
["1P 3M 5P 7m 9m 13M", "", "13b9"],
["1P 3M 5P 7m 9m 13m", "", "7b9b13"],
["1P 3M 5P 7m 9m 9A", "", "7b9#9"],
["1P 3M 5P 9M", "", "Madd9 2 add9 add2"],
["1P 3M 5P 9m", "", "Maddb9"],
["1P 3M 5d", "", "Mb5"],
["1P 3M 5d 6M 7m 9M", "", "13b5"],
["1P 3M 5d 7M", "", "M7b5"],
["1P 3M 5d 7M 9M", "", "M9b5"],
["1P 3M 5d 7m", "", "7b5"],
["1P 3M 5d 7m 9M", "", "9b5"],
["1P 3M 7m", "", "7no5"],
["1P 3M 7m 13m", "", "7b13"],
["1P 3M 7m 9M", "", "9no5"],
["1P 3M 7m 9M 13M", "", "13no5"],
["1P 3M 7m 9M 13m", "", "9b13"],
["1P 3m 4P 5P", "", "madd4"],
["1P 3m 5P 6m 7M", "", "mMaj7b6"],
["1P 3m 5P 6m 7M 9M", "", "mMaj9b6"],
["1P 3m 5P 7m 11P", "", "m7add11 m7add4"],
["1P 3m 5P 9M", "", "madd9"],
["1P 3m 5d 6M 7M", "", "o7M7"],
["1P 3m 5d 7M", "", "oM7"],
["1P 3m 6m 7M", "", "mb6M7"],
["1P 3m 6m 7m", "", "m7#5"],
["1P 3m 6m 7m 9M", "", "m9#5"],
["1P 3m 5A 7m 9M 11P", "", "m11A"],
["1P 3m 6m 9m", "", "mb6b9"],
["1P 2M 3m 5d 7m", "", "m9b5"],
["1P 4P 5A 7M", "", "M7#5sus4"],
["1P 4P 5A 7M 9M", "", "M9#5sus4"],
["1P 4P 5A 7m", "", "7#5sus4"],
["1P 4P 5P 7M", "", "M7sus4"],
["1P 4P 5P 7M 9M", "", "M9sus4"],
["1P 4P 5P 7m 9M", "", "9sus4 9sus"],
["1P 4P 5P 7m 9M 13M", "", "13sus4 13sus"],
["1P 4P 5P 7m 9m 13m", "", "7sus4b9b13 7b9b13sus4"],
["1P 4P 7m 10m", "", "4 quartal"],
["1P 5P 7m 9m 11P", "", "11b9"],
];
let dictionary = [];
/**
* Return a list of all chord types
*/
function all() {
return dictionary.slice();
}
/**
* Add a chord to the dictionary.
* @param intervals
* @param aliases
* @param [fullName]
*/
function add(intervals, aliases, fullName) {
const quality = getQuality(intervals);
const chord = {
...get(intervals),
name: fullName || "",
quality,
intervals,
aliases,
};
dictionary.push(chord);
chord.aliases.forEach((alias) => addAlias());
}
function addAlias(chord, alias) {
}
function getQuality(intervals) {
const has = (interval) => intervals.indexOf(interval) !== -1;
return has("5A")
? "Augmented"
: has("3M")
? "Major"
: has("5d")
? "Diminished"
: has("3m")
? "Minor"
: "Unknown";
}
CHORDS.forEach(([ivls, fullName, names]) => add(ivls.split(" "), names.split(" "), fullName));
dictionary.sort((a, b) => a.setNum - b.setNum);
// SCALES
// Format: ["intervals", "name", "alias1", "alias2", ...]
const SCALES = [
// 5-note scales
["1P 2M 3M 5P 6M", "major pentatonic", "pentatonic"],
["1P 3M 4P 5P 7M", "ionian pentatonic"],
["1P 3M 4P 5P 7m", "mixolydian pentatonic", "indian"],
["1P 2M 4P 5P 6M", "ritusen"],
["1P 2M 4P 5P 7m", "egyptian"],
["1P 3M 4P 5d 7m", "neopolitan major pentatonic"],
["1P 3m 4P 5P 6m", "vietnamese 1"],
["1P 2m 3m 5P 6m", "pelog"],
["1P 2m 4P 5P 6m", "kumoijoshi"],
["1P 2M 3m 5P 6m", "hirajoshi"],
["1P 2m 4P 5d 7m", "iwato"],
["1P 2m 4P 5P 7m", "in-sen"],
["1P 3M 4A 5P 7M", "lydian pentatonic", "chinese"],
["1P 3m 4P 6m 7m", "malkos raga"],
["1P 3m 4P 5d 7m", "locrian pentatonic", "minor seven flat five pentatonic"],
["1P 3m 4P 5P 7m", "minor pentatonic", "vietnamese 2"],
["1P 3m 4P 5P 6M", "minor six pentatonic"],
["1P 2M 3m 5P 6M", "flat three pentatonic", "kumoi"],
["1P 2M 3M 5P 6m", "flat six pentatonic"],
["1P 2m 3M 5P 6M", "scriabin"],
["1P 3M 5d 6m 7m", "whole tone pentatonic"],
["1P 3M 4A 5A 7M", "lydian #5P pentatonic"],
["1P 3M 4A 5P 7m", "lydian dominant pentatonic"],
["1P 3m 4P 5P 7M", "minor #7M pentatonic"],
["1P 3m 4d 5d 7m", "super locrian pentatonic"],
// 6-note scales
["1P 2M 3m 4P 5P 7M", "minor hexatonic"],
["1P 2A 3M 5P 5A 7M", "augmented"],
["1P 2M 3m 3M 5P 6M", "major blues"],
["1P 2M 4P 5P 6M 7m", "piongio"],
["1P 2m 3M 4A 6M 7m", "prometheus neopolitan"],
["1P 2M 3M 4A 6M 7m", "prometheus"],
["1P 2m 3M 5d 6m 7m", "mystery #1"],
["1P 2m 3M 4P 5A 6M", "six tone symmetric"],
["1P 2M 3M 4A 5A 7m", "whole tone", "messiaen's mode #1"],
["1P 2m 4P 4A 5P 7M", "messiaen's mode #5"],
["1P 3m 4P 5d 5P 7m", "minor blues", "blues"],
// 7-note scales
["1P 2M 3M 4P 5d 6m 7m", "locrian major", "arabian"],
["1P 2m 3M 4A 5P 6m 7M", "double harmonic lydian"],
["1P 2M 3m 4P 5P 6m 7M", "harmonic minor"],
[
"1P 2m 2A 3M 4A 6m 7m",
"altered",
"super locrian",
"diminished whole tone",
"pomeroy",
],
["1P 2M 3m 4P 5d 6m 7m", "locrian #2", "half-diminished", "aeolian b5"],
[
"1P 2M 3M 4P 5P 6m 7m",
"mixolydian b6",
"melodic minor fifth mode",
"hindu",
],
["1P 2M 3M 4A 5P 6M 7m", "lydian dominant", "lydian b7", "overtone"],
["1P 2M 3M 4A 5P 6M 7M", "lydian"],
["1P 2M 3M 4A 5A 6M 7M", "lydian augmented"],
[
"1P 2m 3m 4P 5P 6M 7m",
"dorian b2",
"phrygian #6",
"melodic minor second mode",
],
["1P 2M 3m 4P 5P 6M 7M", "melodic minor"],
["1P 2m 3m 4P 5d 6m 7m", "locrian"],
[
"1P 2m 3m 4d 5d 6m 7d",
"ultralocrian",
"superlocrian bb7",
"superlocrian diminished",
],
["1P 2m 3m 4P 5d 6M 7m", "locrian 6", "locrian natural 6", "locrian sharp 6"],
["1P 2A 3M 4P 5P 5A 7M", "augmented heptatonic"],
// Source https://en.wikipedia.org/wiki/Ukrainian_Dorian_scale
[
"1P 2M 3m 4A 5P 6M 7m",
"dorian #4",
"ukrainian dorian",
"romanian minor",
"altered dorian",
],
["1P 2M 3m 4A 5P 6M 7M", "lydian diminished"],
["1P 2m 3m 4P 5P 6m 7m", "phrygian"],
["1P 2M 3M 4A 5A 7m 7M", "leading whole tone"],
["1P 2M 3M 4A 5P 6m 7m", "lydian minor"],
["1P 2m 3M 4P 5P 6m 7m", "phrygian dominant", "spanish", "phrygian major"],
["1P 2m 3m 4P 5P 6m 7M", "balinese"],
["1P 2m 3m 4P 5P 6M 7M", "neopolitan major"],
["1P 2M 3m 4P 5P 6m 7m", "aeolian", "minor"],
["1P 2M 3M 4P 5P 6m 7M", "harmonic major"],
["1P 2m 3M 4P 5P 6m 7M", "double harmonic major", "gypsy"],
["1P 2M 3m 4P 5P 6M 7m", "dorian"],
["1P 2M 3m 4A 5P 6m 7M", "hungarian minor"],
["1P 2A 3M 4A 5P 6M 7m", "hungarian major"],
["1P 2m 3M 4P 5d 6M 7m", "oriental"],
["1P 2m 3m 3M 4A 5P 7m", "flamenco"],
["1P 2m 3m 4A 5P 6m 7M", "todi raga"],
["1P 2M 3M 4P 5P 6M 7m", "mixolydian", "dominant"],
["1P 2m 3M 4P 5d 6m 7M", "persian"],
["1P 2M 3M 4P 5P 6M 7M", "major", "ionian"],
["1P 2m 3M 5d 6m 7m 7M", "enigmatic"],
[
"1P 2M 3M 4P 5A 6M 7M",
"major augmented",
"major #5",
"ionian augmented",
"ionian #5",
],
["1P 2A 3M 4A 5P 6M 7M", "lydian #9"],
// 8-note scales
["1P 2m 2M 4P 4A 5P 6m 7M", "messiaen's mode #4"],
["1P 2m 3M 4P 4A 5P 6m 7M", "purvi raga"],
["1P 2m 3m 3M 4P 5P 6m 7m", "spanish heptatonic"],
["1P 2M 3M 4P 5P 6M 7m 7M", "bebop"],
["1P 2M 3m 3M 4P 5P 6M 7m", "bebop minor"],
["1P 2M 3M 4P 5P 5A 6M 7M", "bebop major"],
["1P 2m 3m 4P 5d 5P 6m 7m", "bebop locrian"],
["1P 2M 3m 4P 5P 6m 7m 7M", "minor bebop"],
["1P 2M 3m 4P 5d 6m 6M 7M", "diminished", "whole-half diminished"],
["1P 2M 3M 4P 5d 5P 6M 7M", "ichikosucho"],
["1P 2M 3m 4P 5P 6m 6M 7M", "minor six diminished"],
[
"1P 2m 3m 3M 4A 5P 6M 7m",
"half-whole diminished",
"dominant diminished",
"messiaen's mode #2",
],
["1P 3m 3M 4P 5P 6M 7m 7M", "kafi raga"],
["1P 2M 3M 4P 4A 5A 6A 7M", "messiaen's mode #6"],
// 9-note scales
["1P 2M 3m 3M 4P 5d 5P 6M 7m", "composite blues"],
["1P 2M 3m 3M 4A 5P 6m 7m 7M", "messiaen's mode #3"],
// 10-note scales
["1P 2m 2M 3m 4P 4A 5P 6m 6M 7M", "messiaen's mode #7"],
// 12-note scales
["1P 2m 2M 3m 3M 4P 5d 5P 6m 6M 7m 7M", "chromatic"],
];
const NoScaleType = {
...EmptyPcset,
intervals: [],
aliases: [],
};
let dictionary$1 = [];
let index = {};
function names() {
return dictionary$1.map((scale) => scale.name);
}
/**
* Given a scale name or chroma, return the scale properties
*
* @param {string} type - scale name or pitch class set chroma
* @example
* import { get } from 'tonaljs/scale-type'
* get('major') // => { name: 'major', ... }
*/
function get$1(type) {
return index[type] || NoScaleType;
}
/**
* Return a list of all scale types
*/
function all$1() {
return dictionary$1.slice();
}
/**
* Add a scale into dictionary
* @param intervals
* @param name
* @param aliases
*/
function add$1(intervals, name, aliases = []) {
const scale = { ...get(intervals), name, intervals, aliases };
dictionary$1.push(scale);
index[scale.name] = scale;
index[scale.setNum] = scale;
index[scale.chroma] = scale;
scale.aliases.forEach((alias) => addAlias$1(scale, alias));
return scale;
}
function addAlias$1(scale, alias) {
index[alias] = scale;
}
SCALES.forEach(([ivls, name, ...aliases]) => add$1(ivls.split(" "), name, aliases));
/**
* Get the natural list of names
*/
function names$1() {
return "1P 2M 3M 4P 5P 6m 7m".split(" ");
}
/**
* Get properties of an interval
*
* @function
* @example
* Interval.get('P4') // => {"alt": 0, "dir": 1, "name": "4P", "num": 4, "oct": 0, "q": "P", "semitones": 5, "simple": 4, "step": 3, "type": "perfectable"}
*/
const get$2 = interval;
/**
* Get name of an interval
*
* @function
* @example
* Interval.name('4P') // => "4P"
* Interval.name('P4') // => "4P"
* Interval.name('C4') // => ""
*/
const name = (name) => interval(name).name;
/**
* Get semitones of an interval
* @function
* @example
* Interval.semitones('P4') // => 5
*/
const semitones = (name) => interval(name).semitones;
/**
* Get quality of an interval
* @function
* @example
* Interval.quality('P4') // => "P"
*/
const quality = (name) => interval(name).q;
/**
* Get number of an interval
* @function
* @example
* Interval.num('P4') // => 4
*/
const num = (name) => interval(name).num;
/**
* Get the simplified version of an interval.
*
* @function
* @param {string} interval - the interval to simplify
* @return {string} the simplified interval
*
* @example
* Interval.simplify("9M") // => "2M"
* Interval.simplify("2M") // => "2M"
* Interval.simplify("-2M") // => "7m"
* ["8P", "9M", "10M", "11P", "12P", "13M", "14M", "15P"].map(Interval.simplify)
* // => [ "8P", "2M", "3M", "4P", "5P", "6M", "7M", "8P" ]
*/
function simplify(name) {
const i = interval(name);
return i.empty ? "" : i.simple + i.q;
}
/**
* Get the inversion (https://en.wikipedia.org/wiki/Inversion_(music)#Intervals)
* of an interval.
*
* @function
* @param {string} interval - the interval to invert in interval shorthand
* notation or interval array notation
* @return {string} the inverted interval
*
* @example
* Interval.invert("3m") // => "6M"
* Interval.invert("2M") // => "7m"
*/
function invert(name) {
const i = interval(name);
if (i.empty) {
return "";
}
const step = (7 - i.step) % 7;
const alt = i.type === "perfectable" ? -i.alt : -(i.alt + 1);
return interval({ step, alt, oct: i.oct, dir: i.dir }).name;
}
// interval numbers
const IN = [1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7];
// interval qualities
const IQ = "P m M m M P d P m M m M".split(" ");
/**
* Get interval name from semitones number. Since there are several interval
* names for the same number, the name it's arbitrary, but deterministic.
*
* @param {Integer} num - the number of semitones (can be negative)
* @return {string} the interval name
* @example
* Interval.fromSemitones(7) // => "5P"
* Interval.fromSemitones(-7) // => "-5P"
*/
function fromSemitones(semitones) {
const d = semitones < 0 ? -1 : 1;
const n = Math.abs(semitones);
const c = n % 12;
const o = Math.floor(n / 12);
return d * (IN[c] + 7 * o) + IQ[c];
}
/**
* Find interval between two notes
*
* @example
* Interval.distance("C4", "G4"); // => "5P"
*/
const distance$1 = distance;
/**
* Adds two intervals
*
* @function
* @param {string} interval1
* @param {string} interval2
* @return {string} the added interval name
* @example
* Interval.add("3m", "5P") // => "7m"
*/
const add$2 = combinator((a, b) => [a[0] + b[0], a[1] + b[1]]);
/**
* Returns a function that adds an interval
*
* @function
* @example
* ['1P', '2M', '3M'].map(Interval.addTo('5P')) // => ["5P", "6M", "7M"]
*/
const addTo = (interval) => (other) => add$2(interval, other);
/**
* Subtracts two intervals
*
* @function
* @param {string} minuendInterval
* @param {string} subtrahendInterval
* @return {string} the substracted interval name
* @example
* Interval.substract('5P', '3M') // => '3m'
* Interval.substract('3M', '5P') // => '-3m'
*/
const substract = combinator((a, b) => [a[0] - b[0], a[1] - b[1]]);
function transposeFifths(interval, fifths) {
const ivl = get$2(interval);
if (ivl.empty)
return "";
const [nFifths, nOcts, dir] = ivl.coord;
return coordToInterval([nFifths + fifths, nOcts, dir]).name;
}
var index$1 = {
names: names$1,
get: get$2,
name,
num,
semitones,
quality,
fromSemitones,
distance: distance$1,
invert,
simplify,
add: add$2,
addTo,
substract,
transposeFifths,
};
function combinator(fn) {
return (a, b) => {
const coordA = interval(a).coord;
const coordB = interval(b).coord;
if (coordA && coordB) {
const coord = fn(coordA, coordB);
return coordToInterval(coord).name;
}
};
}
const L2 = Math.log(2);
const L440 = Math.log(440);
/**
* Get the midi number from a frequency in hertz. The midi number can
* contain decimals (with two digits precission)
*
* @param {number} frequency
* @return {number}
* @example
* import { freqToMidi} from '@tonaljs/midi'
* freqToMidi(220)); //=> 57
* freqToMidi(261.62)); //=> 60
* freqToMidi(261)); //=> 59.96
*/
function freqToMidi(freq) {
const v = (12 * (Math.log(freq) - L440)) / L2 + 69;
return Math.round(v * 100) / 100;
}
const SHARPS = "C C# D D# E F F# G G# A A# B".split(" ");
const FLATS = "C Db D Eb E F Gb G Ab A Bb B".split(" ");
/**
* Given a midi number, returns a note name. The altered notes will have
* flats unless explicitly set with the optional `useSharps` parameter.
*
* @function
* @param {number} midi - the midi note number
* @param {Object} options = default: `{ sharps: false, pitchClass: false }`
* @param {boolean} useSharps - (Optional) set to true to use sharps instead of flats
* @return {string} the note name
* @example
* import { midiToNoteName } from '@tonaljs/midi'
* midiToNoteName(61) // => "Db4"
* midiToNoteName(61, { pitchClass: true }) // => "Db"
* midiToNoteName(61, { sharps: true }) // => "C#4"
* midiToNoteName(61, { pitchClass: true, sharps: true }) // => "C#"
* // it rounds to nearest note
* midiToNoteName(61.7) // => "D4"
*/
function midiToNoteName(midi, options = {}) {
if (isNaN(midi) || midi === -Infinity || midi === Infinity)
return "";
midi = Math.round(midi);
const pcs = options.sharps === true ? SHARPS : FLATS;
const pc = pcs[midi % 12];
if (options.pitchClass) {
return pc;
}
const o = Math.floor(midi / 12) - 1;
return pc + o;
}
const NAMES = ["C", "D", "E", "F", "G", "A", "B"];
const toName = (n) => n.name;
const onlyNotes = (array) => array.map(note).filter((n) => !n.empty);
/**
* Return the natural note names without octave
* @function
* @example
* Note.names(); // => ["C", "D", "E", "F", "G", "A", "B"]
*/
function names$2(array) {
if (array === undefined) {
return NAMES.slice();
}
else if (!Array.isArray(array)) {
return [];
}
else {
return onlyNotes(array).map(toName);
}
}
/**
* Get a note from a note name
*
* @function
* @example
* Note.get('Bb4') // => { name: "Bb4", midi: 70, chroma: 10, ... }
*/
const get$3 = note;
/**
* Get the note name
* @function
*/
const name$1 = (note) => get$3(note).name;
/**
* Get the note pitch class name
* @function
*/
const pitchClass = (note) => get$3(note).pc;
/**
* Get the note accidentals
* @function
*/
const accidentals = (note) => get$3(note).acc;
/**
* Get the note octave
* @function
*/
const octave = (note) => get$3(note).oct;
/**
* Get the note midi
* @function
*/
const midi = (note) => get$3(note).midi;
/**
* Get the note midi
* @function
*/
const freq = (note) => get$3(note).freq;
/**
* Get the note chroma
* @function
*/
const chroma = (note) => get$3(note).chroma;
/**
* Given a midi number, returns a note name. Uses flats for altered notes.
*
* @function
* @param {number} midi - the midi note number
* @return {string} the note name
* @example
* Note.fromMidi(61) // => "Db4"
* Note.fromMidi(61.7) // => "D4"
*/
function fromMidi(midi) {
return midiToNoteName(midi);
}
/**
* Given a midi number, returns a note name. Uses flats for altered notes.
*/
function fromFreq(freq) {
return midiToNoteName(freqToMidi(freq));
}
/**
* Given a midi number, returns a note name. Uses flats for altered notes.
*/
function fromFreqSharps(freq) {
return midiToNoteName(freqToMidi(freq), { sharps: true });
}
/**
* Given a midi number, returns a note name. Uses flats for altered notes.
*
* @function
* @param {number} midi - the midi note number
* @return {string} the note name
* @example
* Note.fromMidiSharps(61) // => "C#4"
*/
function fromMidiSharps(midi) {
return midiToNoteName(midi, { sharps: true });
}
/**
* Transpose a note by an interval
*/
const transpose$1 = transpose;
const tr = transpose;
/**
* Transpose by an interval.
* @function
* @param {string} interval
* @return {function} a function that transposes by the given interval
* @example
* ["C", "D", "E"].map(Note.transposeBy("5P"));
* // => ["G", "A", "B"]
*/
const transposeBy = (interval) => (note) => transpose$1(note, interval);
const trBy = transposeBy;
/**
* Transpose from a note
* @function
* @param {string} note
* @return {function} a function that transposes the the note by an interval
* ["1P", "3M", "5P"].map(Note.transposeFrom("C"));
* // => ["C", "E", "G"]
*/
const transposeFrom = (note) => (interval) => transpose$1(note, interval);
const trFrom = transposeFrom;
/**
* Transpose a note by a number of perfect fifths.
*
* @function
* @param {string} note - the note name
* @param {number} fifhts - the number of fifths
* @return {string} the transposed note name
*
* @example
* import { transposeFifths } from "@tonaljs/note"
* transposeFifths("G4", 1) // => "D"
* [0, 1, 2, 3, 4].map(fifths => transposeFifths("C", fifths)) // => ["C", "G", "D", "A", "E"]
*/
function transposeFifths$1(noteName, fifths) {
const note = get$3(noteName);
if (note.empty) {
return "";
}
const [nFifths, nOcts] = note.coord;
const transposed = nOcts === undefined
? coordToNote([nFifths + fifths])
: coordToNote([nFifths + fifths, nOcts]);
return transposed.name;
}
const trFifths = transposeFifths$1;
const ascending = (a, b) => a.height - b.height;
const descending = (a, b) => b.height - a.height;
function sortedNames(notes, comparator) {
comparator = comparator || ascending;
return onlyNotes(notes).sort(comparator).map(toName);
}
function sortedUniqNames(notes) {
return sortedNames(notes, ascending).filter((n, i, a) => i === 0 || n !== a[i - 1]);
}
/**
* Simplify a note
*
* @function
* @param {string} note - the note to be simplified
* - sameAccType: default true. Use same kind of accidentals that source
* @return {string} the simplified note or '' if not valid note
* @example
* simplify("C##") // => "D"
* simplify("C###") // => "D#"
* simplify("C###")
* simplify("B#4") // => "C5"
*/
const simplify$1 = (noteName) => {
const note = get$3(noteName);
if (note.empty) {
return "";
}
return midiToNoteName(note.midi || note.chroma, {
sharps: note.alt > 0,
pitchClass: note.midi === null,
});
};
/**
* Get enharmonic of a note
*
* @function
* @param {string} note
* @param [string] - [optional] Destination pitch class
* @return {string} the enharmonic note name or '' if not valid note
* @example
* Note.enharmonic("Db") // => "C#"
* Note.enharmonic("C") // => "C"
* Note.enharmonic("F2","E#") // => "E#2"
*/
function enharmonic(noteName, destName) {
const src = get$3(noteName);
if (src.empty) {
return "";
}
// destination: use given or generate one
const dest = get$3(destName ||
midiToNoteName(src.midi || src.chroma, {
sharps: src.alt < 0,
pitchClass: true,
}));
// ensure destination is valid
if (dest.empty || dest.chroma !== src.chroma) {
return "";
}
// if src has no octave, no need to calculate anything else
if (src.oct === undefined) {
return dest.pc;
}
// detect any octave overflow
const srcChroma = src.chroma - src.alt;
const destChroma = dest.chroma - dest.alt;
const destOctOffset = srcChroma > 11 || destChroma < 0
? -1
: srcChroma < 0 || destChroma > 11
? +1
: 0;
// calculate the new octave
const destOct = src.oct + destOctOffset;
return dest.pc + destOct;
}
var index$2 = {
names: names$2,
get: get$3,
name: name$1,
pitchClass,
accidentals,
octave,
midi,
ascending,
descending,
sortedNames,
sortedUniqNames,
fromMidi,
fromMidiSharps,
freq,
fromFreq,
fromFreqSharps,
chroma,
transpose: transpose$1,
tr,
transposeBy,
trBy,
transposeFrom,
trFrom,
transposeFifths: transposeFifths$1,
trFifths,
simplify: simplify$1,
enharmonic,
};
const Empty = Object.freeze([]);
const MODES = [
[0, 2773, 0, "ionian", "", "Maj7", "major"],
[1, 2902, 2, "dorian", "m", "m7"],
[2, 3418, 4, "phrygian", "m", "m7"],
[3, 2741, -1, "lydian", "", "Maj7"],
[4, 2774, 1, "mixolydian", "", "7"],
[5, 2906, 3, "aeolian", "m", "m7", "minor"],
[6, 3434, 5, "locrian", "dim", "m7b5"],
];
const NoMode = {
...EmptyPcset,
name: "",
alt: 0,
modeNum: NaN,
triad: "",
seventh: "",
aliases: [],
};
const modes$1 = MODES.map(toMode);
const index$3 = {};
modes$1.forEach((mode) => {
index$3[mode.name] = mode;
mode.aliases.forEach((alias) => {
index$3[alias] = mode;
});
});
/**
* Get a Mode by it's name
*
* @example
* get('dorian')
* // =>
* // {
* // intervals: [ '1P', '2M', '3m', '4P', '5P', '6M', '7m' ],
* // modeNum: 1,
* // chroma: '101101010110',
* // normalized: '101101010110',
* // name: 'dorian',
* // setNum: 2902,
* // alt: 2,
* // triad: 'm',
* // seventh: 'm7',
* // aliases: []
* // }
*/
function get$4(name) {
return typeof name === "string"
? index$3[name.toLowerCase()] || NoMode
: name && name.name
? get$4(name.name)
: NoMode;
}
function toMode(mode) {
const [modeNum, setNum, alt, name, triad, seventh, alias] = mode;
const aliases = alias ? [alias] : [];
const chroma = Number(setNum).toString(2);
const intervals = get$1(name).intervals;
return {
empty: false,
intervals,
modeNum,
chroma,
normalized: chroma,
name,
setNum,
alt,
triad,
seventh,
aliases,
};
}
function chords(chords) {
return (modeName, tonic) => {
const mode = get$4(modeName);
if (mode.empty)
return [];
const triads = rotate(mode.modeNum, chords);
const tonics = mode.intervals.map((i) => transpose(tonic, i));
return triads.map((triad, i) => tonics[i] + triad);
};
}
const triads = chords(MODES.map((x) => x[4]));
const seventhChords = chords(MODES.map((x) => x[5]));
/**
* References:
* - https://www.researchgate.net/publication/327567188_An_Algorithm_for_Spelling_the_Pitches_of_Any_Musical_Scale
* @module scale
*/
const NoScale = {
empty: true,
name: "",
type: "",
tonic: null,
setNum: NaN,
chroma: "",
normalized: "",
aliases: [],
notes: [],
intervals: [],
};
/**
* Given a string with a scale name and (optionally) a tonic, split
* that components.
*
* It retuns an array with the form [ name, tonic ] where tonic can be a
* note name or null and name can be any arbitrary string
* (this function doesn"t check if that scale name exists)
*
* @function
* @param {string} name - the scale name
* @return {Array} an array [tonic, name]
* @example
* tokenize("C mixolydean") // => ["C", "mixolydean"]
* tokenize("anything is valid") // => ["", "anything is valid"]
* tokenize() // => ["", ""]
*/
function tokenize(name) {
if (typeof name !== "string") {
return ["", ""];
}
const i = name.indexOf(" ");
const tonic = note(name.substring(0, i));
if (tonic.empty) {
const n = note(name);
return n.empty ? ["", name] : [n.name, ""];
}
const type = name.substring(tonic.name.length + 1);
return [tonic.name, type.length ? type : ""];
}
/**
* Get all scale names
* @function
*/
const names$3 = names;
/**
* Get a Scale from a scale name.
*/
function get$5(src) {
const tokens = Array.isArray(src) ? src : tokenize(src);
const tonic = note(tokens[0]).name;
const st = get$1(tokens[1]);
if (st.empty) {
return NoScale;
}
const type = st.name;
const notes = tonic
? st.intervals.map((i) => transpose(tonic, i))
: [];
const name = tonic ? tonic + " " + type : type;
return { ...st, name, type, tonic, notes };
}
const scale = deprecate("Scale.scale", "Scale.get", get$5);
/**
* Get all chords that fits a given scale
*
* @function
* @param {string} name - the scale name
* @return {Array<string>} - the chord names
*
* @example
* scaleChords("pentatonic") // => ["5", "64", "M", "M6", "Madd9", "Msus2"]
*/
function scaleChords(name) {
const s = get$5(name);
const inScale = isSubsetOf(s.chroma);
return all()
.filter((chord) => inScale(chord.chroma))
.map((chord) => chord.aliases[0]);
}
/**
* Get all scales 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 scale names
* @example
* extended("major") // => ["bebop", "bebop dominant", "bebop major", "chromatic", "ichikosucho"]
*/
function extended(name) {
const s = get$5(name);
const isSuperset = isSupersetOf(s.chroma);
return all$1()
.filter((scale) => isSuperset(scale.chroma))
.map((scale) => scale.name);
}
/**
* Find all scales names that are a subset of the given one
* (has less notes but all from the given scale)
*
* @function
* @param {string} name
* @return {Array} a list of scale names
*
* @example
* reduced("major") // => ["ionian pentatonic", "major pentatonic", "ritusen"]
*/
function reduced(name) {
const isSubset = isSubsetOf(get$5(name).chroma);
return all$1()
.filter((scale) => isSubset(scale.chroma))
.map((scale) => scale.name);
}
/**
* Given an array of notes, return the scale: a pitch class set starting from
* the first note of the array
*
* @function
* @param {string[]} notes
* @return {string[]} pitch classes with same tonic
* @example
* scaleNotes(['C4', 'c3', 'C5', 'C4', 'c4']) // => ["C"]
* scaleNotes(['D4', 'c#5', 'A5', 'F#6']) // => ["D", "F#", "A", "C#"]
*/
function scaleNotes(notes) {
const pcset = notes.map((n) => note(n).pc).filter((x) => x);
const tonic = pcset[0];
const scale = sortedUniqNames(pcset);
return rotate(scale.indexOf(tonic), scale);
}
/**
* Find mode names of a scale
*
* @function
* @param {string} name - scale name
* @example
* modeNames("C pentatonic") // => [
* ["C", "major pentatonic"],
* ["D", "egyptian"],
* ["E", "malkos raga"],
* ["G", "ritusen"],
* ["A", "minor pentatonic"]
* ]
*/
function modeNames(name) {
const s = get$5(name);
if (s.empty) {
return [];
}
const tonics = s.tonic ? s.notes : s.intervals;
return modes(s.chroma)
.map((chroma, i) => {
const modeName = get$5(chroma).name;
return modeName ? [tonics[i], modeName] : ["", ""];
})
.filter((x) => x[0]);
}
function getNoteNameOf(scale) {
const names = Array.isArray(scale) ? scaleNotes(scale) : get$5(scale).notes;
const chromas = names.map((name) => note(name).chroma);
return (noteOrMidi) => {
const currNote = typeof noteOrMidi === "number"
? note(fromMidi(noteOrMidi))
: note(noteOrMidi);
const height = currNote.height;
if (height === undefined)
return undefined;
const chroma = height % 12;
const position = chromas.indexOf(chroma);
if (position === -1)
return undefined;
return enharmonic(currNote.name, names[position]);
};
}
function rangeOf(scale) {
const getName = getNoteNameOf(scale);
return (fromNote, toNote) => {
const from = note(fromNote).height;
const to = note(toNote).height;
if (from === undefined || to === undefined)
return [];
return range(from, to)
.map(getName)
.filter((x) => x);
};
}
var index$4 = {
get: get$5,
names: names$3,
extended,
modeNames,
reduced,
scaleChords,
scaleNotes,
tokenize,
rangeOf,
// deprecated
scale,
};
export { index$1 as Interval, index$2 as Note, index$4 as Scale };