UNPKG

@tonaljs/midi

Version:

Functions to work with midi numbers

1 lines 7.27 kB
{"version":3,"sources":["../index.ts"],"sourcesContent":["import { NoteName, note as props } from \"@tonaljs/pitch-note\";\n\ntype Midi = number;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isMidi(arg: any): arg is Midi {\n return +arg >= 0 && +arg <= 127;\n}\n\n/**\n * Get the note midi number (a number between 0 and 127)\n *\n * It returns undefined if not valid note name\n *\n * @function\n * @param {string|number} note - the note name or midi number\n * @return {Integer} the midi number or undefined if not valid note\n * @example\n * import { toMidi } from '@tonaljs/midi'\n * toMidi(\"C4\") // => 60\n * toMidi(60) // => 60\n * toMidi('60') // => 60\n */\nexport function toMidi(note: NoteName | number): number | null {\n if (isMidi(note)) {\n return +note;\n }\n const n = props(note);\n return n.empty ? null : n.midi;\n}\n\n/**\n * Get the frequency in hertz from midi number\n *\n * @param {number} midi - the note midi number\n * @param {number} [tuning = 440] - A4 tuning frequency in Hz (440 by default)\n * @return {number} the frequency or null if not valid note midi\n * @example\n * import { midiToFreq} from '@tonaljs/midi'\n * midiToFreq(69) // => 440\n */\nexport function midiToFreq(midi: number, tuning = 440): number {\n return Math.pow(2, (midi - 69) / 12) * tuning;\n}\n\nconst L2 = Math.log(2);\nconst L440 = Math.log(440);\n\n/**\n * Get the midi number from a frequency in hertz. The midi number can\n * contain decimals (with two digits precision)\n *\n * @param {number} frequency\n * @return {number}\n * @example\n * import { freqToMidi} from '@tonaljs/midi'\n * freqToMidi(220)); //=> 57\n * freqToMidi(261.62)); //=> 60\n * freqToMidi(261)); //=> 59.96\n */\nexport function freqToMidi(freq: number): number {\n const v = (12 * (Math.log(freq) - L440)) / L2 + 69;\n return Math.round(v * 100) / 100;\n}\n\nexport interface ToNoteNameOptions {\n pitchClass?: boolean;\n sharps?: boolean;\n}\n\nconst SHARPS = \"C C# D D# E F F# G G# A A# B\".split(\" \");\nconst FLATS = \"C Db D Eb E F Gb G Ab A Bb B\".split(\" \");\n/**\n * Given a midi number, returns a note name. The altered notes will have\n * flats unless explicitly set with the optional `useSharps` parameter.\n *\n * @function\n * @param {number} midi - the midi note number\n * @param {Object} options = default: `{ sharps: false, pitchClass: false }`\n * @param {boolean} useSharps - (Optional) set to true to use sharps instead of flats\n * @return {string} the note name\n * @example\n * import { midiToNoteName } from '@tonaljs/midi'\n * midiToNoteName(61) // => \"Db4\"\n * midiToNoteName(61, { pitchClass: true }) // => \"Db\"\n * midiToNoteName(61, { sharps: true }) // => \"C#4\"\n * midiToNoteName(61, { pitchClass: true, sharps: true }) // => \"C#\"\n * // it rounds to nearest note\n * midiToNoteName(61.7) // => \"D4\"\n */\nexport function midiToNoteName(midi: number, options: ToNoteNameOptions = {}) {\n if (isNaN(midi) || midi === -Infinity || midi === Infinity) return \"\";\n midi = Math.round(midi);\n const pcs = options.sharps === true ? SHARPS : FLATS;\n const pc = pcs[midi % 12];\n if (options.pitchClass) {\n return pc;\n }\n const o = Math.floor(midi / 12) - 1;\n return pc + o;\n}\n\nexport function chroma(midi: number): number {\n return midi % 12;\n}\n\nfunction pcsetFromChroma(chroma: string): number[] {\n return chroma.split(\"\").reduce((pcset, val, index) => {\n if (index < 12 && val === \"1\") pcset.push(index);\n return pcset;\n }, [] as number[]);\n}\n\nfunction pcsetFromMidi(midi: number[]): number[] {\n return midi\n .map(chroma)\n .sort((a, b) => a - b)\n .filter((n, i, a) => i === 0 || n !== a[i - 1]);\n}\n\n/**\n * Given a list of midi numbers, returns the pitch class set (unique chroma numbers)\n * @param midi\n * @example\n *\n */\nexport function pcset(notes: number[] | string): number[] {\n return Array.isArray(notes) ? pcsetFromMidi(notes) : pcsetFromChroma(notes);\n}\n\nexport function pcsetNearest(notes: number[] | string) {\n const set = pcset(notes);\n return (midi: number): number | undefined => {\n const ch = chroma(midi);\n for (let i = 0; i < 12; i++) {\n if (set.includes(ch + i)) return midi + i;\n if (set.includes(ch - i)) return midi - i;\n }\n return undefined;\n };\n}\n\nexport function pcsetSteps(notes: number[] | string, tonic: number) {\n const set = pcset(notes);\n const len = set.length;\n return (step: number): number => {\n const index = step < 0 ? (len - (-step % len)) % len : step % len;\n const octaves = Math.floor(step / len);\n return set[index] + octaves * 12 + tonic;\n };\n}\n\nexport function pcsetDegrees(notes: number[] | string, tonic: number) {\n const steps = pcsetSteps(notes, tonic);\n return (degree: number): number | undefined => {\n if (degree === 0) return undefined;\n return steps(degree > 0 ? degree - 1 : degree);\n };\n}\n\n/** @deprecated */\nexport default {\n chroma,\n freqToMidi,\n isMidi,\n midiToFreq,\n midiToNoteName,\n pcsetNearest,\n pcset,\n pcsetDegrees,\n pcsetSteps,\n toMidi,\n};\n"],"mappings":"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","names":["chroma","pcset"]}