/** * 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} * * @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} 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} - 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 };