-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
108 lines (89 loc) · 2.57 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
// TYPES: PARSING
export type TimeSignatureLiteral = string | [number, number] | [string, string]
export type ParsedTimeSignature = [number | number[], number]
// TYPES: PROPERTIES
export type ValidTimeSignature = {
readonly empty: false
readonly name: string
readonly upper: number | number[]
readonly lower: number
readonly type: 'simple' | 'compound' | 'irregular' | 'irrational'
readonly additive: number[]
}
export type InvalidTimeSignature = {
readonly empty: true
readonly name: ''
readonly upper: undefined
readonly lower: undefined
readonly type: undefined
readonly additive: []
}
export type TimeSignature = ValidTimeSignature | InvalidTimeSignature
// CONSTANTS
const NONE: InvalidTimeSignature = {
empty: true,
name: '',
upper: undefined,
lower: undefined,
type: undefined,
additive: [],
}
const NAMES = ['4/4', '3/4', '2/4', '2/2', '12/8', '9/8', '6/8', '3/8']
// PUBLIC API
export function names() {
return NAMES.slice()
}
const REGEX = /^(\d*\d(?:\+\d)*)\/(\d+)$/
const CACHE = new Map<TimeSignatureLiteral, TimeSignature>()
export function get(literal: TimeSignatureLiteral): TimeSignature {
const stringifiedLiteral = JSON.stringify(literal)
const cached = CACHE.get(stringifiedLiteral)
if (cached) {
return cached
}
const ts = build(parse(literal))
CACHE.set(stringifiedLiteral, ts)
return ts
}
export function parse(literal: TimeSignatureLiteral): ParsedTimeSignature {
if (typeof literal === 'string') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, up, low] = REGEX.exec(literal) || []
return parse([up, low])
}
const [up, down] = literal
const denominator = +down
if (typeof up === 'number') {
return [up, denominator]
}
const list = up.split('+').map((n) => +n)
return list.length === 1 ? [list[0], denominator] : [list, denominator]
}
export default { names, parse, get }
// PRIVATE
const isPowerOfTwo = (x: number) => (Math.log(x) / Math.log(2)) % 1 === 0
function build([up, down]: ParsedTimeSignature): TimeSignature {
const upper = Array.isArray(up) ? up.reduce((a, b) => a + b, 0) : up
const lower = down
if (upper === 0 || lower === 0) {
return NONE
}
const name = Array.isArray(up) ? `${up.join('+')}/${down}` : `${up}/${down}`
const additive = Array.isArray(up) ? up : []
const type =
lower === 4 || lower === 2
? 'simple'
: lower === 8 && upper % 3 === 0
? 'compound'
: isPowerOfTwo(lower)
? 'irregular'
: 'irrational'
return {
empty: false,
name,
type,
upper,
lower,
additive,
}
}