@tonaljs/pitch-interval
Version:
Parse intervals in shorthand notation
1 lines • 8.5 kB
Source Map (JSON)
{"version":3,"sources":["../index.ts"],"sourcesContent":["import {\n coordinates,\n Direction,\n IntervalCoordinates,\n isNamedPitch,\n isPitch,\n NamedPitch,\n Pitch,\n pitch,\n PitchCoordinates,\n} from \"@tonaljs/pitch\";\n\nconst fillStr = (s: string, n: number) => Array(Math.abs(n) + 1).join(s);\n\nexport type IntervalName = string;\nexport type IntervalLiteral = IntervalName | Pitch | NamedPitch;\n\ntype Quality =\n | \"dddd\"\n | \"ddd\"\n | \"dd\"\n | \"d\"\n | \"m\"\n | \"M\"\n | \"P\"\n | \"A\"\n | \"AA\"\n | \"AAA\"\n | \"AAAA\";\ntype Type = \"perfectable\" | \"majorable\";\n\nexport interface Interval extends Pitch, NamedPitch {\n readonly empty: boolean;\n readonly name: IntervalName;\n readonly num: number;\n readonly q: Quality;\n readonly type: Type;\n readonly step: number;\n readonly alt: number;\n readonly dir: Direction;\n readonly simple: number;\n readonly semitones: number;\n readonly chroma: number;\n readonly coord: IntervalCoordinates;\n readonly oct: number;\n}\n\nexport type IntervalType = Interval;\n\nconst NoInterval: Interval = Object.freeze({\n empty: true,\n name: \"\",\n num: NaN,\n q: \"\" as Quality,\n type: \"\" as Type,\n step: NaN,\n alt: NaN,\n dir: NaN as Direction,\n simple: NaN,\n semitones: NaN,\n chroma: NaN,\n coord: [] as unknown as IntervalCoordinates,\n oct: NaN,\n});\n\n// shorthand tonal notation (with quality after number)\nconst INTERVAL_TONAL_REGEX = \"([-+]?\\\\d+)(d{1,4}|m|M|P|A{1,4})\";\n// standard shorthand notation (with quality before number)\nconst INTERVAL_SHORTHAND_REGEX = \"(AA|A|P|M|m|d|dd)([-+]?\\\\d+)\";\nconst REGEX = new RegExp(\n \"^\" + INTERVAL_TONAL_REGEX + \"|\" + INTERVAL_SHORTHAND_REGEX + \"$\",\n);\n\ntype IntervalTokens = [string, string];\n\n/**\n * @private\n */\nexport function tokenizeInterval(str?: IntervalName): IntervalTokens {\n const m = REGEX.exec(`${str}`);\n if (m === null) {\n return [\"\", \"\"];\n }\n return m[1] ? [m[1], m[2]] : [m[4], m[3]];\n}\n\nconst cache: { [key in string]: Interval } = {};\n\n/**\n * Get interval properties. It returns an object with:\n *\n * - name: the interval name\n * - num: the interval number\n * - type: 'perfectable' or 'majorable'\n * - q: the interval quality (d, m, M, A)\n * - dir: interval direction (1 ascending, -1 descending)\n * - simple: the simplified number\n * - semitones: the size in semitones\n * - chroma: the interval chroma\n *\n * @param {string} interval - the interval name\n * @return {Object} the interval properties\n *\n * @example\n * import { interval } from '@tonaljs/core'\n * interval('P5').semitones // => 7\n * interval('m3').type // => 'majorable'\n */\nexport function interval(src: IntervalLiteral): Interval {\n return typeof src === \"string\"\n ? cache[src] || (cache[src] = parse(src))\n : isPitch(src)\n ? interval(pitchName(src))\n : isNamedPitch(src)\n ? interval(src.name)\n : NoInterval;\n}\n\nconst SIZES = [0, 2, 4, 5, 7, 9, 11];\nconst TYPES = \"PMMPPMM\";\nfunction parse(str?: string): Interval {\n const tokens = tokenizeInterval(str);\n if (tokens[0] === \"\") {\n return NoInterval;\n }\n const num = +tokens[0];\n const q = tokens[1] as Quality;\n const step = (Math.abs(num) - 1) % 7;\n const t = TYPES[step];\n if (t === \"M\" && q === \"P\") {\n return NoInterval;\n }\n const type = t === \"M\" ? \"majorable\" : \"perfectable\";\n\n const name = \"\" + num + q;\n const dir = num < 0 ? -1 : 1;\n const simple = num === 8 || num === -8 ? num : dir * (step + 1);\n const alt = qToAlt(type, q);\n const oct = Math.floor((Math.abs(num) - 1) / 7);\n const semitones = dir * (SIZES[step] + alt + 12 * oct);\n const chroma = (((dir * (SIZES[step] + alt)) % 12) + 12) % 12;\n const coord = coordinates({ step, alt, oct, dir }) as IntervalCoordinates;\n return {\n empty: false,\n name,\n num,\n q,\n step,\n alt,\n dir,\n type,\n simple,\n semitones,\n chroma,\n coord,\n oct,\n };\n}\n\n/**\n * @private\n *\n * forceDescending is used in the case of unison (#243)\n */\nexport function coordToInterval(\n coord: PitchCoordinates,\n forceDescending?: boolean,\n): Interval {\n const [f, o = 0] = coord;\n const isDescending = f * 7 + o * 12 < 0;\n const ivl: IntervalCoordinates =\n forceDescending || isDescending ? [-f, -o, -1] : [f, o, 1];\n return interval(pitch(ivl)) as Interval;\n}\n\nfunction qToAlt(type: Type, q: string): number {\n return (q === \"M\" && type === \"majorable\") ||\n (q === \"P\" && type === \"perfectable\")\n ? 0\n : q === \"m\" && type === \"majorable\"\n ? -1\n : /^A+$/.test(q)\n ? q.length\n : /^d+$/.test(q)\n ? -1 * (type === \"perfectable\" ? q.length : q.length + 1)\n : 0;\n}\n\n// return the interval name of a pitch\nfunction pitchName(props: Pitch): string {\n const { step, alt, oct = 0, dir } = props;\n if (!dir) {\n return \"\";\n }\n const calcNum = step + 1 + 7 * oct;\n // this is an edge case: descending pitch class unison (see #243)\n const num = calcNum === 0 ? step + 1 : calcNum;\n const d = dir < 0 ? \"-\" : \"\";\n const type = TYPES[step] === \"M\" ? \"majorable\" : \"perfectable\";\n const name = d + num + altToQ(type, alt);\n return name;\n}\n\nfunction altToQ(type: Type, alt: number): Quality {\n if (alt === 0) {\n return type === \"majorable\" ? \"M\" : \"P\";\n } else if (alt === -1 && type === \"majorable\") {\n return \"m\";\n } else if (alt > 0) {\n return fillStr(\"A\", alt) as Quality;\n } else {\n return fillStr(\"d\", type === \"perfectable\" ? alt : alt + 1) as Quality;\n }\n}\n"],"mappings":"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","names":[]}