vector-score 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1224 @@
1
+ const B = {
2
+ treble: {
3
+ staffType: "treble",
4
+ paddingTop: 13,
5
+ paddingBottom: 3,
6
+ topLineNote: { name: "F", octave: 5 },
7
+ topLineYPos: 0,
8
+ bottomLineYPos: 40
9
+ },
10
+ bass: {
11
+ staffType: "bass",
12
+ paddingTop: 0,
13
+ paddingBottom: 0,
14
+ topLineNote: { name: "A", octave: 3 },
15
+ topLineYPos: 0,
16
+ bottomLineYPos: 40
17
+ },
18
+ alto: {
19
+ staffType: "alto",
20
+ paddingTop: 0,
21
+ paddingBottom: 0,
22
+ topLineNote: { name: "G", octave: 4 },
23
+ topLineYPos: 0,
24
+ bottomLineYPos: 40
25
+ },
26
+ grand: {
27
+ staffType: "grand",
28
+ paddingTop: 13,
29
+ paddingBottom: 0,
30
+ topLineNote: { name: "F", octave: 5 },
31
+ topLineYPos: 0,
32
+ bottomLineYPos: 110
33
+ }
34
+ }, d = {
35
+ w: 4,
36
+ h: 2,
37
+ q: 1,
38
+ e: 0.5,
39
+ s: 0.25
40
+ }, X = {
41
+ CLEF_TREBLE: {
42
+ path: "m150 273 10 58c2 7 2 7 12 7 59 0 96 46 96 97 0 45-26 79-67 95-5 2-6 2-5 7 4 25 12 63 12 85 0 68-52 80-79 80-61 0-76-39-76-65 0-25 16-46 43-46 24 0 38 19 38 41 0 23-14 34-27 38-9 2-13 4-13 6 0 6 11 12 32 12 24 0 64-7 64-66 0-19-6-54-11-81-1-5-1-4-6-3l-27 2C48 540 0 474 0 404c0-80 61-138 119-185 5-4 4-5 3-10-2-16-5-42-5-65 0-42 9-92 39-125 8-9 20-19 26-19 4 0 15 11 21 20 16 24 26 58 26 93 0 61-33 112-76 153-3 2-3 2-3 7Zm38-211c-24 0-53 38-53 101l2 37c1 5 3 5 5 3 32-28 70-64 70-108 0-22-11-33-24-33Zm-44 272-8-51c-1-4-2-5-6-1-18 15-37 30-61 56-33 38-37 70-37 93 0 56 45 95 115 95l23-2c6-2 6-2 5-6l-20-119c-1-5-1-5-8-3-24 6-40 24-40 46 0 19 12 36 29 43 3 1 6 3 6 5 0 3-2 5-5 5l-11-3c-27-9-46-34-46-70 0-34 23-66 58-78 8-2 8-2 6-10Zm28 64 20 114c0 5 1 5 6 2 22-11 38-31 38-56 0-36-27-63-60-66-4 0-5 1-4 6Z",
43
+ xOffset: 0,
44
+ yOffset: -14
45
+ },
46
+ CLEF_BASS: {
47
+ path: "M101 0c68 0 110 46 110 114 0 120-102 190-199 237l-7 2c-3 0-5-2-5-5s2-5 6-7c92-52 146-114 146-223 0-62-18-103-60-103-40 0-64 29-64 44 0 4 1 8 7 8 5 0 9-3 19-3 20 0 38 15 38 41 0 24-17 41-42 41-32 0-48-27-48-58C2 50 33 0 101 0Zm148 32c13 0 22 10 22 22s-9 22-22 22c-12 0-21-10-21-22s9-22 21-22Zm1 99c12 0 21 9 21 21s-9 22-21 22-21-10-21-22 9-21 21-21Z",
48
+ xOffset: 0,
49
+ yOffset: 0
50
+ },
51
+ CLEF_ALTO: {
52
+ path: "M91 9v174c0 3 2 2 3 2 11-3 27-13 36-58 1-6 3-10 7-10s6 4 8 11c5 17 15 37 43 37 25 0 32-25 32-77s-9-75-42-75c-5 0-33 2-33 10 0 2 6 5 11 6 7 3 15 11 15 26 0 17-11 27-26 27-17 0-31-11-31-32 0-25 22-50 69-50 65 0 93 45 93 87 0 54-30 92-83 92-11 0-18-2-24-4-4-1-8-1-11 1-6 3-14 16-14 24s8 21 14 24c3 2 7 2 11 1 6-2 13-4 24-4 53 0 83 38 83 92 0 42-28 87-93 87-47 0-69-25-69-50 0-21 14-32 31-32 15 0 26 10 26 27 0 15-8 23-15 26-5 1-11 4-11 6 0 8 28 10 33 10 33 0 42-23 42-75s-7-77-32-77c-28 0-38 20-43 37-2 7-4 11-8 11s-6-4-7-10c-9-45-25-55-36-58-1 0-3-1-3 2v174c0 5-3 8-8 8h-1c-5 0-8-3-8-8V9c0-5 3-8 8-8h1c5 0 8 3 8 8ZM8 1h34c6 0 9 3 9 8v382c0 5-3 8-9 8H8c-5 0-8-3-8-8V9c0-5 3-8 8-8Z",
53
+ xOffset: 0,
54
+ yOffset: 0
55
+ },
56
+ NOTE_HEAD_WHOLE: {
57
+ path: "M77 0c34 0 74 19 74 45 0 24-19 45-77 45C21 90 0 68 0 45 0 20 30 0 77 0Zm-9 8c-17 0-30 5-30 24 0 22 23 50 47 50 16 0 28-8 28-26 0-21-20-48-45-48Z",
58
+ xOffset: 0,
59
+ yOffset: -4.5
60
+ },
61
+ NOTE_HEAD_HALF: {
62
+ path: "M35 90C15 90 0 79 0 60 0 42 17 0 70 0c21 0 36 12 36 30 0 12-12 60-71 60Zm-8-14c18 0 68-31 68-47l-2-6c-3-5-7-8-14-8-17 0-68 29-68 46l2 7c2 4 7 8 14 8Z",
63
+ xOffset: 0,
64
+ yOffset: -4.5
65
+ },
66
+ NOTE_HEAD_QUARTER: {
67
+ path: "M35 90C16 90 0 79 0 60 0 29 32 0 71 0c20 0 35 12 35 30 0 30-40 60-71 60Z",
68
+ xOffset: 0,
69
+ yOffset: -4.5
70
+ },
71
+ EIGHTH_NOTE: {
72
+ path: "M142 89c20 32 36 70 36 110 0 25-8 55-8 55-2 5-5 7-8 6h-1c-2-1-5-4-5-9l1-5c5-14 8-29 8-44 0-19-3-37-8-48-10-23-33-58-53-70v179c0 30-38 60-70 60-19 0-34-11-34-30 0-31 31-60 70-60 11 0 19 3 25 8V3c0-2 2-3 3-3 6 0 9 2 10 7 5 31 18 57 34 82Z",
73
+ xOffset: 0,
74
+ yOffset: -28
75
+ },
76
+ EIGHTH_NOTE_FLIPPED: {
77
+ path: "M9 323H0V60C0 29 32 0 71 0c20 0 35 12 35 30 0 30-40 60-71 60-19 0-26-9-26-9v158s58-56 58-89c0 0 0-25-8-43-5-12 10-17 14-6 13 34 9 48 9 48 0 39-41 97-41 97-20 27-32 77-32 77Z",
78
+ xOffset: 0,
79
+ yOffset: -4
80
+ },
81
+ REST_WHOLE: {
82
+ path: "M0 0h104v53H0V0Z",
83
+ xOffset: 0,
84
+ yOffset: 0
85
+ },
86
+ REST_HALF: {
87
+ path: "M0 0h104v53H0V0Z",
88
+ xOffset: 0,
89
+ yOffset: -5
90
+ },
91
+ REST_QUARTER: {
92
+ path: "m28 151-18-22s-3-4-3-9c0-3 1-7 5-11 16-17 22-33 22-46 0-27-21-45-23-49l-1-6c0-5 4-8 7-8s5 1 7 3l61 71 1 7-1 5c-10 15-23 34-25 55v3c0 20 11 35 25 52 4 4 14 16 14 20 0 2-2 2-5 1-6-2-19-6-29-6-16 0-23 12-23 27 0 8 5 22 11 22 3 3 6 6 6 9s-2 5-7 5l-9-3c-27-21-43-39-43-57s13-35 32-35c3 0 10 3 14 0l-2-6-16-22Z",
93
+ xOffset: 0,
94
+ yOffset: -16
95
+ },
96
+ REST_EIGHTH: {
97
+ path: "M61 35c18 0 41-35 47-32 0 1 5 3 5 8l-5 17S64 189 62 190c-4 4-11 5-16 5-3 0-13 0-13-6 8-30 41-122 42-128 1-5 1-10-1-10-4 0-3 3-19 8C16 71 0 46 0 31 0 14 14 0 31 0c5 0 30 1 30 35Z",
98
+ xOffset: 0,
99
+ yOffset: -10
100
+ },
101
+ ACCIDENTAL_SHARP: {
102
+ path: "m67 66-8 3c-2 0-3 6-3 8v26c0 4 2 5 3 5l8-2 1-1c1 0 2 1 2 3v20c0 1-1 4-3 4l-8 4c-2 0-3 4-3 6v40c0 2-2 4-5 4-2 0-4-2-4-4v-35c0-2-1-5-4-5h-1l-17 7c-1 1-3 3-3 6v40c0 2-1 3-4 3-2 0-4-1-4-3v-35c0-1-1-5-3-5l-1 1-7 2v1c-2 0-3-1-3-3v-20c0-2 1-4 3-5l8-3c2-1 3-3 3-6V94c0-3-2-5-4-5l-7 3c-2 0-3-1-3-3V69l3-4 8-3c1-1 3-4 3-8V16c0-2 2-4 5-4 2 0 3 2 3 4v34c0 2 1 5 4 5l18-7c2-1 3-5 3-8V3c0-2 2-3 5-3 2 0 4 1 4 3v35c0 2 2 3 4 3l7-2h1c1 0 2 0 2 2v20c0 2-1 4-3 5Zm-20 46c2-11 2-23 0-34l-4-2c-7 0-20 6-21 11v35l4 1c6 0 20-5 21-11Z",
103
+ xOffset: -8,
104
+ yOffset: -10
105
+ },
106
+ ACCIDENTAL_FLAT: {
107
+ path: "M4 196C1 193 0 9 0 9c0-6 5-9 10-9 3 0 6 2 6 5l-2 91c0 3 1 5 3 6h2l7-4c5-3 12-6 18-6 15 1 29 13 29 31 0 15-10 35-39 55-8 5-16 14-25 19l-2 1c-1 0-2 0-3-2Zm11-28c0 1 1 5 4 5l3-1c14-9 29-27 29-44 0-8-4-19-14-19-7 0-20 10-22 16l-1 10 1 33Z",
108
+ xOffset: -8,
109
+ yOffset: -14
110
+ },
111
+ ACCIDENTAL_NATURAL: {
112
+ path: "m41 47 5-2h1l2 2v147c0 3-2 4-3 4h-4c-2 0-4-1-4-4v-43c0-2-2-3-5-3-8 0-25 7-29 8l-1 1c-2 0-3-1-3-3V4c0-3 1-4 4-4h3c2 0 4 1 4 4v48c0 2 1 2 3 2 7 0 26-7 26-7h1ZM11 88v31l3 1c8 0 24-6 24-12V78l-2-1c-7 0-25 7-25 11Z",
113
+ xOffset: -8,
114
+ yOffset: -10
115
+ },
116
+ ACCIDENTAL_DOUBLE_SHARP: {
117
+ path: "M68 33h3c6 0 13 0 15-2l2-15C88 4 86 0 72 0c-6 0-12 1-14 4-2 1-2 7-2 13-1 5-8 17-12 17-5 0-9-10-11-15l-1-2c0-6-1-12-3-13-2-3-8-4-13-4C10 0 4 1 2 4L0 17l2 14c2 2 9 2 15 2h4c5 2 16 8 16 12 0 3-13 10-17 12h-3c-6 0-13 1-15 3L0 74l2 14 14 2 13-2c2-2 3-8 3-14 1-5 7-17 12-17 4 0 10 13 12 17 0 6 0 12 2 14l14 2c15 0 16-2 16-17l-2-13c-4-2-11-3-15-3h-3c-5-2-17-7-17-12 0-2 13-10 17-12Z",
118
+ xOffset: -10,
119
+ yOffset: -4
120
+ },
121
+ ACCIDENTAL_DOUBLE_FLAT: {
122
+ path: "M102 93h2c15 0 29 12 29 30 0 15-10 35-39 55-8 5-16 14-26 19l-2 1-2-2-3-43c-6 7-15 16-27 25-7 5-15 14-25 19l-2 1c-1 0-2 0-3-2C2 193 0 8 0 8c0-5 6-8 10-8 3 0 6 2 6 5l-2 91c0 3 1 5 3 6h2c3 0 5-3 8-4 5-3 9-5 15-5h2c6 0 12 1 17 5L60 8c0-5 5-8 10-8 3 0 6 2 6 5l-2 91c0 3 1 5 3 6h2c3 0 5-3 7-4 6-3 10-5 16-5Zm-80 78c14-9 29-25 29-43 0-8-3-19-13-19-8 0-21 10-23 16l-1 11 1 31c0 2 1 5 4 5l3-1Zm59 0c15-9 29-25 29-43 0-6-2-12-5-16-2-2-4-3-8-3-7 0-21 10-23 16v8l1 34c0 2 1 5 3 5l3-1Z",
123
+ xOffset: -14,
124
+ yOffset: -14
125
+ },
126
+ TIME_4: {
127
+ path: "M53 0h66S70 76 20 124h61V86l47-50v89h22v19h-22v27c0 9 13 9 13 9h9v10H61v-10h10s10 0 10-10v-26H0v-20S54 67 53 0Z",
128
+ xOffset: 0,
129
+ yOffset: 0
130
+ },
131
+ TIME_3: {
132
+ path: "M2 43c0 21 15 30 30 30 5 0 28-2 28-27 0-11-16-20-16-24 0-14 56-15 56 26 0 26-19 38-31 38H45v13h24c12 0 33 15 33 41 0 38-54 40-54 25 0-3 10-11 10-21 0-20-15-27-27-27-17 0-32 8-31 30 1 30 36 43 75 43 36 0 75-19 75-50 0-34-23-47-37-47v-2c4 0 35-13 35-39 0-37-35-52-75-52C39 0 3 14 2 43Z",
133
+ xOffset: 0,
134
+ yOffset: 0
135
+ }
136
+ }, x = /^(?<name>[A-Ga-g])(?<accidental>##|bb|[#bn]?)(?<octave>\d)(?<duration>[whqeWHQE]?)$/, U = /^[whqeWHQE]$/, I = ["C", "D", "E", "F", "G", "A", "B"];
137
+ function g(h) {
138
+ const e = h.match(x);
139
+ if (!e || !e.groups)
140
+ throw new Error(`Invalid note string format: ${h}. Expected format: [A-Ga-g][#|b]?[0-9][w|h|q|e].`);
141
+ let { name: t, accidental: s, octave: r, duration: o } = e.groups;
142
+ if (t = t.toUpperCase(), o = o.toLowerCase(), !t)
143
+ throw new Error(`Invalid note name: ${t}. Valid note names are: C, D, E, F, G, A, B.`);
144
+ if (!r)
145
+ throw new Error(`Invalid octave: ${r}. Octave must be a number between 0 and 9.`);
146
+ o || (o = "w");
147
+ const n = {
148
+ name: t,
149
+ octave: parseInt(r),
150
+ duration: o
151
+ };
152
+ return s && (n.accidental = s), n;
153
+ }
154
+ function T(h) {
155
+ const e = h.match(U);
156
+ if (!e) throw new Error(`Invalid note duration '${h}'. Use w | h | q | e.`);
157
+ return e.toString().toLowerCase();
158
+ }
159
+ function S(h) {
160
+ let e;
161
+ switch (h) {
162
+ case "treble":
163
+ e = "CLEF_TREBLE";
164
+ break;
165
+ case "bass":
166
+ e = "CLEF_BASS";
167
+ break;
168
+ case "alto":
169
+ e = "CLEF_ALTO";
170
+ break;
171
+ }
172
+ if (!e) throw new Error(`Invalid clef type: ${h}. Valid clef types are: treble, bass, alto.`);
173
+ return e;
174
+ }
175
+ function D(h, e) {
176
+ const t = I.indexOf(h.name) - I.indexOf(e.name);
177
+ let s = h.octave - e.octave;
178
+ return s *= 7, t + s;
179
+ }
180
+ function R(h) {
181
+ let e = I.indexOf(h.name);
182
+ return e += h.octave * 12, e;
183
+ }
184
+ function y(h, e) {
185
+ return I.findIndex((s) => s === h) + 1 + e * 7;
186
+ }
187
+ const N = 48, w = 40 + 30 / 2, b = 20, F = 90;
188
+ class H {
189
+ params;
190
+ rendererRef;
191
+ width = 0;
192
+ constructor(e, t) {
193
+ this.rendererRef = e;
194
+ const s = B[t];
195
+ if (!s) throw new Error(`Staff type ${t} is not supported`);
196
+ this.params = s;
197
+ }
198
+ drawStaffLines = (e, t) => {
199
+ let s = e;
200
+ for (let r = 0; r < 5; r++)
201
+ this.rendererRef.drawLine(0, s, this.width, s, t), s += 10;
202
+ return s - 10;
203
+ };
204
+ drawStaff = (e) => {
205
+ this.width = e;
206
+ const t = this.rendererRef.getLayerByName("staff");
207
+ let s = this.drawStaffLines(0, t), r = this.drawStaffLines(s + 30, t);
208
+ this.rendererRef.drawLine(0, 0, 0, r, t), this.rendererRef.drawLine(this.width, 0, this.width, r, t);
209
+ let o = r, n = 1;
210
+ const a = S("treble"), i = S("bass");
211
+ this.rendererRef.drawGlyph(a, t), this.rendererRef.drawGlyph(i, t, { yOffset: s + 30 }), o += this.params.paddingBottom + 1, n += this.params.paddingTop, this.rendererRef.addTotalRootSvgHeight(o), this.rendererRef.addTotalRootSvgYOffset(n);
212
+ };
213
+ shouldNoteFlip(e) {
214
+ const t = Math.abs(e - F), s = Math.abs(e - b);
215
+ return e === w ? !1 : t <= s ? !(e >= F) : e <= b;
216
+ }
217
+ getLedgerLinesX(e, t) {
218
+ const s = [], r = R(e);
219
+ let o = e.duration === "w" ? 17 : 12.5;
220
+ if (r === N)
221
+ return s.push({ x1: -2, x2: o, yPos: 0 }), s;
222
+ if (t < this.params.topLineYPos) {
223
+ let n = this.params.topLineYPos - 10;
224
+ for (; n >= t; )
225
+ s.push({
226
+ x1: -2,
227
+ x2: o,
228
+ yPos: n - t
229
+ }), n -= 10;
230
+ } else if (t > this.params.bottomLineYPos) {
231
+ let n = this.params.bottomLineYPos + 10;
232
+ for (; n <= t; )
233
+ s.push({
234
+ x1: -2,
235
+ x2: o,
236
+ yPos: n - t
237
+ }), n += 10;
238
+ }
239
+ return s;
240
+ }
241
+ calculateNoteYPos = (e) => {
242
+ let s = D(this.params.topLineNote, e) * (10 / 2);
243
+ const r = R(e);
244
+ return r < N ? s += 10 : r === N && (s = w), s;
245
+ };
246
+ }
247
+ const M = 20;
248
+ class A {
249
+ params;
250
+ rendererRef;
251
+ constructor(e, t) {
252
+ this.rendererRef = e;
253
+ const s = B[t];
254
+ if (!s) throw new Error(`Staff type ${t} is not supported`);
255
+ this.params = s;
256
+ }
257
+ drawStaff = (e) => {
258
+ const t = this.rendererRef.getLayerByName("staff");
259
+ let s = 0;
260
+ for (let a = 0; a < 5; a++)
261
+ this.rendererRef.drawLine(0, s, e, s, t), s += 10;
262
+ this.rendererRef.drawLine(0, 0, 0, s - 10, t), this.rendererRef.drawLine(e, 0, e, s - 10, t);
263
+ let r = s - 10 + 1, o = 1;
264
+ const n = S(this.params.staffType);
265
+ this.rendererRef.drawGlyph(n, t), r += this.params.paddingTop + this.params.paddingBottom, o += this.params.paddingTop, this.rendererRef.addTotalRootSvgHeight(r), this.rendererRef.addTotalRootSvgYOffset(o);
266
+ };
267
+ shouldNoteFlip(e) {
268
+ return e <= M;
269
+ }
270
+ getLedgerLinesX(e, t) {
271
+ const s = [];
272
+ let r = e.duration === "w" ? 17 : 12.5;
273
+ if (t < this.params.topLineYPos) {
274
+ let o = this.params.topLineYPos - 10;
275
+ for (; o >= t; )
276
+ s.push({
277
+ x1: -2,
278
+ x2: r,
279
+ yPos: o - t
280
+ }), o -= 10;
281
+ } else if (t > this.params.bottomLineYPos) {
282
+ let o = this.params.bottomLineYPos + 10;
283
+ for (; o <= t; )
284
+ s.push({
285
+ x1: -2,
286
+ x2: r,
287
+ yPos: o - t
288
+ }), o += 10;
289
+ }
290
+ return s;
291
+ }
292
+ calculateNoteYPos = (e) => D(this.params.topLineNote, e) * (10 / 2);
293
+ }
294
+ class P {
295
+ svgRendererInstance;
296
+ strategyInstance;
297
+ constructor(e, t) {
298
+ this.svgRendererInstance = e, this.strategyInstance = t;
299
+ }
300
+ drawStem(e, t) {
301
+ t ? this.svgRendererInstance.drawLine(0, 0, 0, 28, e) : this.svgRendererInstance.drawLine(10, 0, 10, -28, e);
302
+ }
303
+ chordOffsetConsecutiveAccidentals(e) {
304
+ let t = 0, s = 0, r = 0;
305
+ for (let o = 0; o < e.length; o++) {
306
+ const n = e[o];
307
+ if (n.noteObj.accidental && r < 3 ? (t += -8, s = Math.min(s, t), r++) : n.noteObj.accidental && r <= 3 ? (t = -8, r = 1) : (t = 0, r = 0), t !== 0) {
308
+ const i = Array.from(n.noteGroup.getElementsByTagName("use")).find((c) => c.getAttribute("href")?.includes("ACCIDENTAL"));
309
+ if (!i) continue;
310
+ i.setAttribute("transform", `translate(${t + 8}, 0)`);
311
+ }
312
+ }
313
+ return -s;
314
+ }
315
+ chordOffsetCloseNotes(e) {
316
+ let t = e[0], s = 0;
317
+ for (let r = 1; r < e.length; r++) {
318
+ const o = e[r];
319
+ if (y(o.noteObj.name, o.noteObj.octave) - y(t.noteObj.name, t.noteObj.octave) === 1) {
320
+ s = 28 / 2, o.noteGroup.setAttribute("transform", `translate(${s}, ${o.noteYPos})`);
321
+ const i = Array.from(o.noteGroup.getElementsByTagName("use")).find((c) => c.getAttribute("href")?.includes("ACCIDENTAL"));
322
+ if (i) {
323
+ const c = i.getAttribute("transform")?.match(/([-]?\d+)/), l = c && c[0];
324
+ let f = -s;
325
+ l && (f += Number(l)), i.setAttribute("transform", `translate(${f}, 0)`);
326
+ }
327
+ r++, t = e[r];
328
+ continue;
329
+ }
330
+ t = o;
331
+ }
332
+ return s;
333
+ }
334
+ // Handles drawing the glyphs to internal group, applies the xPositioning to note Cursor X
335
+ renderNote(e) {
336
+ const t = this.svgRendererInstance.createGroup("note"), s = g(e), r = this.strategyInstance.calculateNoteYPos({
337
+ name: s.name,
338
+ octave: s.octave
339
+ });
340
+ let o = this.strategyInstance.shouldNoteFlip(r);
341
+ switch (s.duration) {
342
+ case "h":
343
+ this.svgRendererInstance.drawGlyph("NOTE_HEAD_HALF", t), this.drawStem(t, o);
344
+ break;
345
+ case "q":
346
+ this.svgRendererInstance.drawGlyph("NOTE_HEAD_QUARTER", t), this.drawStem(t, o);
347
+ break;
348
+ case "e":
349
+ o ? this.svgRendererInstance.drawGlyph("EIGHTH_NOTE_FLIPPED", t) : this.svgRendererInstance.drawGlyph("EIGHTH_NOTE", t);
350
+ break;
351
+ default:
352
+ this.svgRendererInstance.drawGlyph("NOTE_HEAD_WHOLE", t);
353
+ }
354
+ let n = 0;
355
+ switch (s.accidental) {
356
+ case "#":
357
+ this.svgRendererInstance.drawGlyph("ACCIDENTAL_SHARP", t), n -= -8;
358
+ break;
359
+ case "b":
360
+ this.svgRendererInstance.drawGlyph("ACCIDENTAL_FLAT", t), n -= -8;
361
+ break;
362
+ case "n":
363
+ this.svgRendererInstance.drawGlyph("ACCIDENTAL_NATURAL", t), n -= -8;
364
+ break;
365
+ case "##":
366
+ this.svgRendererInstance.drawGlyph("ACCIDENTAL_DOUBLE_SHARP", t), n -= -10;
367
+ break;
368
+ case "bb":
369
+ this.svgRendererInstance.drawGlyph("ACCIDENTAL_DOUBLE_FLAT", t), n -= -14;
370
+ break;
371
+ }
372
+ return this.strategyInstance.getLedgerLinesX(s, r).forEach((i) => {
373
+ this.svgRendererInstance.drawLine(i.x1, i.yPos, i.x2, i.yPos, t);
374
+ }), {
375
+ noteGroup: t,
376
+ noteObj: s,
377
+ noteYPos: r,
378
+ accidentalOffset: n,
379
+ cursorOffset: 0
380
+ };
381
+ }
382
+ renderChord(e) {
383
+ const t = this.svgRendererInstance.createGroup("chord"), s = [];
384
+ for (const n of e) {
385
+ const a = this.renderNote(n);
386
+ a.noteGroup.setAttribute("transform", `translate(0, ${a.noteYPos})`), t.appendChild(a.noteGroup), s.push({
387
+ noteGroup: a.noteGroup,
388
+ noteObj: a.noteObj,
389
+ noteYPos: a.noteYPos,
390
+ cursorOffset: 0,
391
+ accidentalOffset: 0
392
+ });
393
+ }
394
+ const r = this.chordOffsetConsecutiveAccidentals(s), o = this.chordOffsetCloseNotes(s);
395
+ return {
396
+ noteGroup: t,
397
+ noteObj: s[0].noteObj,
398
+ noteYPos: 0,
399
+ accidentalOffset: r,
400
+ cursorOffset: o
401
+ };
402
+ }
403
+ }
404
+ const u = "http://www.w3.org/2000/svg", Y = 0.1, m = 1200;
405
+ class O {
406
+ rootElementRef;
407
+ svgElementRef;
408
+ // Layers
409
+ parentGroupContainer;
410
+ musicStaffLayer;
411
+ musicNotesLayer;
412
+ musicUILayer;
413
+ // Positioning variables
414
+ width;
415
+ scale;
416
+ totalYOffset = 0;
417
+ totalHeight = 0;
418
+ // Setups root SVG element and layers, sets attributes for scaling, creates defs for glyphs
419
+ // Does not append to DOM automatically, must be done manually with commitElementsToDOM and passing rootSvgElement
420
+ // from get rootSvgElement().
421
+ // HEIGHT and YOfsset should be set externally, values are calculated internally.
422
+ constructor(e, t) {
423
+ this.rootElementRef = e, this.width = t.width, this.scale = t.scale, this.width > m && (this.width = m, console.warn(`Width provided for the staff ${this.width} exceeds the limit ${m}. Please use this value to fix positioning issues.`)), this.svgElementRef = document.createElementNS(u, "svg"), this.svgElementRef.classList.add("vs-svg-renderer-root"), this.svgElementRef.style.maxWidth = "100%", this.svgElementRef.style.height = "auto", this.svgElementRef.style.display = "block", this.svgElementRef.setAttribute("color", t.staffColor), this.svgElementRef.style.backgroundColor = t.staffBackgroundColor, this.makeGlyphDefs(t.useGlyphs), this.parentGroupContainer = this.createGroup("svg-renderer-parent"), this.svgElementRef.appendChild(this.parentGroupContainer), this.musicStaffLayer = this.createGroup("music-staff-layer"), this.musicNotesLayer = this.createGroup("music-notes-layer"), this.musicUILayer = this.createGroup("music-ui-layer"), this.parentGroupContainer.appendChild(this.musicStaffLayer), this.parentGroupContainer.appendChild(this.musicNotesLayer), this.parentGroupContainer.appendChild(this.musicUILayer), this.musicNotesLayer.setAttribute("transform", "translate(38, 0)");
424
+ }
425
+ // Creates SVG defs for all glyphs in GLYPH_ENTRIES, applies global scale and offsets, appends to root SVG
426
+ makeGlyphDefs(e) {
427
+ const t = document.createElementNS(u, "defs");
428
+ Object.entries(X).filter(([r]) => e.includes(r)).forEach(([r, o]) => {
429
+ const n = document.createElementNS(u, "path");
430
+ n.setAttribute("id", `glyph-${r}`), n.setAttribute("d", o.path), n.setAttribute("fill", "currentColor"), n.setAttribute("transform", `translate(${o.xOffset}, ${o.yOffset}) scale(${Y})`), t.appendChild(n);
431
+ }), this.rootSvgElement.appendChild(t);
432
+ }
433
+ createGroup(e) {
434
+ const t = document.createElementNS(u, "g");
435
+ return e && t.classList.add(`vs-${e}`), t;
436
+ }
437
+ commitElementsToDOM(e, t = this.rootElementRef) {
438
+ const s = document.createDocumentFragment();
439
+ Array.isArray(e) ? e.forEach((r) => {
440
+ s.appendChild(r);
441
+ }) : s.appendChild(e), t.appendChild(s);
442
+ }
443
+ getLayerByName(e) {
444
+ switch (e) {
445
+ case "staff":
446
+ return this.musicStaffLayer;
447
+ case "notes":
448
+ return this.musicNotesLayer;
449
+ case "ui":
450
+ return this.musicUILayer;
451
+ default:
452
+ throw new Error(`Layer with name '${e}' does not exist.`);
453
+ }
454
+ }
455
+ addTotalRootSvgHeight(e) {
456
+ this.totalHeight += e;
457
+ }
458
+ addTotalRootSvgYOffset(e) {
459
+ this.totalYOffset += e;
460
+ }
461
+ applySizingToRootSvg() {
462
+ this.parentGroupContainer.setAttribute("transform", `translate(0, ${this.totalYOffset})`);
463
+ let e = this.width * this.scale;
464
+ const t = (this.totalHeight + this.totalYOffset) * this.scale;
465
+ e += 2, this.svgElementRef.setAttribute("width", e.toString()), this.svgElementRef.setAttribute("height", t.toString()), this.svgElementRef.setAttribute("viewBox", `0 0 ${this.width} ${this.totalHeight + this.totalYOffset}`);
466
+ }
467
+ get rootSvgElement() {
468
+ return this.svgElementRef;
469
+ }
470
+ get parentGroupElement() {
471
+ return this.parentGroupContainer;
472
+ }
473
+ // Drawing Methods
474
+ drawLine(e, t, s, r, o) {
475
+ const n = document.createElementNS(u, "line");
476
+ n.setAttribute("x1", e.toString()), n.setAttribute("y1", t.toString()), n.setAttribute("x2", s.toString()), n.setAttribute("y2", r.toString()), n.setAttribute("stroke", "currentColor"), n.setAttribute("stroke-width", "1"), o.appendChild(n);
477
+ }
478
+ drawRect(e, t, s, r) {
479
+ const o = document.createElementNS(u, "rect");
480
+ return o.setAttribute("width", e.toString()), o.setAttribute("height", t.toString()), o.setAttribute("x", (r?.x ?? 0).toString()), o.setAttribute("y", (r?.y ?? 0).toString()), r?.fill ? o.setAttribute("fill", r.fill) : o.setAttribute("fill", "currentColor"), s.appendChild(o), o;
481
+ }
482
+ drawGlyph(e, t, s) {
483
+ s = {
484
+ xOffset: 0,
485
+ yOffset: 0,
486
+ ...s
487
+ };
488
+ const r = document.createElementNS(u, "use");
489
+ r.setAttribute("href", `#glyph-${e}`), (s.xOffset || s.yOffset) && r.setAttribute("transform", `translate(${s.xOffset}, ${s.yOffset})`), t.appendChild(r);
490
+ }
491
+ destroy() {
492
+ this.rootElementRef && this.svgElementRef && this.rootElementRef.contains(this.svgElementRef) && this.rootElementRef.removeChild(this.svgElementRef);
493
+ }
494
+ }
495
+ const $ = [
496
+ "CLEF_TREBLE",
497
+ "CLEF_BASS",
498
+ "CLEF_ALTO",
499
+ "NOTE_HEAD_WHOLE",
500
+ "NOTE_HEAD_HALF",
501
+ "NOTE_HEAD_QUARTER",
502
+ "EIGHTH_NOTE",
503
+ "EIGHTH_NOTE_FLIPPED",
504
+ "ACCIDENTAL_SHARP",
505
+ "ACCIDENTAL_FLAT",
506
+ "ACCIDENTAL_NATURAL",
507
+ "ACCIDENTAL_DOUBLE_SHARP",
508
+ "ACCIDENTAL_DOUBLE_FLAT"
509
+ ];
510
+ class j {
511
+ svgRendererInstance;
512
+ strategyInstance;
513
+ noteRendererInstance;
514
+ options;
515
+ noteEntries = [];
516
+ noteCursorX = 0;
517
+ /**
518
+ * Creates an instance of a MusicStaff, A single staff.
519
+ *
520
+ * @param rootElementCtx - The element (div) reference that will append the music staff elements to.
521
+ * @param options - Optional configuration settings. Can adjust staff type (treble, bass, alto, grand), width, scale, spaces above/below, etc. All config options are in the type MusicStaffOptions
522
+ */
523
+ constructor(e, t) {
524
+ this.options = {
525
+ width: 300,
526
+ scale: 1,
527
+ staffType: "treble",
528
+ spaceAbove: 0,
529
+ spaceBelow: 0,
530
+ staffColor: "black",
531
+ staffBackgroundColor: "transparent",
532
+ ...t
533
+ }, this.svgRendererInstance = new O(e, {
534
+ width: this.options.width,
535
+ height: 100,
536
+ scale: this.options.scale,
537
+ staffColor: this.options.staffColor,
538
+ staffBackgroundColor: this.options.staffBackgroundColor,
539
+ useGlyphs: $
540
+ });
541
+ const s = this.svgRendererInstance.rootSvgElement;
542
+ switch (this.options.staffType) {
543
+ case "grand":
544
+ this.strategyInstance = new H(this.svgRendererInstance, "grand");
545
+ break;
546
+ case "bass":
547
+ this.strategyInstance = new A(this.svgRendererInstance, "bass");
548
+ break;
549
+ case "treble":
550
+ this.strategyInstance = new A(this.svgRendererInstance, "treble");
551
+ break;
552
+ case "alto":
553
+ this.strategyInstance = new A(this.svgRendererInstance, "alto");
554
+ break;
555
+ default:
556
+ throw new Error(`The staff type ${this.options.staffType} is not supported. Please use "treble", "bass", "alto", or "grand".`);
557
+ }
558
+ if (this.strategyInstance.drawStaff(this.options.width), this.noteRendererInstance = new P(this.svgRendererInstance, this.strategyInstance), this.options.spaceAbove) {
559
+ const r = this.options.spaceAbove * 10;
560
+ this.svgRendererInstance.addTotalRootSvgYOffset(r);
561
+ }
562
+ if (this.options.spaceBelow) {
563
+ let r = this.options.spaceBelow * 10;
564
+ this.options.staffType === "grand" && (r -= 10 / 2), this.svgRendererInstance.addTotalRootSvgHeight(r);
565
+ }
566
+ this.svgRendererInstance.applySizingToRootSvg(), this.svgRendererInstance.commitElementsToDOM(s);
567
+ }
568
+ /**
569
+ * Draws a note on the staff.
570
+ * @param notes - A single string OR array of note strings in the format `[Root][Accidental?][Octave][Duration?]`.
571
+ * If an array is passed, it will draw each individual note on the staff.
572
+ *
573
+ * * **Root**: (A-G)
574
+ * * **Accidental** (Optional): `#` (sharp) `b` (flat) `n` (natural) `##` (double sharp) or `bb` (double flat).
575
+ * * **Octave**: The octave number (e.g., `4`).
576
+ * * **Duration** (Optional): `w` (whole) `h` (half) `q` (quarter) or `e` (eighth). Defaults to `w` if duration is omitted
577
+ * @returns void
578
+ * @throws {Error} If a note string is not correct format. If an array was passed, it will still draw whatever correctly formatted notes before it.
579
+ *
580
+ * * @example
581
+ * // Draws the specified notes individually on the staff
582
+ * drawNote(["D4w", "F4w", "A4w", "B#5q", "Ebb4e"]);
583
+ *
584
+ * * @example
585
+ * // Draws the specified single note on the staff
586
+ * drawNote("D4w");
587
+ */
588
+ drawNote(e) {
589
+ const t = Array.isArray(e) ? e : [e], s = this.svgRendererInstance.getLayerByName("notes"), r = [];
590
+ for (const o of t) {
591
+ let n;
592
+ try {
593
+ n = this.noteRendererInstance.renderNote(o);
594
+ } catch (a) {
595
+ throw r.length > 0 && this.svgRendererInstance.commitElementsToDOM(r, s), a;
596
+ }
597
+ n.noteGroup.setAttribute("transform", `translate(${this.noteCursorX + n.accidentalOffset}, ${n.noteYPos})`), this.noteEntries.push({
598
+ gElement: n.noteGroup,
599
+ note: n.noteObj,
600
+ xPos: this.noteCursorX + n.accidentalOffset,
601
+ yPos: n.noteYPos,
602
+ accidentalXOffset: n.accidentalOffset
603
+ }), this.noteCursorX += 28 + n.accidentalOffset, r.push(n.noteGroup);
604
+ }
605
+ this.svgRendererInstance.commitElementsToDOM(r, s);
606
+ }
607
+ /**
608
+ * Draws a chord on the staff.
609
+ * @param notes - An array of note strings in the format `[Root][Accidental?][Octave][Duration?]`.
610
+ *
611
+ * * **Root**: (A-G)
612
+ * * **Accidental** (Optional): `#` (sharp) `b` (flat) `n` (natural) `##` (double sharp) or `bb` (double flat).
613
+ * * **Octave**: The octave number (e.g., `4`).
614
+ * * **Duration** (Optional): `w` (whole) `h` (half) `q` (quarter) or `e` (eighth). Defaults to `w` if duration is omitted
615
+ * @returns void
616
+ * @throws {Error} If a note string is not correct format OR if less than one note was provided.
617
+ *
618
+ * * @example
619
+ * // Draw a D minor chord starting on 4th octave
620
+ * drawChord(["D4w", "F4w", "A4w"], 0);
621
+ */
622
+ drawChord(e) {
623
+ if (e.length < 2) throw new Error("Provide more than one note for a chord.");
624
+ const t = this.svgRendererInstance.getLayerByName("notes"), s = this.noteRendererInstance.renderChord(e);
625
+ s.noteGroup.setAttribute("transform", `translate(${this.noteCursorX + s.accidentalOffset}, 0)`), this.noteEntries.push({
626
+ gElement: s.noteGroup,
627
+ note: g(e[0]),
628
+ xPos: this.noteCursorX + s.accidentalOffset,
629
+ yPos: 0,
630
+ accidentalXOffset: s.accidentalOffset
631
+ }), this.noteCursorX += 28 + s.accidentalOffset + s.cursorOffset, this.svgRendererInstance.commitElementsToDOM(s.noteGroup, t);
632
+ }
633
+ /**
634
+ * Evenly spaces out the notes on the staff.
635
+ * @returns Returns early if no notes are on the staff
636
+ */
637
+ justifyNotes() {
638
+ const e = this.options.width - 38, t = this.noteEntries.length;
639
+ if (t <= 0 || e <= 0) return;
640
+ const s = Math.round(e / t);
641
+ this.noteEntries.map((o, n) => {
642
+ const a = (n + 0.5) * s, i = o.gElement.getBBox(), c = a - i.width / 2 - i.x, l = Math.round(c * 10) / 10;
643
+ return {
644
+ entry: o,
645
+ newX: l,
646
+ isChord: o.gElement.classList.contains("chord")
647
+ };
648
+ }).forEach((o) => {
649
+ const { entry: n, newX: a, isChord: i } = o;
650
+ i ? n.gElement.setAttribute("transform", `translate(${a}, 0)`) : n.gElement.setAttribute("transform", `translate(${a}, ${n.yPos})`), n.xPos = a;
651
+ });
652
+ }
653
+ /**
654
+ * Clears staff of notes and resets internal positioning.
655
+ * @returns void
656
+ */
657
+ clearAllNotes() {
658
+ this.noteCursorX = 0, this.svgRendererInstance.getLayerByName("notes").replaceChildren(), this.noteEntries = [];
659
+ }
660
+ /**
661
+ * Changes the note by index to the specified note.
662
+ * @param notes - A note string in the format `[Root][Accidental?][Octave][Duration?]`.
663
+ *
664
+ * * **Root**: (A-G)
665
+ * * **Accidental** (Optional): `#` (sharp) `b` (flat) `n` (natural) `##` (double sharp) or `bb` (double flat).
666
+ * * **Octave**: The octave number (e.g., `4`).
667
+ * * **Duration** (Optional): `w` (whole) `h` (half) `q` (quarter) or `e` (eighth). Defaults to `w` if duration is omitted
668
+ * @param noteIndex The index of the note that will replaced by the specified note.
669
+ * @returns void
670
+ * @throws {Error} If the index provided is out of bounds, or if a note string is not correct format.
671
+ *
672
+ * * @example
673
+ * // Changes note at pos `0` to a B flat quarter note on the 3rd octave
674
+ * changeNoteByIndex("Bb3q", 0);
675
+ */
676
+ changeNoteByIndex(e, t) {
677
+ if (t >= this.noteEntries.length) throw new Error("Note index was out of bounds.");
678
+ const s = this.noteEntries[t], r = this.noteRendererInstance.renderNote(e), n = s.xPos - s.accidentalXOffset + r.accidentalOffset;
679
+ r.noteGroup.setAttribute("transform", `translate(${n}, ${r.noteYPos})`), this.svgRendererInstance.getLayerByName("notes").replaceChild(r.noteGroup, s.gElement), this.noteEntries[t] = {
680
+ gElement: r.noteGroup,
681
+ note: r.noteObj,
682
+ xPos: n,
683
+ yPos: r.noteYPos,
684
+ accidentalXOffset: r.accidentalOffset
685
+ };
686
+ }
687
+ /**
688
+ * Changes the note by index to the specified chord.
689
+ * @param notes - An array of note strings in the format `[Root][Accidental?][Octave][Duration?]`.
690
+ *
691
+ * * **Root**: (A-G)
692
+ * * **Accidental** (Optional): `#` (sharp) `b` (flat) `n` (natural) `##` (double sharp) or `bb` (double flat).
693
+ * * **Octave**: The octave number (e.g., `4`).
694
+ * * **Duration** (Optional): `w` (whole) `h` (half) `q` (quarter) or `e` (eighth). Defaults to `w` if duration is omitted
695
+ * @param noteIndex The index of the note that will replaced by the specified chord.
696
+ * @returns void
697
+ * @throws {Error} If the index provided is out of bounds, or if a note string is not correct format.
698
+ *
699
+ * * @example
700
+ * // Changes chord at pos `0` to a C Minor chord
701
+ * changeChordByIndex(["C4w", "D#4w", "G4w"], 0);
702
+ */
703
+ changeChordByIndex(e, t) {
704
+ if (t >= this.noteEntries.length) throw new Error("Chord index was out of bounds.");
705
+ if (e.length < 2) throw new Error("Notes provided need to be more than one to be considered a chord.");
706
+ const s = this.noteEntries[t], r = this.noteRendererInstance.renderChord(e), n = s.xPos - s.accidentalXOffset + r.accidentalOffset;
707
+ r.noteGroup.setAttribute("transform", `translate(${n}, 0)`), this.svgRendererInstance.getLayerByName("notes").replaceChild(r.noteGroup, s.gElement), this.noteEntries[t] = {
708
+ gElement: r.noteGroup,
709
+ note: g(e[0]),
710
+ xPos: n,
711
+ yPos: 0,
712
+ accidentalXOffset: r.accidentalOffset
713
+ };
714
+ }
715
+ /**
716
+ * Adds a class to the note by the index provided.
717
+ * @param className The name added to the note
718
+ * @param noteIndex The index of the note that will have 'className' added to it.
719
+ * @returns void
720
+ * @throws {Error} If the index provided is out of bounds
721
+ */
722
+ addClassToNoteByIndex(e, t) {
723
+ if (t >= this.noteEntries.length) throw new Error("Note index was out of bounds.");
724
+ this.noteEntries[t].gElement.classList.add(e);
725
+ }
726
+ /**
727
+ * Removes a class to the note by the index provided.
728
+ * @param className The name removed from the note
729
+ * @param noteIndex The index of the note that will have 'className' removed from it.
730
+ * @returns void
731
+ * @throws {Error} If the index provided is out of bounds
732
+ */
733
+ removeClassToNoteByIndex(e, t) {
734
+ if (t >= this.noteEntries.length) throw new Error("Note index was out of bounds.");
735
+ this.noteEntries[t].gElement.classList.remove(e);
736
+ }
737
+ /**
738
+ * Removes the root svg element and cleans up arrays.
739
+ * @returns void
740
+ */
741
+ destroy() {
742
+ this.noteEntries = [], this.svgRendererInstance.destroy();
743
+ }
744
+ }
745
+ const E = 30, G = 19, C = 12, k = 4, W = 6, v = 1, L = 38, Z = [
746
+ "TIME_4",
747
+ "TIME_3",
748
+ "NOTE_HEAD_WHOLE",
749
+ "NOTE_HEAD_HALF",
750
+ "NOTE_HEAD_QUARTER",
751
+ "EIGHTH_NOTE",
752
+ "REST_WHOLE",
753
+ "REST_HALF",
754
+ "REST_QUARTER",
755
+ "REST_EIGHTH"
756
+ ];
757
+ class Q {
758
+ rendererInstance;
759
+ options;
760
+ barSpacing;
761
+ quarterNoteSpacing;
762
+ noteCursorX = 0;
763
+ noteEntries = [];
764
+ maxBeatCount;
765
+ currentBeatCount = 0;
766
+ currentBeatUICount = 0;
767
+ currentBeatUIElement = null;
768
+ currentBeatUIXPos = L;
769
+ /**
770
+ * Creates an instance of a RhythmStaff, A single staff that will automatically apply positioning of elements based on the duration of a note.
771
+ *
772
+ * @param rootElementCtx - The element (div) reference that will append the music staff elements to.
773
+ * @param options - Optional configuration settings. All config options are in the type RhythmStaffOptions
774
+ * @throws {Error} - If top number is not 3 or 4 OR if bars count is not between 1 - 3. These are the currently only supported values.
775
+ */
776
+ constructor(e, t) {
777
+ this.options = {
778
+ width: 300,
779
+ scale: 1,
780
+ topNumber: 4,
781
+ barsCount: 2,
782
+ spaceAbove: 0,
783
+ spaceBelow: 0,
784
+ staffColor: "black",
785
+ staffBackgroundColor: "white",
786
+ currentBeatUIColor: "#24ff7450",
787
+ ...t
788
+ }, this.rendererInstance = new O(e, {
789
+ width: this.options.width,
790
+ height: 100,
791
+ scale: this.options.scale,
792
+ staffColor: this.options.staffColor,
793
+ staffBackgroundColor: this.options.staffBackgroundColor,
794
+ useGlyphs: Z
795
+ });
796
+ const s = this.rendererInstance.rootSvgElement;
797
+ let r = "TIME_4";
798
+ switch (this.options.topNumber) {
799
+ case 3:
800
+ r = "TIME_3";
801
+ break;
802
+ case 4:
803
+ r = "TIME_4";
804
+ break;
805
+ default:
806
+ throw new Error(`Time signature ${this.options.topNumber} not supported. Please use either 3 or 4.`);
807
+ }
808
+ if (this.options.barsCount < 1 || this.options.barsCount > 3) throw new Error(`Bars count ${this.options.barsCount} not supported. Please use 1 - 3`);
809
+ if (this.options.spaceAbove) {
810
+ const p = this.options.spaceAbove * 10;
811
+ this.rendererInstance.addTotalRootSvgYOffset(p);
812
+ }
813
+ if (this.options.spaceBelow) {
814
+ let p = this.options.spaceBelow * 10;
815
+ this.rendererInstance.addTotalRootSvgHeight(p);
816
+ }
817
+ const o = this.rendererInstance.getLayerByName("staff");
818
+ this.rendererInstance.addTotalRootSvgHeight(E * 2);
819
+ const n = this.rendererInstance.createGroup("time-signature");
820
+ o.appendChild(n);
821
+ const a = E - G;
822
+ this.rendererInstance.drawGlyph(r, n), this.rendererInstance.drawGlyph("TIME_4", n, { yOffset: G }), n.setAttribute("transform", `translate(0, ${a})`);
823
+ let i = this.options.width - 38;
824
+ this.options.barsCount > 1 && (i -= (this.options.barsCount - 1) * C), i -= v, this.rendererInstance.drawLine(0, E, this.options.width - v, E, o), this.barSpacing = i / this.options.barsCount, this.quarterNoteSpacing = Math.round(this.barSpacing / this.options.topNumber), this.maxBeatCount = this.options.barsCount * this.options.topNumber;
825
+ let c = this.barSpacing + 38;
826
+ const l = E / 2, f = E + l;
827
+ for (let p = 0; p < this.options.barsCount - 1; p++)
828
+ this.rendererInstance.drawLine(c, l, c, f, o), c += this.barSpacing;
829
+ this.rendererInstance.getLayerByName("notes").setAttribute("transform", `translate(38, ${E})`), this.rendererInstance.applySizingToRootSvg(), this.rendererInstance.commitElementsToDOM(s);
830
+ }
831
+ createBeatUIElement() {
832
+ const e = this.rendererInstance.getLayerByName("ui");
833
+ this.currentBeatUIElement = this.rendererInstance.drawRect(
834
+ this.quarterNoteSpacing / 2,
835
+ E * 2,
836
+ e,
837
+ {
838
+ x: L,
839
+ fill: this.options.currentBeatUIColor
840
+ }
841
+ );
842
+ }
843
+ handleNewBar() {
844
+ this.noteCursorX += C;
845
+ }
846
+ // Translates group, returns cursor increment amount
847
+ translateGroupByDuration(e, t) {
848
+ return t.setAttribute("transform", `translate(${this.noteCursorX}, 0)`), this.quarterNoteSpacing * e;
849
+ }
850
+ drawStem(e, t) {
851
+ this.rendererInstance.drawLine(10 + (t ?? 0), 0, 10 + (t ?? 0), -28, e);
852
+ }
853
+ renderNote(e, t) {
854
+ switch (e) {
855
+ case "w":
856
+ this.rendererInstance.drawGlyph("NOTE_HEAD_WHOLE", t);
857
+ break;
858
+ case "h":
859
+ this.rendererInstance.drawGlyph("NOTE_HEAD_HALF", t), this.drawStem(t);
860
+ break;
861
+ case "q":
862
+ this.rendererInstance.drawGlyph("NOTE_HEAD_QUARTER", t), this.drawStem(t);
863
+ break;
864
+ case "e":
865
+ this.rendererInstance.drawGlyph("EIGHTH_NOTE", t), this.drawStem(t);
866
+ break;
867
+ }
868
+ }
869
+ renderRest(e, t) {
870
+ switch (e) {
871
+ case "w":
872
+ this.rendererInstance.drawGlyph("REST_WHOLE", t);
873
+ break;
874
+ case "h":
875
+ this.rendererInstance.drawGlyph("REST_HALF", t);
876
+ break;
877
+ case "q":
878
+ this.rendererInstance.drawGlyph("REST_QUARTER", t);
879
+ break;
880
+ case "e":
881
+ this.rendererInstance.drawGlyph("REST_EIGHTH", t);
882
+ break;
883
+ }
884
+ }
885
+ checkAndCreateNewBar() {
886
+ const e = this.currentBeatCount > 0 && this.currentBeatCount % this.options.topNumber === 0, t = this.currentBeatCount < this.maxBeatCount;
887
+ e && t && this.handleNewBar();
888
+ }
889
+ checkAndFillBarWithRests(e) {
890
+ const t = this.options.topNumber - this.currentBeatCount % this.options.topNumber;
891
+ if (e > t) {
892
+ const s = this.createRemainingRests(t);
893
+ return this.handleNewBar(), s;
894
+ }
895
+ return null;
896
+ }
897
+ // If the last beat exceeded the remaining value in bar, fill the space with approiate rests
898
+ createRemainingRests(e) {
899
+ const t = [];
900
+ let s = e;
901
+ for (; s > 0; ) {
902
+ const r = this.rendererInstance.createGroup("rest");
903
+ let o = 0;
904
+ s - d.h >= 0 ? (this.rendererInstance.drawGlyph("REST_HALF", r), o = d.h) : s - d.q >= 0 ? (this.rendererInstance.drawGlyph("REST_QUARTER", r), o = d.q) : (this.rendererInstance.drawGlyph("REST_EIGHTH", r), o = d.e), s -= o, this.currentBeatCount += o, r.setAttribute("transform", `translate(${this.noteCursorX}, 0)`), this.noteCursorX += o * this.quarterNoteSpacing, t.push(r);
905
+ }
906
+ return t;
907
+ }
908
+ renderBeamRect(e, t, s, r) {
909
+ this.rendererInstance.drawRect(
910
+ e - t,
911
+ k,
912
+ s,
913
+ {
914
+ x: 10,
915
+ y: -28 + (r ?? 0),
916
+ fill: this.options.staffColor
917
+ }
918
+ );
919
+ }
920
+ /**
921
+ * Draws a note duration on the staff.
922
+ * @param notes - A single string OR array of note strings in the format `[Duration]`.
923
+ * If an array is passed, it will draw each individual note duration on the staff.
924
+ * If a duration exceeds the remaining value on the bar, rests will fill the empty space.
925
+ *
926
+ * * **Duration**: `w` (whole) `h` (half) `q` (quarter) `e` (eighth)
927
+ * @returns void
928
+ * @throws {Error} If a note string is not correct format. If an array was passed, it will still draw whatever correctly formatted notes before it.
929
+ *
930
+ * * @example
931
+ * // Draws the specified note durations individually on the staff
932
+ * drawNote(["q", "q", "q", "q", "w"]);
933
+ *
934
+ * * @example
935
+ * // Draws the specified single note duration on the staff
936
+ * drawNote("w");
937
+ */
938
+ drawNote(e) {
939
+ const t = Array.isArray(e) ? e : [e], s = this.rendererInstance.getLayerByName("notes"), r = [];
940
+ for (const o of t) {
941
+ let n = "w";
942
+ try {
943
+ n = T(o);
944
+ } catch (f) {
945
+ throw r.length > 0 && this.rendererInstance.commitElementsToDOM(r, s), f;
946
+ }
947
+ const a = d[n];
948
+ if (this.currentBeatCount >= this.maxBeatCount)
949
+ throw r.length > 0 && this.rendererInstance.commitElementsToDOM(r, s), new Error("Max beat count reached. Can't add additional notes.");
950
+ this.checkAndCreateNewBar();
951
+ const i = this.checkAndFillBarWithRests(a);
952
+ i && i.forEach((f) => {
953
+ r.push(f), this.noteEntries.push(f);
954
+ });
955
+ const c = this.rendererInstance.createGroup("note"), l = this.translateGroupByDuration(a, c);
956
+ this.noteCursorX += l, this.currentBeatCount += a, this.renderNote(n, c), r.push(c), this.noteEntries.push(c);
957
+ }
958
+ this.rendererInstance.commitElementsToDOM(r, s);
959
+ }
960
+ /**
961
+ * Draws a rest duration on the staff.
962
+ * @param rests - A single string OR array of rest strings in the format `[Duration]`.
963
+ * If an array is passed, it will draw each individual rest duration on the staff.
964
+ * If a duration exceeds the remaining value on the bar, rests will fill the empty space.
965
+ *
966
+ * * **Duration**: `w` (whole) `h` (half) `q` (quarter) `e` (eighth)
967
+ * @returns void
968
+ * @throws {Error} If a rest string is not correct format. If an array was passed, it will still draw whatever correctly formatted rests before it.
969
+ *
970
+ * * @example
971
+ * // Draws the specified rest durations individually on the staff
972
+ * drawRest(["q", "q", "q", "q", "w"]);
973
+ *
974
+ * * @example
975
+ * // Draws the specified single rest duration on the staff
976
+ * drawRest("w");
977
+ */
978
+ drawRest(e) {
979
+ const t = Array.isArray(e) ? e : [e], s = this.rendererInstance.getLayerByName("notes"), r = [];
980
+ for (const o of t) {
981
+ let n = "w";
982
+ try {
983
+ n = T(o);
984
+ } catch (f) {
985
+ throw r.length > 0 && this.rendererInstance.commitElementsToDOM(r, s), f;
986
+ }
987
+ const a = this.rendererInstance.createGroup("rest"), i = d[n], c = i * this.quarterNoteSpacing;
988
+ if (this.currentBeatCount >= this.maxBeatCount)
989
+ throw r.length > 0 && this.rendererInstance.commitElementsToDOM(r, s), new Error("Max beat count reached. Can't add additional notes.");
990
+ this.checkAndCreateNewBar();
991
+ const l = this.checkAndFillBarWithRests(i);
992
+ l && l.forEach((f) => {
993
+ r.push(f), this.noteEntries.push(f);
994
+ }), this.renderRest(n, a), a.setAttribute("transform", `translate(${this.noteCursorX}, 0)`), this.noteCursorX += c, this.currentBeatCount += i, r.push(a), this.noteEntries.push(a);
995
+ }
996
+ this.rendererInstance.commitElementsToDOM(r, s);
997
+ }
998
+ /**
999
+ * Draws a beamed note of specified duration/count on the staff.
1000
+ * Will stop beam early if bar line is reached / if beat count is over max limit
1001
+ * @param note - A duration string of either 'e' (eighth) or 's' (sixth).
1002
+ * @param noteCount - The amount of notes in the beam
1003
+ *
1004
+ * @returns void
1005
+ * @throws {Error} If a rest string is not correct format. If an array was passed, it will still draw whatever correctly formatted rests before it.
1006
+ *
1007
+ * * @example
1008
+ * // Draws a 4 beamed eighth note
1009
+ * drawBeamedNotes("e", 4);
1010
+ *
1011
+ * * @example
1012
+ * // Draws a 8 beamed sixth note
1013
+ * drawBeamedNotes("s", 8);
1014
+ */
1015
+ drawBeamedNotes(e, t) {
1016
+ if (t < 2)
1017
+ throw new Error("Must provide a value greater than 2 for beamed note.");
1018
+ if (this.currentBeatCount >= this.maxBeatCount)
1019
+ throw new Error("Max beat count reached. Can't add additional beamed note.");
1020
+ let s = "s";
1021
+ e === "s" ? s = "s" : s = T(e), this.checkAndCreateNewBar();
1022
+ const r = this.rendererInstance.getLayerByName("notes"), o = d[s], n = o * this.quarterNoteSpacing, a = this.options.topNumber - this.currentBeatCount % this.options.topNumber, i = Math.min(t, a / o), c = this.rendererInstance.createGroup("beamed-note");
1023
+ c.setAttribute("transform", `translate(${this.noteCursorX}, 0)`);
1024
+ let l = 0;
1025
+ for (let f = 0; f < i; f++)
1026
+ this.rendererInstance.drawGlyph("NOTE_HEAD_QUARTER", c, { xOffset: l }), this.drawStem(c, l), l += n, this.currentBeatCount += o;
1027
+ this.renderBeamRect(l, n, c), e === "s" && this.renderBeamRect(l, n, c, W), this.noteCursorX += l, this.noteEntries.push(c), this.rendererInstance.commitElementsToDOM(c, r);
1028
+ }
1029
+ /**
1030
+ * Will increment the UI showing the current beat in quarters. Once exceeded, must be reset with `resetCurrentBeatUI()`
1031
+ * @returns void
1032
+ */
1033
+ incrementCurrentBeatUI() {
1034
+ if (this.currentBeatUIElement || this.createBeatUIElement(), this.currentBeatUICount >= this.maxBeatCount) {
1035
+ this.currentBeatUIElement.setAttribute("display", "none");
1036
+ return;
1037
+ }
1038
+ this.currentBeatUIElement?.getAttribute("display") === "none" && this.currentBeatUIElement.removeAttribute("display"), this.currentBeatUICount++, this.currentBeatUICount > this.options.topNumber && this.currentBeatUICount % this.options.topNumber === 1 && (this.currentBeatUIXPos += C), this.currentBeatUICount > 1 && (this.currentBeatUIXPos += this.quarterNoteSpacing), this.currentBeatUIElement.setAttribute("x", this.currentBeatUIXPos.toString());
1039
+ }
1040
+ /**
1041
+ * Resets the ui showing the current beat value.
1042
+ * @returns void
1043
+ */
1044
+ resetCurrentBeatUI() {
1045
+ this.currentBeatUICount = 0, this.currentBeatUIXPos = L, this.currentBeatUIElement && (this.currentBeatUIElement.setAttribute("display", "none"), this.currentBeatUIElement.setAttribute("x", this.currentBeatUIXPos.toString()));
1046
+ }
1047
+ /**
1048
+ * Clears staff of notes and resets internal positioning.
1049
+ * @returns void
1050
+ */
1051
+ clearAllNotes() {
1052
+ this.noteCursorX = 0, this.currentBeatCount = 0, this.rendererInstance.getLayerByName("notes").replaceChildren(), this.noteEntries = [];
1053
+ }
1054
+ /**
1055
+ * Removes the root svg element and cleans up arrays.
1056
+ * @returns void
1057
+ */
1058
+ destroy() {
1059
+ this.noteEntries = [], this.rendererInstance.destroy();
1060
+ }
1061
+ }
1062
+ const V = [
1063
+ "CLEF_TREBLE",
1064
+ "CLEF_BASS",
1065
+ "CLEF_ALTO",
1066
+ "NOTE_HEAD_WHOLE",
1067
+ "NOTE_HEAD_HALF",
1068
+ "NOTE_HEAD_QUARTER",
1069
+ "EIGHTH_NOTE",
1070
+ "EIGHTH_NOTE_FLIPPED",
1071
+ "ACCIDENTAL_SHARP",
1072
+ "ACCIDENTAL_FLAT",
1073
+ "ACCIDENTAL_NATURAL",
1074
+ "ACCIDENTAL_DOUBLE_SHARP",
1075
+ "ACCIDENTAL_DOUBLE_FLAT"
1076
+ ], _ = 60, q = _;
1077
+ class z {
1078
+ svgRendererInstance;
1079
+ strategyInstance;
1080
+ noteRendererInstance;
1081
+ options;
1082
+ activeEntries = [];
1083
+ noteBuffer = [];
1084
+ notesLayer;
1085
+ noteCursorX = 0;
1086
+ /**
1087
+ * Creates an instance of a ScrollingStaff, A single staff that takes in a queue of notes that can be advanced with in a 'endless' style of staff.
1088
+ *
1089
+ * @param rootElementCtx - The element (div) reference that will append the music staff elements to.
1090
+ * @param options - Optional configuration settings. All config options are in the type ScrollingStaffOptions
1091
+ */
1092
+ constructor(e, t) {
1093
+ this.options = {
1094
+ width: 300,
1095
+ scale: 1,
1096
+ staffType: "treble",
1097
+ spaceAbove: 0,
1098
+ spaceBelow: 0,
1099
+ staffColor: "black",
1100
+ staffBackgroundColor: "transparent",
1101
+ ...t
1102
+ }, this.svgRendererInstance = new O(e, {
1103
+ width: this.options.width,
1104
+ height: 100,
1105
+ scale: this.options.scale,
1106
+ staffColor: this.options.staffColor,
1107
+ staffBackgroundColor: this.options.staffBackgroundColor,
1108
+ useGlyphs: V
1109
+ });
1110
+ const s = this.svgRendererInstance.rootSvgElement;
1111
+ switch (this.options.staffType) {
1112
+ case "grand":
1113
+ this.strategyInstance = new H(this.svgRendererInstance, "grand");
1114
+ break;
1115
+ case "bass":
1116
+ this.strategyInstance = new A(this.svgRendererInstance, "bass");
1117
+ break;
1118
+ case "treble":
1119
+ this.strategyInstance = new A(this.svgRendererInstance, "treble");
1120
+ break;
1121
+ case "alto":
1122
+ this.strategyInstance = new A(this.svgRendererInstance, "alto");
1123
+ break;
1124
+ default:
1125
+ throw new Error(`The staff type ${this.options.staffType} is not supported. Please use "treble", "bass", "alto", or "grand".`);
1126
+ }
1127
+ if (this.strategyInstance.drawStaff(this.options.width), this.noteRendererInstance = new P(this.svgRendererInstance, this.strategyInstance), this.options.spaceAbove) {
1128
+ const r = this.options.spaceAbove * 10;
1129
+ this.svgRendererInstance.addTotalRootSvgYOffset(r);
1130
+ }
1131
+ if (this.options.spaceBelow) {
1132
+ let r = this.options.spaceBelow * 10;
1133
+ this.options.staffType === "grand" && (r -= 10 / 2), this.svgRendererInstance.addTotalRootSvgHeight(r);
1134
+ }
1135
+ this.notesLayer = this.svgRendererInstance.getLayerByName("notes"), this.notesLayer.classList.add("vs-scrolling-notes-layer"), this.svgRendererInstance.applySizingToRootSvg(), this.svgRendererInstance.commitElementsToDOM(s);
1136
+ }
1137
+ renderFirstNoteGroups() {
1138
+ const e = this.options.width - 38 + q;
1139
+ for (; this.noteBuffer.length > 0 && this.noteCursorX < e; )
1140
+ this.renderNextNote(), this.noteCursorX += _;
1141
+ this.activeEntries.length > 1 && (this.noteCursorX -= _);
1142
+ }
1143
+ renderNextNote() {
1144
+ if (this.noteBuffer.length < 1) return;
1145
+ const e = this.noteBuffer[0], t = this.svgRendererInstance.createGroup("note-wrapper");
1146
+ if (e.type === "chord") {
1147
+ const s = this.noteRendererInstance.renderChord(e.notes);
1148
+ s.noteGroup.setAttribute("transform", `translate(0, ${s.noteYPos})`), t.appendChild(s.noteGroup);
1149
+ } else {
1150
+ const s = this.noteRendererInstance.renderNote(e.notes[0]);
1151
+ s.noteGroup.setAttribute("transform", `translate(0, ${s.noteYPos})`), t.appendChild(s.noteGroup);
1152
+ }
1153
+ t.style.transform = `translate(${this.noteCursorX}px, 0px)`, this.activeEntries.push({
1154
+ noteWrapper: t,
1155
+ xPos: this.noteCursorX
1156
+ }), this.noteBuffer.shift(), this.notesLayer.appendChild(t);
1157
+ }
1158
+ /**
1159
+ * Adds notes to the queue for scrolling staff. Clears any previously added notes.
1160
+ * @param notes - A array of note strings or sub-arrays of strings.
1161
+ * * A single string will draw a single note `C#4w`
1162
+ * * A sub-array will draw a chord `["C4w", "E4w", "G4w"]`
1163
+ *
1164
+ * Note string format
1165
+ * * **Root**: (A-G)
1166
+ * * **Accidental** (Optional): `#` (sharp) `b` (flat) `n` (natural) `##` (double sharp) or `bb` (double flat).
1167
+ * * **Octave**: The octave number (e.g., `4`).
1168
+ * * **Duration** (Optional): `w` (whole) `h` (half) `q` (quarter) or `e` (eighth). Defaults to `w` if duration is omitted
1169
+ * @param noteIndex The index of the note that will replaced by the specified note.
1170
+ * @returns void
1171
+ * @throws {Error} If the index provided is out of bounds, or if a note string is not correct format.
1172
+ *
1173
+ * * @example
1174
+ * // Queues a few single notes, followed by a C chord
1175
+ * queueNotes("Bb3q", "C4w", "G4q", ["C4w", "E4w", "G4w"]);
1176
+ */
1177
+ queueNotes(e) {
1178
+ this.clearAllNotes();
1179
+ for (const t of e)
1180
+ Array.isArray(t) ? this.noteBuffer.push({
1181
+ type: "chord",
1182
+ notes: t
1183
+ }) : this.noteBuffer.push({
1184
+ type: "note",
1185
+ notes: [t]
1186
+ });
1187
+ this.renderFirstNoteGroups();
1188
+ }
1189
+ /**
1190
+ * Advances to the next note in sequence, if theres any remaining notes left.
1191
+ * @returns void
1192
+ * @callback onNotesOut passed in from the constructor options once notes are out.
1193
+ */
1194
+ advanceNotes() {
1195
+ if (this.activeEntries.length <= 0) {
1196
+ this.clearAllNotes(), this.options.onNotesOut && this.options.onNotesOut();
1197
+ return;
1198
+ }
1199
+ this.activeEntries.forEach((t) => {
1200
+ t.xPos -= _, t.noteWrapper.style.transform = `translate(${t.xPos}px, 0px)`;
1201
+ });
1202
+ const e = this.activeEntries[0];
1203
+ e.xPos <= 0 && (this.notesLayer.removeChild(e.noteWrapper), this.activeEntries.shift()), this.renderNextNote();
1204
+ }
1205
+ /**
1206
+ * Clears staff of notes and resets internal positioning.
1207
+ * @returns void
1208
+ */
1209
+ clearAllNotes() {
1210
+ this.noteCursorX = 0, this.notesLayer.replaceChildren(), this.activeEntries = [], this.noteBuffer = [];
1211
+ }
1212
+ /**
1213
+ * Removes the root svg element and cleans up arrays.
1214
+ * @returns void
1215
+ */
1216
+ destroy() {
1217
+ this.svgRendererInstance.destroy(), this.activeEntries = [], this.noteBuffer = [];
1218
+ }
1219
+ }
1220
+ export {
1221
+ j as MusicStaff,
1222
+ Q as RhythmStaff,
1223
+ z as ScrollingStaff
1224
+ };