{"version":3,"sources":["../index.ts"],"sourcesContent":["import { compact, range, rotate } from \"@tonaljs/collection\";\nimport { NotFound } from \"@tonaljs/pitch\";\nimport { transpose } from \"@tonaljs/pitch-distance\";\nimport { Interval, IntervalName, interval } from \"@tonaljs/pitch-interval\";\nimport { Note, NoteName, note } from \"@tonaljs/pitch-note\";\n\n/**\n * The properties of a pitch class set\n * @param {number} num - a number between 1 and 4095 (both included) that\n * uniquely identifies the set. It's the decimal number of the chroma.\n * @param {string} chroma - a string representation of the set: a 12-char string\n * with either \"1\" or \"0\" as characters, representing a pitch class or not\n * for the given position in the octave. For example, a \"1\" at index 0 means 'C',\n * a \"1\" at index 2 means 'D', and so on...\n * @param {string} normalized - the chroma but shifted to the first 1\n * @param {number} length - the number of notes of the pitch class set\n * @param {IntervalName[]} intervals - the intervals of the pitch class set\n * *starting from C*\n */\nexport interface Pcset {\n  readonly name: string;\n  readonly empty: boolean;\n  readonly setNum: number;\n  readonly chroma: PcsetChroma;\n  readonly normalized: PcsetChroma;\n  readonly intervals: IntervalName[];\n}\n\nexport const EmptyPcset: Pcset = {\n  empty: true,\n  name: \"\",\n  setNum: 0,\n  chroma: \"000000000000\",\n  normalized: \"000000000000\",\n  intervals: [],\n};\n\nexport type PcsetChroma = string;\nexport type PcsetNum = number;\n\n// UTILITIES\nconst setNumToChroma = (num: number): string =>\n  Number(num).toString(2).padStart(12, \"0\");\nconst chromaToNumber = (chroma: string): number => parseInt(chroma, 2);\nconst REGEX = /^[01]{12}$/;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isChroma(set: any): set is PcsetChroma {\n  return REGEX.test(set);\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isPcsetNum = (set: any): set is PcsetNum =>\n  typeof set === \"number\" && set >= 0 && set <= 4095;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isPcset = (set: any): set is Pcset => set && isChroma(set.chroma);\n\nconst cache: { [key in string]: Pcset } = { [EmptyPcset.chroma]: EmptyPcset };\n\n/**\n * A definition of a pitch class set. It could be:\n * - The pitch class set chroma (a 12-length string with only 1s or 0s)\n * - The pitch class set number (an integer between 1 and 4095)\n * - An array of note names\n * - An array of interval names\n */\nexport type Set =\n  | Partial<Pcset>\n  | PcsetChroma\n  | PcsetNum\n  | NoteName[]\n  | IntervalName[];\n\n/**\n * Get the pitch class set of a collection of notes or set number or chroma\n */\nexport function get(src: Set): Pcset {\n  const chroma: PcsetChroma = isChroma(src)\n    ? src\n    : isPcsetNum(src)\n      ? setNumToChroma(src)\n      : Array.isArray(src)\n        ? listToChroma(src)\n        : isPcset(src)\n          ? src.chroma\n          : EmptyPcset.chroma;\n\n  return (cache[chroma] = cache[chroma] || chromaToPcset(chroma));\n}\n\n/**\n * @use Pcset.get\n * @deprecated\n */\nexport const pcset = get;\n\n/**\n * Get pitch class set chroma\n * @function\n * @example\n * Pcset.chroma([\"c\", \"d\", \"e\"]); //=> \"101010000000\"\n */\nexport const chroma = (set: Set) => get(set).chroma;\n\n/**\n * Get intervals (from C) of a set\n * @function\n * @example\n * Pcset.intervals([\"c\", \"d\", \"e\"]); //=>\n */\nexport const intervals = (set: Set) => get(set).intervals;\n\n/**\n * Get pitch class set number\n * @function\n * @example\n * Pcset.num([\"c\", \"d\", \"e\"]); //=> 2192\n */\nexport const num = (set: Set) => get(set).setNum;\n\nconst IVLS = [\n  \"1P\",\n  \"2m\",\n  \"2M\",\n  \"3m\",\n  \"3M\",\n  \"4P\",\n  \"5d\",\n  \"5P\",\n  \"6m\",\n  \"6M\",\n  \"7m\",\n  \"7M\",\n];\n\n/**\n * Get the intervals of a pcset *starting from C*\n * @private\n * @param {Set} set - the pitch class set\n * @return {IntervalName[]} an array of interval names or an empty array\n * if not a valid pitch class set\n */\nfunction chromaToIntervals(chroma: PcsetChroma): IntervalName[] {\n  const intervals = [];\n  for (let i = 0; i < 12; i++) {\n    // tslint:disable-next-line:curly\n    if (chroma.charAt(i) === \"1\") intervals.push(IVLS[i]);\n  }\n  return intervals;\n}\n\nexport function notes(set: Set): NoteName[] {\n  return get(set).intervals.map((ivl) => transpose(\"C\", ivl));\n}\n\n/**\n * Get a list of all possible pitch class sets (all possible chromas) *having\n * C as root*. There are 2048 different chromas. If you want them with another\n * note you have to transpose it\n *\n * @see http://allthescales.org/\n * @return {Array<PcsetChroma>} an array of possible chromas from '10000000000' to '11111111111'\n */\nexport function chromas(): PcsetChroma[] {\n  return range(2048, 4095).map(setNumToChroma);\n}\n\n/**\n * Given a a list of notes or a pcset chroma, produce the rotations\n * of the chroma discarding the ones that starts with \"0\"\n *\n * This is used, for example, to get all the modes of a scale.\n *\n * @param {Array|string} set - the list of notes or pitchChr of the set\n * @param {boolean} normalize - (Optional, true by default) remove all\n * the rotations that starts with \"0\"\n * @return {Array<string>} an array with all the modes of the chroma\n *\n * @example\n * Pcset.modes([\"C\", \"D\", \"E\"]).map(Pcset.intervals)\n */\nexport function modes(set: Set, normalize = true): PcsetChroma[] {\n  const pcs = get(set);\n\n  const binary = pcs.chroma.split(\"\");\n  return compact(\n    binary.map((_, i) => {\n      const r = rotate(i, binary);\n      return normalize && r[0] === \"0\" ? null : r.join(\"\");\n    }),\n  );\n}\n\n/**\n * Test if two pitch class sets are equal\n *\n * @param {Array|string} set1 - one of the pitch class sets\n * @param {Array|string} set2 - the other pitch class set\n * @return {boolean} true if they are equal\n * @example\n * Pcset.isEqual([\"c2\", \"d3\"], [\"c5\", \"d2\"]) // => true\n */\nexport function isEqual(s1: Set, s2: Set) {\n  return get(s1).setNum === get(s2).setNum;\n}\n\n/**\n * Create a function that test if a collection of notes is a\n * subset of a given set\n *\n * The function is curried.\n *\n * @param {PcsetChroma|NoteName[]} set - the superset to test against (chroma or\n * list of notes)\n * @return{function(PcsetChroma|NoteNames[]): boolean} a function accepting a set\n * to test against (chroma or list of notes)\n * @example\n * const inCMajor = Pcset.isSubsetOf([\"C\", \"E\", \"G\"])\n * inCMajor([\"e6\", \"c4\"]) // => true\n * inCMajor([\"e6\", \"c4\", \"d3\"]) // => false\n */\nexport function isSubsetOf(set: Set) {\n  const s = get(set).setNum;\n\n  return (notes: Set | Pcset) => {\n    const o = get(notes).setNum;\n    // tslint:disable-next-line: no-bitwise\n    return s && s !== o && (o & s) === o;\n  };\n}\n\n/**\n * Create a function that test if a collection of notes is a\n * superset of a given set (it contains all notes and at least one more)\n *\n * @param {Set} set - an array of notes or a chroma set string to test against\n * @return {(subset: Set): boolean} a function that given a set\n * returns true if is a subset of the first one\n * @example\n * const extendsCMajor = Pcset.isSupersetOf([\"C\", \"E\", \"G\"])\n * extendsCMajor([\"e6\", \"a\", \"c4\", \"g2\"]) // => true\n * extendsCMajor([\"c6\", \"e4\", \"g3\"]) // => false\n */\nexport function isSupersetOf(set: Set) {\n  const s = get(set).setNum;\n  return (notes: Set) => {\n    const o = get(notes).setNum;\n    // tslint:disable-next-line: no-bitwise\n    return s && s !== o && (o | s) === o;\n  };\n}\n\n/**\n * Test if a given pitch class set includes a note\n *\n * @param {Array<string>} set - the base set to test against\n * @param {string} note - the note to test\n * @return {boolean} true if the note is included in the pcset\n *\n * Can be partially applied\n *\n * @example\n * const isNoteInCMajor = isNoteIncludedIn(['C', 'E', 'G'])\n * isNoteInCMajor('C4') // => true\n * isNoteInCMajor('C#4') // => false\n */\nexport function isNoteIncludedIn(set: Set) {\n  const s = get(set);\n\n  return (noteName: NoteName): boolean => {\n    const n = note(noteName);\n    return s && !n.empty && s.chroma.charAt(n.chroma) === \"1\";\n  };\n}\n\n/** @deprecated use: isNoteIncludedIn */\nexport const includes = isNoteIncludedIn;\n\n/**\n * Filter a list with a pitch class set\n *\n * @param {Array|string} set - the pitch class set notes\n * @param {Array|string} notes - the note list to be filtered\n * @return {Array} the filtered notes\n *\n * @example\n * Pcset.filter([\"C\", \"D\", \"E\"], [\"c2\", \"c#2\", \"d2\", \"c3\", \"c#3\", \"d3\"]) // => [ \"c2\", \"d2\", \"c3\", \"d3\" ])\n * Pcset.filter([\"C2\"], [\"c2\", \"c#2\", \"d2\", \"c3\", \"c#3\", \"d3\"]) // => [ \"c2\", \"c3\" ])\n */\nexport function filter(set: Set) {\n  const isIncluded = isNoteIncludedIn(set);\n  return (notes: NoteName[]) => {\n    return notes.filter(isIncluded);\n  };\n}\n\n/** @deprecated */\nexport default {\n  get,\n  chroma,\n  num,\n  intervals,\n  chromas,\n  isSupersetOf,\n  isSubsetOf,\n  isNoteIncludedIn,\n  isEqual,\n  filter,\n  modes,\n  notes,\n  // deprecated\n  pcset,\n};\n\n//// PRIVATE ////\n\nfunction chromaRotations(chroma: string): string[] {\n  const binary = chroma.split(\"\");\n  return binary.map((_, i) => rotate(i, binary).join(\"\"));\n}\n\nfunction chromaToPcset(chroma: PcsetChroma): Pcset {\n  const setNum = chromaToNumber(chroma);\n  const normalizedNum = chromaRotations(chroma)\n    .map(chromaToNumber)\n    .filter((n) => n >= 2048)\n    .sort()[0];\n  const normalized = setNumToChroma(normalizedNum);\n\n  const intervals = chromaToIntervals(chroma);\n\n  return {\n    empty: false,\n    name: \"\",\n    setNum,\n    chroma,\n    normalized,\n    intervals,\n  };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction listToChroma(set: any[]): PcsetChroma {\n  if (set.length === 0) {\n    return EmptyPcset.chroma;\n  }\n\n  let pitch: Note | Interval | NotFound;\n  const binary = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n  // tslint:disable-next-line:prefer-for-of\n  for (let i = 0; i < set.length; i++) {\n    pitch = note(set[i]);\n    // tslint:disable-next-line: curly\n    if (pitch.empty) pitch = interval(set[i]);\n    // tslint:disable-next-line: curly\n    if (!pitch.empty) binary[pitch.chroma] = 1;\n  }\n  return binary.join(\"\");\n}\n"],"mappings":"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","names":["num","chroma","intervals","notes"]}