-
Notifications
You must be signed in to change notification settings - Fork 223
/
Copy pathindex.ts
120 lines (107 loc) · 3.04 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { isNamedPitch, isPitch, Pitch } from "@tonaljs/pitch";
import { interval } from "@tonaljs/pitch-interval";
import { accToAlt, altToAcc } from "@tonaljs/pitch-note";
export interface RomanNumeral extends Pitch {
readonly name: string;
readonly empty: boolean;
readonly roman: string;
readonly interval: string;
readonly acc: string;
readonly chordType: string;
readonly major: boolean;
readonly dir: 1;
}
export interface NoRomanNumeral extends Partial<RomanNumeral> {
readonly empty: true;
readonly name: "";
readonly chordType: "";
}
const NoRomanNumeral: NoRomanNumeral = { empty: true, name: "", chordType: "" };
const cache: Record<string, RomanNumeral | NoRomanNumeral> = {};
/**
* Get properties of a roman numeral string
*
* @function
* @param {string} - the roman numeral string (can have type, like: Imaj7)
* @return {Object} - the roman numeral properties
* @param {string} name - the roman numeral (tonic)
* @param {string} type - the chord type
* @param {string} num - the number (1 = I, 2 = II...)
* @param {boolean} major - major or not
*
* @example
* romanNumeral("VIIb5") // => { name: "VII", type: "b5", num: 7, major: true }
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function get(src: any): RomanNumeral | NoRomanNumeral {
return typeof src === "string"
? cache[src] || (cache[src] = parse(src))
: typeof src === "number"
? get(NAMES[src] || "")
: isPitch(src)
? fromPitch(src)
: isNamedPitch(src)
? get(src.name)
: NoRomanNumeral;
}
/**
* @deprecated
* @use RomanNumeral.get
*/
export const romanNumeral = get;
/**
* Get roman numeral names
*
* @function
* @param {boolean} [isMajor=true]
* @return {Array<String>}
*
* @example
* names() // => ["I", "II", "III", "IV", "V", "VI", "VII"]
*/
export function names(major = true) {
return (major ? NAMES : NAMES_MINOR).slice();
}
function fromPitch(pitch: Pitch): RomanNumeral | NoRomanNumeral {
return get(altToAcc(pitch.alt) + NAMES[pitch.step]);
}
const REGEX =
/^(#{1,}|b{1,}|x{1,}|)(IV|I{1,3}|VI{0,2}|iv|i{1,3}|vi{0,2})([^IViv]*)$/;
// [name, accidentals, romanNumeral, chordType]
type RomanNumeralTokens = [string, string, string, string];
export function tokenize(str: string): RomanNumeralTokens {
return (REGEX.exec(str) || ["", "", "", ""]) as RomanNumeralTokens;
}
const ROMANS = "I II III IV V VI VII";
const NAMES = ROMANS.split(" ");
const NAMES_MINOR = ROMANS.toLowerCase().split(" ");
function parse(src: string): RomanNumeral | NoRomanNumeral {
const [name, acc, roman, chordType] = tokenize(src);
if (!roman) {
return NoRomanNumeral;
}
const upperRoman = roman.toUpperCase();
const step = NAMES.indexOf(upperRoman);
const alt = accToAlt(acc);
const dir = 1;
return {
empty: false,
name,
roman,
interval: interval({ step, alt, dir }).name,
acc,
chordType,
alt,
step,
major: roman === upperRoman,
oct: 0,
dir,
};
}
/** @deprecated */
export default {
names,
get,
// deprecated
romanNumeral,
};