text-guitar-chart 0.0.1
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.
- package/AGENTS.md +18 -0
- package/FORMAT.md +277 -0
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/docs/app.js +172 -0
- package/docs/bundle.js +8712 -0
- package/docs/bundle.js.map +7 -0
- package/docs/index.html +93 -0
- package/index.js +7 -0
- package/lib/editableSVGuitar.js +1050 -0
- package/lib/fingeringToString.js +261 -0
- package/lib/layoutChordStrings.js +92 -0
- package/lib/splitStringInRectangles.js +132 -0
- package/lib/stringToFingering.js +435 -0
- package/package.json +43 -0
- package/scripts/build.js +30 -0
- package/test/editableSVGuitar.test.js +444 -0
- package/test/fingeringToString.test.js +705 -0
- package/test/layoutChordStrings.test.js +193 -0
- package/test/splitStringInRectangles.test.js +98 -0
- package/test/stringToFingering.test.js +1086 -0
- package/tsconfig.json +25 -0
- package/types/editableSVGuitar.d.ts +209 -0
- package/types/fingeringToString.d.ts +10 -0
- package/types/layoutChordStrings.d.ts +10 -0
- package/types/splitStringInRectangles.d.ts +18 -0
- package/types/stringToFingering.d.ts +10 -0
|
@@ -0,0 +1,1086 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { describe, test } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import stringToFingering from "../lib/stringToFingering.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {import("svguitar").Finger} fingerA
|
|
9
|
+
* @param {import("svguitar").Finger} fingerB
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
const isFingerEqual = (fingerA, fingerB) => {
|
|
13
|
+
if (!(fingerA[0] === fingerB[0] && fingerA[1] === fingerB[1])) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (typeof fingerA[2] === "object" && typeof fingerB[2] === "object") {
|
|
17
|
+
return (
|
|
18
|
+
fingerA[2].color === fingerB[2].color &&
|
|
19
|
+
fingerA[2].text === fingerB[2].text
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return fingerA === fingerB;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {import("svguitar").Chord | null} result
|
|
27
|
+
* @param {Array<import("svguitar").Finger>} fingers
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
function fingersContains(result, fingers) {
|
|
31
|
+
if (!result) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (fingers.length !== result.fingers.length) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
for (const finger of fingers) {
|
|
38
|
+
const found = result.fingers.find((f) => isFingerEqual(f, finger));
|
|
39
|
+
if (!found) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("stringToFingering", () => {
|
|
47
|
+
describe("ASCII format parsing", () => {
|
|
48
|
+
test("parses simple A minor chord (open strings with dots)", () => {
|
|
49
|
+
const fingeringStr = ` A min
|
|
50
|
+
######
|
|
51
|
+
oo o
|
|
52
|
+
------
|
|
53
|
+
||||o|
|
|
54
|
+
||o*||
|
|
55
|
+
||||||`;
|
|
56
|
+
|
|
57
|
+
const result = stringToFingering(fingeringStr);
|
|
58
|
+
assert.equal(
|
|
59
|
+
fingersContains(result, [
|
|
60
|
+
[
|
|
61
|
+
2,
|
|
62
|
+
1,
|
|
63
|
+
{
|
|
64
|
+
text: "",
|
|
65
|
+
color: "#000000",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
3,
|
|
70
|
+
2,
|
|
71
|
+
{
|
|
72
|
+
text: "",
|
|
73
|
+
color: "#e74c3c",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
4,
|
|
78
|
+
2,
|
|
79
|
+
{
|
|
80
|
+
text: "",
|
|
81
|
+
color: "#000000",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
[5, 0, { text: "", color: "#000000" }],
|
|
85
|
+
[6, 0, { text: "", color: "#000000" }],
|
|
86
|
+
[1, 0, { text: "", color: "#000000" }],
|
|
87
|
+
]),
|
|
88
|
+
true,
|
|
89
|
+
"Fingering does not match expected A minor chord"
|
|
90
|
+
);
|
|
91
|
+
assert.equal(
|
|
92
|
+
result?.title,
|
|
93
|
+
"A min",
|
|
94
|
+
"Title does not match expected A min"
|
|
95
|
+
);
|
|
96
|
+
assert.equal(
|
|
97
|
+
result?.position,
|
|
98
|
+
undefined,
|
|
99
|
+
"Position does not match expected undefined"
|
|
100
|
+
);
|
|
101
|
+
assert.deepEqual(result.barres, [], "Barres should be empty");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("parses D chord with muted strings", () => {
|
|
105
|
+
const fingeringStr = ` D
|
|
106
|
+
######
|
|
107
|
+
xoo
|
|
108
|
+
------
|
|
109
|
+
||||||
|
|
110
|
+
|||o|o
|
|
111
|
+
||||*|`;
|
|
112
|
+
|
|
113
|
+
const result = stringToFingering(fingeringStr);
|
|
114
|
+
|
|
115
|
+
assert.equal(
|
|
116
|
+
fingersContains(result, [
|
|
117
|
+
[
|
|
118
|
+
1,
|
|
119
|
+
2,
|
|
120
|
+
{
|
|
121
|
+
text: "",
|
|
122
|
+
color: "#000000",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
3,
|
|
127
|
+
2,
|
|
128
|
+
{
|
|
129
|
+
text: "",
|
|
130
|
+
color: "#000000",
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
2,
|
|
135
|
+
3,
|
|
136
|
+
{
|
|
137
|
+
text: "",
|
|
138
|
+
color: "#e74c3c",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
142
|
+
[5, 0, {text: "", color: "#000000"}],
|
|
143
|
+
[4, 0, {text: "", color: "#000000"}],
|
|
144
|
+
]),
|
|
145
|
+
true,
|
|
146
|
+
"Fingering does not match expected D chord"
|
|
147
|
+
);
|
|
148
|
+
assert.equal(result?.title, "D", "Title does not match expected D");
|
|
149
|
+
assert.equal(
|
|
150
|
+
result?.position,
|
|
151
|
+
undefined,
|
|
152
|
+
"Position does not match expected undefined"
|
|
153
|
+
);
|
|
154
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("parses G7 chord with starting fret number", () => {
|
|
158
|
+
const fingeringStr = ` G 7
|
|
159
|
+
######
|
|
160
|
+
xx
|
|
161
|
+
------
|
|
162
|
+
5||*|||
|
|
163
|
+
||||o|
|
|
164
|
+
|||o|o`;
|
|
165
|
+
|
|
166
|
+
const result = stringToFingering(fingeringStr);
|
|
167
|
+
|
|
168
|
+
assert.equal(
|
|
169
|
+
fingersContains(result, [
|
|
170
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
171
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
172
|
+
[
|
|
173
|
+
4,
|
|
174
|
+
1,
|
|
175
|
+
{
|
|
176
|
+
text: "",
|
|
177
|
+
color: "#e74c3c",
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
[
|
|
181
|
+
2,
|
|
182
|
+
2,
|
|
183
|
+
{
|
|
184
|
+
text: "",
|
|
185
|
+
color: "#000000",
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
[
|
|
189
|
+
3,
|
|
190
|
+
3,
|
|
191
|
+
{
|
|
192
|
+
text: "",
|
|
193
|
+
color: "#000000",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
[
|
|
197
|
+
1,
|
|
198
|
+
3,
|
|
199
|
+
{
|
|
200
|
+
text: "",
|
|
201
|
+
color: "#000000",
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
]),
|
|
205
|
+
true,
|
|
206
|
+
"Fingering does not match expected G7 chord"
|
|
207
|
+
);
|
|
208
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
209
|
+
assert.equal(result?.position, 5, "Position does not match expected 5");
|
|
210
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("parses G7 chord with starting fret number 1", () => {
|
|
214
|
+
const fingeringStr = ` G 7
|
|
215
|
+
######
|
|
216
|
+
xx
|
|
217
|
+
------
|
|
218
|
+
1||*|||
|
|
219
|
+
||||o|
|
|
220
|
+
|||o|o`;
|
|
221
|
+
|
|
222
|
+
const result = stringToFingering(fingeringStr);
|
|
223
|
+
|
|
224
|
+
assert.equal(
|
|
225
|
+
fingersContains(result, [
|
|
226
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
227
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
228
|
+
[
|
|
229
|
+
4,
|
|
230
|
+
1,
|
|
231
|
+
{
|
|
232
|
+
text: "",
|
|
233
|
+
color: "#e74c3c",
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
[
|
|
237
|
+
2,
|
|
238
|
+
2,
|
|
239
|
+
{
|
|
240
|
+
text: "",
|
|
241
|
+
color: "#000000",
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
[
|
|
245
|
+
3,
|
|
246
|
+
3,
|
|
247
|
+
{
|
|
248
|
+
text: "",
|
|
249
|
+
color: "#000000",
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
[
|
|
253
|
+
1,
|
|
254
|
+
3,
|
|
255
|
+
{
|
|
256
|
+
text: "",
|
|
257
|
+
color: "#000000",
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
]),
|
|
261
|
+
true,
|
|
262
|
+
"Fingering does not match expected G7 chord"
|
|
263
|
+
);
|
|
264
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
265
|
+
assert.equal(result?.position, 1, "Position does not match expected 1");
|
|
266
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("parses G7 chord with starting fret number 1 (alternate version)", () => {
|
|
270
|
+
const fingeringStr = ` G 7
|
|
271
|
+
######
|
|
272
|
+
xx
|
|
273
|
+
======
|
|
274
|
+
||*|||
|
|
275
|
+
||||o|
|
|
276
|
+
|||o|o`;
|
|
277
|
+
|
|
278
|
+
const result = stringToFingering(fingeringStr);
|
|
279
|
+
|
|
280
|
+
assert.equal(
|
|
281
|
+
fingersContains(result, [
|
|
282
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
283
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
284
|
+
[
|
|
285
|
+
4,
|
|
286
|
+
1,
|
|
287
|
+
{
|
|
288
|
+
text: "",
|
|
289
|
+
color: "#e74c3c",
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
[
|
|
293
|
+
2,
|
|
294
|
+
2,
|
|
295
|
+
{
|
|
296
|
+
text: "",
|
|
297
|
+
color: "#000000",
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
[
|
|
301
|
+
3,
|
|
302
|
+
3,
|
|
303
|
+
{
|
|
304
|
+
text: "",
|
|
305
|
+
color: "#000000",
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
[
|
|
309
|
+
1,
|
|
310
|
+
3,
|
|
311
|
+
{
|
|
312
|
+
text: "",
|
|
313
|
+
color: "#000000",
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
]),
|
|
317
|
+
true,
|
|
318
|
+
"Fingering does not match expected G7 chord"
|
|
319
|
+
);
|
|
320
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
321
|
+
assert.equal(result?.position, 1, "Position does not match expected 1");
|
|
322
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("parses G7 chord with 2 digits starting fret number", () => {
|
|
326
|
+
const fingeringStr = ` G 7
|
|
327
|
+
######
|
|
328
|
+
xx
|
|
329
|
+
------
|
|
330
|
+
15||*|||
|
|
331
|
+
||||o|
|
|
332
|
+
|||o|o`;
|
|
333
|
+
|
|
334
|
+
const result = stringToFingering(fingeringStr);
|
|
335
|
+
|
|
336
|
+
assert.equal(
|
|
337
|
+
fingersContains(result, [
|
|
338
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
339
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
340
|
+
[
|
|
341
|
+
4,
|
|
342
|
+
1,
|
|
343
|
+
{
|
|
344
|
+
text: "",
|
|
345
|
+
color: "#e74c3c",
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
[
|
|
349
|
+
2,
|
|
350
|
+
2,
|
|
351
|
+
{
|
|
352
|
+
text: "",
|
|
353
|
+
color: "#000000",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
[
|
|
357
|
+
3,
|
|
358
|
+
3,
|
|
359
|
+
{
|
|
360
|
+
text: "",
|
|
361
|
+
color: "#000000",
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
[
|
|
365
|
+
1,
|
|
366
|
+
3,
|
|
367
|
+
{
|
|
368
|
+
text: "",
|
|
369
|
+
color: "#000000",
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
]),
|
|
373
|
+
true,
|
|
374
|
+
"Fingering does not match expected G7 chord"
|
|
375
|
+
);
|
|
376
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
377
|
+
assert.equal(result?.position, 15, "Position does not match expected 15");
|
|
378
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("parses E dom 7 chord with finger numbers", () => {
|
|
382
|
+
const fingeringStr = ` E dom 7
|
|
383
|
+
#######
|
|
384
|
+
x x
|
|
385
|
+
------
|
|
386
|
+
|||3||
|
|
387
|
+
|51|||
|
|
388
|
+
||||7|`;
|
|
389
|
+
|
|
390
|
+
const result = stringToFingering(fingeringStr);
|
|
391
|
+
assert.equal(
|
|
392
|
+
fingersContains(result, [
|
|
393
|
+
[
|
|
394
|
+
5,
|
|
395
|
+
2,
|
|
396
|
+
{
|
|
397
|
+
text: "5",
|
|
398
|
+
color: "#000000",
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
[
|
|
402
|
+
4,
|
|
403
|
+
2,
|
|
404
|
+
{
|
|
405
|
+
text: "1",
|
|
406
|
+
color: "#000000",
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
[
|
|
410
|
+
3,
|
|
411
|
+
1,
|
|
412
|
+
{
|
|
413
|
+
text: "3",
|
|
414
|
+
color: "#000000",
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
[
|
|
418
|
+
2,
|
|
419
|
+
3,
|
|
420
|
+
{
|
|
421
|
+
text: "7",
|
|
422
|
+
color: "#000000",
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
426
|
+
[1, "x", {text: "", color: "#000000"}],
|
|
427
|
+
]),
|
|
428
|
+
true,
|
|
429
|
+
"Fingering does not match expected E dom 7 chord with finger numbers"
|
|
430
|
+
);
|
|
431
|
+
assert.equal(
|
|
432
|
+
result?.title,
|
|
433
|
+
"E dom 7",
|
|
434
|
+
"Title does not match expected E dom 7"
|
|
435
|
+
);
|
|
436
|
+
assert.equal(
|
|
437
|
+
result?.position,
|
|
438
|
+
undefined,
|
|
439
|
+
"Position does not match expected undefined"
|
|
440
|
+
);
|
|
441
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe("Unicode format parsing", () => {
|
|
446
|
+
test("parses A minor chord (Unicode format)", () => {
|
|
447
|
+
const fingeringStr = ` A min
|
|
448
|
+
‾‾‾‾‾‾‾‾‾‾‾
|
|
449
|
+
○ ○ ○
|
|
450
|
+
┌─┬─┬─┬─┬─┐
|
|
451
|
+
│ │ │ │ ○ │
|
|
452
|
+
├─┼─┼─┼─┼─┤
|
|
453
|
+
│ │ ○ ● │ │
|
|
454
|
+
├─┼─┼─┼─┼─┤
|
|
455
|
+
│ │ │ │ │ │
|
|
456
|
+
└─┴─┴─┴─┴─┘`;
|
|
457
|
+
|
|
458
|
+
const result = stringToFingering(fingeringStr);
|
|
459
|
+
assert.equal(
|
|
460
|
+
fingersContains(result, [
|
|
461
|
+
[
|
|
462
|
+
2,
|
|
463
|
+
1,
|
|
464
|
+
{
|
|
465
|
+
text: "",
|
|
466
|
+
color: "#000000",
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
[
|
|
470
|
+
3,
|
|
471
|
+
2,
|
|
472
|
+
{
|
|
473
|
+
text: "",
|
|
474
|
+
color: "#e74c3c",
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
[
|
|
478
|
+
4,
|
|
479
|
+
2,
|
|
480
|
+
{
|
|
481
|
+
text: "",
|
|
482
|
+
color: "#000000",
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
[5, 0, { text: "", color: "#000000" }],
|
|
486
|
+
[6, 0, { text: "", color: "#000000" }],
|
|
487
|
+
[1, 0, { text: "", color: "#000000" }],
|
|
488
|
+
]),
|
|
489
|
+
true,
|
|
490
|
+
"Fingering does not match expected A minor chord"
|
|
491
|
+
);
|
|
492
|
+
assert.equal(
|
|
493
|
+
result?.title,
|
|
494
|
+
"A min",
|
|
495
|
+
"Title does not match expected A min"
|
|
496
|
+
);
|
|
497
|
+
assert.equal(
|
|
498
|
+
result?.position,
|
|
499
|
+
undefined,
|
|
500
|
+
"Position does not match expected undefined"
|
|
501
|
+
);
|
|
502
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty"); });
|
|
503
|
+
test("parses D chord (Unicode format)", () => {
|
|
504
|
+
const fingeringStr = ` D
|
|
505
|
+
‾‾‾‾‾‾‾‾‾‾‾
|
|
506
|
+
× ○ ○
|
|
507
|
+
┌─┬─┬─┬─┬─┐
|
|
508
|
+
│ │ │ │ │ │
|
|
509
|
+
├─┼─┼─┼─┼─┤
|
|
510
|
+
│ │ │ ○ │ ○
|
|
511
|
+
├─┼─┼─┼─┼─┤
|
|
512
|
+
│ │ │ │ ● │
|
|
513
|
+
└─┴─┴─┴─┴─┘`;
|
|
514
|
+
|
|
515
|
+
const result = stringToFingering(fingeringStr);
|
|
516
|
+
|
|
517
|
+
assert.equal(
|
|
518
|
+
fingersContains(result, [
|
|
519
|
+
[
|
|
520
|
+
1,
|
|
521
|
+
2,
|
|
522
|
+
{
|
|
523
|
+
text: "",
|
|
524
|
+
color: "#000000",
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
[
|
|
528
|
+
3,
|
|
529
|
+
2,
|
|
530
|
+
{
|
|
531
|
+
text: "",
|
|
532
|
+
color: "#000000",
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
[
|
|
536
|
+
2,
|
|
537
|
+
3,
|
|
538
|
+
{
|
|
539
|
+
text: "",
|
|
540
|
+
color: "#e74c3c",
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
544
|
+
[5, 0, {text: "", color: "#000000"}],
|
|
545
|
+
[4, 0, {text: "", color: "#000000"}],
|
|
546
|
+
]),
|
|
547
|
+
true,
|
|
548
|
+
"Fingering does not match expected D chord"
|
|
549
|
+
);
|
|
550
|
+
assert.equal(result?.title, "D", "Title does not match expected D");
|
|
551
|
+
assert.equal(
|
|
552
|
+
result?.position,
|
|
553
|
+
undefined,
|
|
554
|
+
"Position does not match expected undefined"
|
|
555
|
+
);
|
|
556
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("parses G7 chord with starting fret (Unicode format)", () => {
|
|
560
|
+
const fingeringStr = ` G 7
|
|
561
|
+
‾‾‾‾‾‾‾‾‾‾‾
|
|
562
|
+
× ×
|
|
563
|
+
┌─┬─┬─┬─┬─┐
|
|
564
|
+
5│ │ ● │ │ │
|
|
565
|
+
├─┼─┼─┼─┼─┤
|
|
566
|
+
│ │ │ │ ○ │
|
|
567
|
+
├─┼─┼─┼─┼─┤
|
|
568
|
+
│ │ │ ○ │ ○
|
|
569
|
+
└─┴─┴─┴─┴─┘`;
|
|
570
|
+
|
|
571
|
+
const result = stringToFingering(fingeringStr);
|
|
572
|
+
|
|
573
|
+
assert.equal(
|
|
574
|
+
fingersContains(result, [
|
|
575
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
576
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
577
|
+
[
|
|
578
|
+
4,
|
|
579
|
+
1,
|
|
580
|
+
{
|
|
581
|
+
text: "",
|
|
582
|
+
color: "#e74c3c",
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
[
|
|
586
|
+
2,
|
|
587
|
+
2,
|
|
588
|
+
{
|
|
589
|
+
text: "",
|
|
590
|
+
color: "#000000",
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
[
|
|
594
|
+
3,
|
|
595
|
+
3,
|
|
596
|
+
{
|
|
597
|
+
text: "",
|
|
598
|
+
color: "#000000",
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
[
|
|
602
|
+
1,
|
|
603
|
+
3,
|
|
604
|
+
{
|
|
605
|
+
text: "",
|
|
606
|
+
color: "#000000",
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
]),
|
|
610
|
+
true,
|
|
611
|
+
"Fingering does not match expected G7 chord"
|
|
612
|
+
);
|
|
613
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
614
|
+
assert.equal(result?.position, 5, "Position does not match expected 5");
|
|
615
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("parses G7 chord with 2 digits starting fret (Unicode format)", () => {
|
|
619
|
+
const fingeringStr = ` G 7
|
|
620
|
+
‾‾‾‾‾‾‾‾‾‾‾
|
|
621
|
+
× ×
|
|
622
|
+
┌─┬─┬─┬─┬─┐
|
|
623
|
+
15│ │ ● │ │ │
|
|
624
|
+
├─┼─┼─┼─┼─┤
|
|
625
|
+
│ │ │ │ ○ │
|
|
626
|
+
├─┼─┼─┼─┼─┤
|
|
627
|
+
│ │ │ ○ │ ○
|
|
628
|
+
└─┴─┴─┴─┴─┘`;
|
|
629
|
+
|
|
630
|
+
const result = stringToFingering(fingeringStr);
|
|
631
|
+
|
|
632
|
+
assert.equal(
|
|
633
|
+
fingersContains(result, [
|
|
634
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
635
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
636
|
+
[
|
|
637
|
+
4,
|
|
638
|
+
1,
|
|
639
|
+
{
|
|
640
|
+
text: "",
|
|
641
|
+
color: "#e74c3c",
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
[
|
|
645
|
+
2,
|
|
646
|
+
2,
|
|
647
|
+
{
|
|
648
|
+
text: "",
|
|
649
|
+
color: "#000000",
|
|
650
|
+
},
|
|
651
|
+
],
|
|
652
|
+
[
|
|
653
|
+
3,
|
|
654
|
+
3,
|
|
655
|
+
{
|
|
656
|
+
text: "",
|
|
657
|
+
color: "#000000",
|
|
658
|
+
},
|
|
659
|
+
],
|
|
660
|
+
[
|
|
661
|
+
1,
|
|
662
|
+
3,
|
|
663
|
+
{
|
|
664
|
+
text: "",
|
|
665
|
+
color: "#000000",
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
]),
|
|
669
|
+
true,
|
|
670
|
+
"Fingering does not match expected G7 chord"
|
|
671
|
+
);
|
|
672
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
673
|
+
assert.equal(result?.position, 15, "Position does not match expected 15");
|
|
674
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("parses G7 chord with 1 starting fret (Unicode format)", () => {
|
|
678
|
+
const fingeringStr = ` G 7
|
|
679
|
+
‾‾‾‾‾‾‾‾‾‾‾
|
|
680
|
+
× ×
|
|
681
|
+
┌─┬─┬─┬─┬─┐
|
|
682
|
+
1│ │ ● │ │ │
|
|
683
|
+
├─┼─┼─┼─┼─┤
|
|
684
|
+
│ │ │ │ ○ │
|
|
685
|
+
├─┼─┼─┼─┼─┤
|
|
686
|
+
│ │ │ ○ │ ○
|
|
687
|
+
└─┴─┴─┴─┴─┘`;
|
|
688
|
+
|
|
689
|
+
const result = stringToFingering(fingeringStr);
|
|
690
|
+
|
|
691
|
+
assert.equal(
|
|
692
|
+
fingersContains(result, [
|
|
693
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
694
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
695
|
+
[
|
|
696
|
+
4,
|
|
697
|
+
1,
|
|
698
|
+
{
|
|
699
|
+
text: "",
|
|
700
|
+
color: "#e74c3c",
|
|
701
|
+
},
|
|
702
|
+
],
|
|
703
|
+
[
|
|
704
|
+
2,
|
|
705
|
+
2,
|
|
706
|
+
{
|
|
707
|
+
text: "",
|
|
708
|
+
color: "#000000",
|
|
709
|
+
},
|
|
710
|
+
],
|
|
711
|
+
[
|
|
712
|
+
3,
|
|
713
|
+
3,
|
|
714
|
+
{
|
|
715
|
+
text: "",
|
|
716
|
+
color: "#000000",
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
[
|
|
720
|
+
1,
|
|
721
|
+
3,
|
|
722
|
+
{
|
|
723
|
+
text: "",
|
|
724
|
+
color: "#000000",
|
|
725
|
+
},
|
|
726
|
+
],
|
|
727
|
+
]),
|
|
728
|
+
true,
|
|
729
|
+
"Fingering does not match expected G7 chord"
|
|
730
|
+
);
|
|
731
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
732
|
+
assert.equal(result?.position, 1, "Position does not match expected 1");
|
|
733
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test("parses G7 chord with 1 starting fret (Unicode format) - alternate version", () => {
|
|
737
|
+
const fingeringStr = ` G 7
|
|
738
|
+
‾‾‾‾‾‾‾‾‾‾‾
|
|
739
|
+
× ×
|
|
740
|
+
╒═╤═╤═╤═╤═╕
|
|
741
|
+
│ │ ● │ │ │
|
|
742
|
+
├─┼─┼─┼─┼─┤
|
|
743
|
+
│ │ │ │ ○ │
|
|
744
|
+
├─┼─┼─┼─┼─┤
|
|
745
|
+
│ │ │ ○ │ ○
|
|
746
|
+
└─┴─┴─┴─┴─┘`;
|
|
747
|
+
|
|
748
|
+
const result = stringToFingering(fingeringStr);
|
|
749
|
+
|
|
750
|
+
assert.equal(
|
|
751
|
+
fingersContains(result, [
|
|
752
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
753
|
+
[5, "x", {text: "", color: "#000000"}],
|
|
754
|
+
[
|
|
755
|
+
4,
|
|
756
|
+
1,
|
|
757
|
+
{
|
|
758
|
+
text: "",
|
|
759
|
+
color: "#e74c3c",
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
[
|
|
763
|
+
2,
|
|
764
|
+
2,
|
|
765
|
+
{
|
|
766
|
+
text: "",
|
|
767
|
+
color: "#000000",
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
[
|
|
771
|
+
3,
|
|
772
|
+
3,
|
|
773
|
+
{
|
|
774
|
+
text: "",
|
|
775
|
+
color: "#000000",
|
|
776
|
+
},
|
|
777
|
+
],
|
|
778
|
+
[
|
|
779
|
+
1,
|
|
780
|
+
3,
|
|
781
|
+
{
|
|
782
|
+
text: "",
|
|
783
|
+
color: "#000000",
|
|
784
|
+
},
|
|
785
|
+
],
|
|
786
|
+
]),
|
|
787
|
+
true,
|
|
788
|
+
"Fingering does not match expected G7 chord"
|
|
789
|
+
);
|
|
790
|
+
assert.equal(result?.title, "G 7", "Title does not match expected G 7");
|
|
791
|
+
assert.equal(result?.position, 1, "Position does not match expected 1");
|
|
792
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("parses E dom 7 with finger numbers (Unicode format)", () => {
|
|
796
|
+
const fingeringStr = ` E dom 7
|
|
797
|
+
‾‾‾‾‾‾‾‾‾‾‾
|
|
798
|
+
× ×
|
|
799
|
+
┌─┬─┬─┬─┬─┐
|
|
800
|
+
│ │ │ 3 │ │
|
|
801
|
+
├─┼─┼─┼─┼─┤
|
|
802
|
+
│ 5 1 │ │ │
|
|
803
|
+
├─┼─┼─┼─┼─┤
|
|
804
|
+
│ │ │ │ 7 │
|
|
805
|
+
└─┴─┴─┴─┴─┘`;
|
|
806
|
+
|
|
807
|
+
const result = stringToFingering(fingeringStr);
|
|
808
|
+
assert.equal(
|
|
809
|
+
fingersContains(result, [
|
|
810
|
+
[
|
|
811
|
+
5,
|
|
812
|
+
2,
|
|
813
|
+
{
|
|
814
|
+
text: "5",
|
|
815
|
+
color: "#000000",
|
|
816
|
+
},
|
|
817
|
+
],
|
|
818
|
+
[
|
|
819
|
+
4,
|
|
820
|
+
2,
|
|
821
|
+
{
|
|
822
|
+
text: "1",
|
|
823
|
+
color: "#000000",
|
|
824
|
+
},
|
|
825
|
+
],
|
|
826
|
+
[
|
|
827
|
+
3,
|
|
828
|
+
1,
|
|
829
|
+
{
|
|
830
|
+
text: "3",
|
|
831
|
+
color: "#000000",
|
|
832
|
+
},
|
|
833
|
+
],
|
|
834
|
+
[
|
|
835
|
+
2,
|
|
836
|
+
3,
|
|
837
|
+
{
|
|
838
|
+
text: "7",
|
|
839
|
+
color: "#000000",
|
|
840
|
+
},
|
|
841
|
+
],
|
|
842
|
+
[6, "x", {text: "", color: "#000000"}],
|
|
843
|
+
[1, "x", {text: "", color: "#000000"}],
|
|
844
|
+
]),
|
|
845
|
+
true,
|
|
846
|
+
"Fingering does not match expected E dom 7 chord with finger numbers"
|
|
847
|
+
);
|
|
848
|
+
assert.equal(
|
|
849
|
+
result?.title,
|
|
850
|
+
"E dom 7",
|
|
851
|
+
"Title does not match expected E dom 7"
|
|
852
|
+
);
|
|
853
|
+
assert.equal(
|
|
854
|
+
result?.position,
|
|
855
|
+
undefined,
|
|
856
|
+
"Position does not match expected undefined"
|
|
857
|
+
);
|
|
858
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
describe("Edge cases and variations", () => {
|
|
863
|
+
test("handles empty fingering string", () => {
|
|
864
|
+
const result = stringToFingering("");
|
|
865
|
+
|
|
866
|
+
assert.equal(result, null, "Result should be null for empty string");
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test("handles fingering with no title", () => {
|
|
870
|
+
const fingeringStr = ` oo o
|
|
871
|
+
------
|
|
872
|
+
||||o|
|
|
873
|
+
||o*||
|
|
874
|
+
||||||`;
|
|
875
|
+
const result = stringToFingering(fingeringStr);
|
|
876
|
+
|
|
877
|
+
assert.equal(
|
|
878
|
+
fingersContains(result, [
|
|
879
|
+
[
|
|
880
|
+
2,
|
|
881
|
+
1,
|
|
882
|
+
{
|
|
883
|
+
text: "",
|
|
884
|
+
color: "#000000",
|
|
885
|
+
},
|
|
886
|
+
],
|
|
887
|
+
[
|
|
888
|
+
3,
|
|
889
|
+
2,
|
|
890
|
+
{
|
|
891
|
+
text: "",
|
|
892
|
+
color: "#e74c3c",
|
|
893
|
+
},
|
|
894
|
+
],
|
|
895
|
+
[
|
|
896
|
+
4,
|
|
897
|
+
2,
|
|
898
|
+
{
|
|
899
|
+
text: "",
|
|
900
|
+
color: "#000000",
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
[5, 0, { text: "", color: "#000000" }],
|
|
904
|
+
[6, 0, { text: "", color: "#000000" }],
|
|
905
|
+
[1, 0, { text: "", color: "#000000" }],
|
|
906
|
+
]),
|
|
907
|
+
true,
|
|
908
|
+
"Fingering does not match expected A minor chord"
|
|
909
|
+
);
|
|
910
|
+
assert.equal(
|
|
911
|
+
result?.title,
|
|
912
|
+
"",
|
|
913
|
+
"Title should be empty"
|
|
914
|
+
);
|
|
915
|
+
assert.equal(
|
|
916
|
+
result?.position,
|
|
917
|
+
undefined,
|
|
918
|
+
"Position does not match expected undefined"
|
|
919
|
+
);
|
|
920
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test("handles all open strings", () => {
|
|
924
|
+
const fingeringStr = ` E major
|
|
925
|
+
######
|
|
926
|
+
oooooo
|
|
927
|
+
------
|
|
928
|
+
||||||
|
|
929
|
+
||||||
|
|
930
|
+
||||||`;
|
|
931
|
+
|
|
932
|
+
const result = stringToFingering(fingeringStr);
|
|
933
|
+
|
|
934
|
+
assert.equal(
|
|
935
|
+
fingersContains(result, [
|
|
936
|
+
[1, 0, { text: "", color: "#000000" }],
|
|
937
|
+
[2, 0, { text: "", color: "#000000" }],
|
|
938
|
+
[3, 0, { text: "", color: "#000000" }],
|
|
939
|
+
[4, 0, { text: "", color: "#000000" }],
|
|
940
|
+
[5, 0, { text: "", color: "#000000" }],
|
|
941
|
+
[6, 0, { text: "", color: "#000000" }],
|
|
942
|
+
]),
|
|
943
|
+
true,
|
|
944
|
+
"Fingering does not match expected E major"
|
|
945
|
+
);
|
|
946
|
+
assert.equal(
|
|
947
|
+
result?.title,
|
|
948
|
+
"E major",
|
|
949
|
+
"Title does not match expected E major"
|
|
950
|
+
);
|
|
951
|
+
assert.equal(
|
|
952
|
+
result?.position,
|
|
953
|
+
undefined,
|
|
954
|
+
"Position does not match expected undefined"
|
|
955
|
+
);
|
|
956
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
test("handles all muted strings", () => {
|
|
960
|
+
const fingeringStr = ` Muted
|
|
961
|
+
######
|
|
962
|
+
xxxxxx
|
|
963
|
+
------
|
|
964
|
+
||||||
|
|
965
|
+
||||||
|
|
966
|
+
||||||`;
|
|
967
|
+
|
|
968
|
+
const result = stringToFingering(fingeringStr);
|
|
969
|
+
|
|
970
|
+
assert.equal(
|
|
971
|
+
fingersContains(result, [
|
|
972
|
+
[1, 'x', { text: "", color: "#000000" }],
|
|
973
|
+
[2, 'x', { text: "", color: "#000000" }],
|
|
974
|
+
[3, 'x', { text: "", color: "#000000" }],
|
|
975
|
+
[4, 'x', { text: "", color: "#000000" }],
|
|
976
|
+
[5, 'x', { text: "", color: "#000000" }],
|
|
977
|
+
[6, 'x', { text: "", color: "#000000" }],
|
|
978
|
+
]), true,
|
|
979
|
+
"Fingering does not match expected Muted strings"
|
|
980
|
+
);
|
|
981
|
+
assert.equal(
|
|
982
|
+
result?.title,
|
|
983
|
+
"Muted",
|
|
984
|
+
"Title does not match expected Muted"
|
|
985
|
+
);
|
|
986
|
+
assert.equal(
|
|
987
|
+
result?.position,
|
|
988
|
+
undefined,
|
|
989
|
+
"Position does not match expected undefined"
|
|
990
|
+
);
|
|
991
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
test("handles no indications for open strings", () => {
|
|
995
|
+
const fingeringStr = ` Muted
|
|
996
|
+
######
|
|
997
|
+
||||||
|
|
998
|
+
||||||
|
|
999
|
+
||||||`;
|
|
1000
|
+
|
|
1001
|
+
const result = stringToFingering(fingeringStr);
|
|
1002
|
+
assert.equal(
|
|
1003
|
+
fingersContains(result, []),
|
|
1004
|
+
true,
|
|
1005
|
+
"Fingering does not match expected Muted strings"
|
|
1006
|
+
);
|
|
1007
|
+
assert.equal(
|
|
1008
|
+
result?.title,
|
|
1009
|
+
"Muted",
|
|
1010
|
+
"Title does not match expected Muted"
|
|
1011
|
+
);
|
|
1012
|
+
assert.equal(
|
|
1013
|
+
result?.position,
|
|
1014
|
+
undefined,
|
|
1015
|
+
"Position does not match expected undefined"
|
|
1016
|
+
);
|
|
1017
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
test("handles no title or open strings (bug 1)", () => {
|
|
1021
|
+
const fingeringStr = ` ------
|
|
1022
|
+
oooooo
|
|
1023
|
+
||||||
|
|
1024
|
+
||||||`;
|
|
1025
|
+
|
|
1026
|
+
const result = stringToFingering(fingeringStr);
|
|
1027
|
+
assert.equal(
|
|
1028
|
+
fingersContains(result, [
|
|
1029
|
+
[1, 1, { text: "", color: "#000000" }],
|
|
1030
|
+
[2, 1, { text: "", color: "#000000" }],
|
|
1031
|
+
[3, 1, { text: "", color: "#000000" }],
|
|
1032
|
+
[4, 1, { text: "", color: "#000000" }],
|
|
1033
|
+
[5, 1, { text: "", color: "#000000" }],
|
|
1034
|
+
[6, 1, { text: "", color: "#000000" }],
|
|
1035
|
+
]), true,
|
|
1036
|
+
"Fingering does not match expected strings"
|
|
1037
|
+
);
|
|
1038
|
+
assert.equal(
|
|
1039
|
+
result?.title,
|
|
1040
|
+
"",
|
|
1041
|
+
"Title should be undefined"
|
|
1042
|
+
);
|
|
1043
|
+
assert.equal(
|
|
1044
|
+
result?.position,
|
|
1045
|
+
undefined,
|
|
1046
|
+
"Position does not match expected undefined"
|
|
1047
|
+
);
|
|
1048
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
test("handles no title or open strings in unicode (bug 2)", () => {
|
|
1052
|
+
const fingeringStr = ` ┌─┬─┬─┬─┬─┐
|
|
1053
|
+
○ ○ ○ ○ ○ ○
|
|
1054
|
+
├─┼─┼─┼─┼─┤
|
|
1055
|
+
│ │ │ │ │ │
|
|
1056
|
+
├─┼─┼─┼─┼─┤
|
|
1057
|
+
│ │ │ │ │ │
|
|
1058
|
+
└─┴─┴─┴─┴─┘`;
|
|
1059
|
+
|
|
1060
|
+
const result = stringToFingering(fingeringStr);
|
|
1061
|
+
assert.equal(
|
|
1062
|
+
fingersContains(result, [
|
|
1063
|
+
[1, 1, { text: "", color: "#000000" }],
|
|
1064
|
+
[2, 1, { text: "", color: "#000000" }],
|
|
1065
|
+
[3, 1, { text: "", color: "#000000" }],
|
|
1066
|
+
[4, 1, { text: "", color: "#000000" }],
|
|
1067
|
+
[5, 1, { text: "", color: "#000000" }],
|
|
1068
|
+
[6, 1, { text: "", color: "#000000" }],
|
|
1069
|
+
]), true,
|
|
1070
|
+
"Fingering does not match expected strings"
|
|
1071
|
+
);
|
|
1072
|
+
assert.equal(
|
|
1073
|
+
result?.title,
|
|
1074
|
+
"",
|
|
1075
|
+
"Title should be undefined"
|
|
1076
|
+
);
|
|
1077
|
+
assert.equal(
|
|
1078
|
+
result?.position,
|
|
1079
|
+
undefined,
|
|
1080
|
+
"Position does not match expected undefined"
|
|
1081
|
+
);
|
|
1082
|
+
assert.deepEqual(result?.barres, [], "Barres should be empty");
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
});
|
|
1086
|
+
});
|