UNPKG

@tonaljs/pitch-note

Version:

Parse intervals in shorthand notation

1 lines 6.13 kB
{"version":3,"sources":["../index.ts"],"sourcesContent":["import {\n coordinates,\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 NoteWithOctave = string;\nexport type PcName = string;\nexport type NoteName = NoteWithOctave | PcName;\nexport type NoteLiteral = NoteName | Pitch | NamedPitch;\n\nexport interface Note extends Pitch, NamedPitch {\n readonly empty: boolean;\n readonly name: NoteName;\n readonly letter: string;\n readonly acc: string;\n readonly pc: PcName;\n readonly chroma: number;\n readonly height: number;\n readonly coord: PitchCoordinates;\n readonly midi: number | null;\n readonly freq: number | null;\n}\n\nexport type NoteType = Note;\n\nconst NoNote: Note = Object.freeze({\n empty: true,\n name: \"\",\n letter: \"\",\n acc: \"\",\n pc: \"\",\n step: NaN,\n alt: NaN,\n chroma: NaN,\n height: NaN,\n coord: [] as unknown as PitchCoordinates,\n midi: null,\n freq: null,\n});\n\nconst cache: Map<NoteLiteral | undefined, Note> = new Map();\n\nexport const stepToLetter = (step: number) => \"CDEFGAB\".charAt(step);\nexport const altToAcc = (alt: number): string =>\n alt < 0 ? fillStr(\"b\", -alt) : fillStr(\"#\", alt);\nexport const accToAlt = (acc: string): number =>\n acc[0] === \"b\" ? -acc.length : acc.length;\n\n/**\n * Given a note literal (a note name or a note object), returns the Note object\n * @example\n * note('Bb4') // => { name: \"Bb4\", midi: 70, chroma: 10, ... }\n */\nexport function note(src: NoteLiteral): Note {\n const stringSrc = JSON.stringify(src);\n\n const cached = cache.get(stringSrc);\n if (cached) {\n return cached;\n }\n\n const value =\n typeof src === \"string\"\n ? parse(src)\n : isPitch(src)\n ? note(pitchName(src))\n : isNamedPitch(src)\n ? note(src.name)\n : NoNote;\n cache.set(stringSrc, value);\n return value;\n}\n\ntype NoteTokens = [string, string, string, string];\n\nconst REGEX = /^([a-gA-G]?)(#{1,}|b{1,}|x{1,}|)(-?\\d*)\\s*(.*)$/;\n\n/**\n * @private\n */\nexport function tokenizeNote(str: string): NoteTokens {\n const m = REGEX.exec(str) as string[];\n return m\n ? [m[1].toUpperCase(), m[2].replace(/x/g, \"##\"), m[3], m[4]]\n : [\"\", \"\", \"\", \"\"];\n}\n\n/**\n * @private\n */\nexport function coordToNote(noteCoord: PitchCoordinates): Note {\n return note(pitch(noteCoord)) as Note;\n}\n\nconst mod = (n: number, m: number) => ((n % m) + m) % m;\n\nconst SEMI = [0, 2, 4, 5, 7, 9, 11];\nfunction parse(noteName: NoteName): Note {\n const tokens = tokenizeNote(noteName);\n if (tokens[0] === \"\" || tokens[3] !== \"\") {\n return NoNote;\n }\n\n const letter = tokens[0];\n const acc = tokens[1];\n const octStr = tokens[2];\n\n const step = (letter.charCodeAt(0) + 3) % 7;\n const alt = accToAlt(acc);\n const oct = octStr.length ? +octStr : undefined;\n const coord = coordinates({ step, alt, oct });\n\n const name = letter + acc + octStr;\n const pc = letter + acc;\n const chroma = (SEMI[step] + alt + 120) % 12;\n const height =\n oct === undefined\n ? mod(SEMI[step] + alt, 12) - 12 * 99\n : SEMI[step] + alt + 12 * (oct + 1);\n const midi = height >= 0 && height <= 127 ? height : null;\n const freq = oct === undefined ? null : Math.pow(2, (height - 69) / 12) * 440;\n\n return {\n empty: false,\n acc,\n alt,\n chroma,\n coord,\n freq,\n height,\n letter,\n midi,\n name,\n oct,\n pc,\n step,\n };\n}\n\nfunction pitchName(props: Pitch): NoteName {\n const { step, alt, oct } = props;\n const letter = stepToLetter(step);\n if (!letter) {\n return \"\";\n }\n\n const pc = letter + altToAcc(alt);\n return oct || oct === 0 ? pc + oct : pc;\n}\n"],"mappings":"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","names":[]}