@goplayerjuggler/abc-tools 1.0.16 → 1.0.18
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/package.json +3 -3
- package/src/incipit.js +5 -5
- package/src/index.js +1 -2
- package/src/javascriptify.js +21 -7
- package/src/manipulator.js +206 -19
- package/src/math.js +1 -1
- package/src/parse/barline-parser.js +2 -2
- package/src/parse/getBarInfo.js +7 -7
- package/src/parse/header-parser.js +13 -6
- package/src/parse/misc-parser.js +6 -6
- package/src/parse/note-parser.js +12 -12
- package/src/parse/parser.js +19 -19
- package/src/parse/token-utils.js +4 -4
- package/src/sort/contour-sort.js +16 -16
- package/src/sort/contour-svg.js +3 -3
- package/src/sort/display-contour.js +1 -1
- package/src/sort/encode.js +7 -1
- package/src/sort/get-contour.js +35 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goplayerjuggler/abc-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "sorting algorithm and implementation for ABC tunes; plus other tools for parsing and manipulating ABC tunes",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -44,13 +44,13 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/eslintrc": "^3.3.1",
|
|
46
46
|
"@eslint/js": "^9.37.0",
|
|
47
|
-
"baseline-browser-mapping": "^2.9.19",
|
|
48
47
|
"clipboardy": "^5.0.1",
|
|
49
48
|
"eslint": "^9.37.0",
|
|
50
49
|
"eslint-plugin-jest": "^29.0.1",
|
|
51
50
|
"globals": "^16.4.0",
|
|
52
51
|
"jest": "^30.2.0",
|
|
53
|
-
"jsdoc": "^4.0.5"
|
|
52
|
+
"jsdoc": "^4.0.5",
|
|
53
|
+
"prettier": "^3.8.1"
|
|
54
54
|
},
|
|
55
55
|
"jest": {
|
|
56
56
|
"testEnvironment": "node",
|
package/src/incipit.js
CHANGED
|
@@ -278,7 +278,7 @@ function sanitise(theTune) {
|
|
|
278
278
|
function getIncipit(data) {
|
|
279
279
|
let {
|
|
280
280
|
abc,
|
|
281
|
-
numBars
|
|
281
|
+
numBars //, part=null
|
|
282
282
|
} = typeof data === "string" ? { abc: data } : data;
|
|
283
283
|
|
|
284
284
|
const { withAnacrucis = true } =
|
|
@@ -313,7 +313,7 @@ function getIncipitForContourGeneration(
|
|
|
313
313
|
return getIncipit({
|
|
314
314
|
abc,
|
|
315
315
|
withAnacrucis: false,
|
|
316
|
-
numBars
|
|
316
|
+
numBars
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
319
|
|
|
@@ -322,7 +322,7 @@ function getContourFromFullAbc(
|
|
|
322
322
|
{
|
|
323
323
|
withSvg = true,
|
|
324
324
|
withSwingTransform = false,
|
|
325
|
-
numBars = new Fraction(3, 2)
|
|
325
|
+
numBars = new Fraction(3, 2)
|
|
326
326
|
} = {}
|
|
327
327
|
) {
|
|
328
328
|
if (Array.isArray(abc)) {
|
|
@@ -331,12 +331,12 @@ function getContourFromFullAbc(
|
|
|
331
331
|
}
|
|
332
332
|
return getContour(getIncipitForContourGeneration(abc, { numBars }), {
|
|
333
333
|
withSvg,
|
|
334
|
-
withSwingTransform
|
|
334
|
+
withSwingTransform
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
module.exports = {
|
|
339
339
|
getIncipit,
|
|
340
340
|
getIncipitForContourGeneration,
|
|
341
|
-
getContourFromFullAbc
|
|
341
|
+
getContourFromFullAbc
|
|
342
342
|
};
|
package/src/index.js
CHANGED
package/src/javascriptify.js
CHANGED
|
@@ -12,8 +12,12 @@ function javascriptify(value, indent = 0) {
|
|
|
12
12
|
const nextIndentStr = " ".repeat(indent + 1);
|
|
13
13
|
|
|
14
14
|
// Handle null and undefined
|
|
15
|
-
if (value === null) {
|
|
16
|
-
|
|
15
|
+
if (value === null) {
|
|
16
|
+
return "null";
|
|
17
|
+
}
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
return "undefined";
|
|
20
|
+
}
|
|
17
21
|
|
|
18
22
|
// Handle primitives
|
|
19
23
|
if (typeof value === "number") {
|
|
@@ -39,7 +43,9 @@ function javascriptify(value, indent = 0) {
|
|
|
39
43
|
|
|
40
44
|
// Handle arrays
|
|
41
45
|
if (Array.isArray(value)) {
|
|
42
|
-
if (value.length === 0) {
|
|
46
|
+
if (value.length === 0) {
|
|
47
|
+
return "[]";
|
|
48
|
+
}
|
|
43
49
|
|
|
44
50
|
const items = value
|
|
45
51
|
.map((item) => nextIndentStr + javascriptify(item, indent + 1))
|
|
@@ -54,15 +60,23 @@ function javascriptify(value, indent = 0) {
|
|
|
54
60
|
const keys = Object.keys(value).filter((k) => {
|
|
55
61
|
const val = value[k];
|
|
56
62
|
// Omit all falsey values (including false, but keep 0)
|
|
57
|
-
if (val === 0) {
|
|
63
|
+
if (val === 0) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
58
66
|
//if (val === null || val === undefined || val === "") return false;
|
|
59
|
-
if (!val) {
|
|
67
|
+
if (!val) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
60
70
|
// Omit empty arrays
|
|
61
|
-
if (Array.isArray(val) && val.length === 0) {
|
|
71
|
+
if (Array.isArray(val) && val.length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
62
74
|
return true;
|
|
63
75
|
});
|
|
64
76
|
|
|
65
|
-
if (keys.length === 0) {
|
|
77
|
+
if (keys.length === 0) {
|
|
78
|
+
return "{}";
|
|
79
|
+
}
|
|
66
80
|
|
|
67
81
|
const properties = keys
|
|
68
82
|
.map((key) => {
|
package/src/manipulator.js
CHANGED
|
@@ -2,6 +2,7 @@ const { Fraction } = require("./math.js");
|
|
|
2
2
|
const { parseAbc, getMeter, getUnitLength } = require("./parse/parser.js");
|
|
3
3
|
|
|
4
4
|
const { getBarInfo } = require("./parse/getBarInfo.js");
|
|
5
|
+
const { getHeaderValue } = require("./parse/header-parser.js");
|
|
5
6
|
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// ABC manipulation functions
|
|
@@ -56,7 +57,7 @@ function normaliseKey(keyHeader) {
|
|
|
56
57
|
lyd: "lydian",
|
|
57
58
|
lydian: "lydian",
|
|
58
59
|
loc: "locrian",
|
|
59
|
-
locrian: "locrian"
|
|
60
|
+
locrian: "locrian"
|
|
60
61
|
};
|
|
61
62
|
const mode = Object.keys(modeMap).find((m) => key.includes(m)) || "major";
|
|
62
63
|
|
|
@@ -104,7 +105,7 @@ function hasAnacrucisFromParsed(parsed, barLines) {
|
|
|
104
105
|
getBarInfo(bars, barLines, meter, {
|
|
105
106
|
barNumbers: true,
|
|
106
107
|
isPartial: true,
|
|
107
|
-
cumulativeDuration: false
|
|
108
|
+
cumulativeDuration: false
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -178,6 +179,7 @@ function toggleMeterDoubling(abc, smallMeter, largeMeter, currentMeter) {
|
|
|
178
179
|
|
|
179
180
|
const parsed = parseAbc(abc);
|
|
180
181
|
const { headerLines, barLines, musicText, bars, meter } = parsed;
|
|
182
|
+
|
|
181
183
|
// throw if there's a change of meter or unit length in the tune
|
|
182
184
|
if (barLines.find((bl) => bl.newMeter || bl.newUnitLength)) {
|
|
183
185
|
throw new Error("change of meter or unit length not handled");
|
|
@@ -198,7 +200,7 @@ function toggleMeterDoubling(abc, smallMeter, largeMeter, currentMeter) {
|
|
|
198
200
|
// Get bar info to understand musical structure
|
|
199
201
|
getBarInfo(bars, barLines, meter, {
|
|
200
202
|
barNumbers: true,
|
|
201
|
-
isPartial: true
|
|
203
|
+
isPartial: true
|
|
202
204
|
});
|
|
203
205
|
|
|
204
206
|
// Build a map of which bars start with variant endings
|
|
@@ -268,13 +270,6 @@ function toggleMeterDoubling(abc, smallMeter, largeMeter, currentMeter) {
|
|
|
268
270
|
continue;
|
|
269
271
|
}
|
|
270
272
|
|
|
271
|
-
// If the current bar starts with a variant, keep its bar line
|
|
272
|
-
const currentBarVariant = barStartsWithVariant.get(i);
|
|
273
|
-
if (currentBarVariant) {
|
|
274
|
-
barLineDecisions.set(i, { action: "keep" });
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
273
|
// This is a complete bar - use its barNumber to decide
|
|
279
274
|
// Without anacrucis: Remove complete bars with even barNumber (0, 2, 4, ...), keep odd ones (1, 3, 5, ...)
|
|
280
275
|
// With anacrucis: the other way round!
|
|
@@ -286,12 +281,21 @@ function toggleMeterDoubling(abc, smallMeter, largeMeter, currentMeter) {
|
|
|
286
281
|
? barLine.barNumber % 2 !== 0
|
|
287
282
|
: barLine.barNumber % 2 === 0;
|
|
288
283
|
if (remove) {
|
|
284
|
+
// Check if current bar starts with variant
|
|
285
|
+
if (barStartsWithVariant.has(i)) {
|
|
286
|
+
const variantToken = barStartsWithVariant.get(i);
|
|
287
|
+
barLinesToConvert.set(variantToken.sourceIndex, {
|
|
288
|
+
oldLength: variantToken.sourceLength,
|
|
289
|
+
oldText: variantToken.token
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Also check if next bar starts with variant
|
|
289
293
|
const nextBarIdx = i + 1;
|
|
290
294
|
if (nextBarIdx < bars.length && barStartsWithVariant.has(nextBarIdx)) {
|
|
291
295
|
const variantToken = barStartsWithVariant.get(nextBarIdx);
|
|
292
296
|
barLinesToConvert.set(variantToken.sourceIndex, {
|
|
293
297
|
oldLength: variantToken.sourceLength,
|
|
294
|
-
oldText: variantToken.token
|
|
298
|
+
oldText: variantToken.token
|
|
295
299
|
});
|
|
296
300
|
}
|
|
297
301
|
barLineDecisions.set(i, { action: "remove" });
|
|
@@ -301,6 +305,26 @@ function toggleMeterDoubling(abc, smallMeter, largeMeter, currentMeter) {
|
|
|
301
305
|
}
|
|
302
306
|
}
|
|
303
307
|
|
|
308
|
+
// --- Debugging - may be helpful ----
|
|
309
|
+
// console.log(
|
|
310
|
+
// "Bar line decisions:",
|
|
311
|
+
// Array.from(barLineDecisions.entries()).map(([i, d]) => ({
|
|
312
|
+
// index: i,
|
|
313
|
+
// barLine: barLines[i]?.text,
|
|
314
|
+
// sourceIndex: barLines[i]?.sourceIndex,
|
|
315
|
+
// action: d.action
|
|
316
|
+
// }))
|
|
317
|
+
// );
|
|
318
|
+
|
|
319
|
+
// console.log(
|
|
320
|
+
// "Bar lines:",
|
|
321
|
+
// barLines.map((bl) => ({ text: bl.text, sourceIndex: bl.sourceIndex }))
|
|
322
|
+
// );
|
|
323
|
+
// console.log(
|
|
324
|
+
// "Bars starting with variants:",
|
|
325
|
+
// Array.from(barStartsWithVariant.entries())
|
|
326
|
+
// );
|
|
327
|
+
|
|
304
328
|
// Reconstruct music
|
|
305
329
|
let newMusic = "";
|
|
306
330
|
let pos = 0;
|
|
@@ -355,7 +379,7 @@ function toggleMeterDoubling(abc, smallMeter, largeMeter, currentMeter) {
|
|
|
355
379
|
} else {
|
|
356
380
|
// Going from large to small: add bar lines at midpoints
|
|
357
381
|
const barInfo = getBarInfo(bars, barLines, meter, {
|
|
358
|
-
divideBarsBy: 2
|
|
382
|
+
divideBarsBy: 2
|
|
359
383
|
});
|
|
360
384
|
|
|
361
385
|
const { midpoints } = barInfo;
|
|
@@ -382,11 +406,13 @@ function toggleMeter_4_4_to_4_2(abc, currentMeter) {
|
|
|
382
406
|
}
|
|
383
407
|
|
|
384
408
|
const defaultCommentForReelConversion =
|
|
385
|
-
"*abc-tools: convert
|
|
409
|
+
"*abc-tools: convert to M:4/4 & L:1/16*";
|
|
410
|
+
const defaultCommentForHornpipeConversion = "*abc-tools: convert to M:4/2*";
|
|
411
|
+
const defaultCommentForJigConversion = "*abc-tools: convert to M:12/8*";
|
|
386
412
|
/**
|
|
387
|
-
* Adjusts bar lengths and L
|
|
388
|
-
* reel written in the normal way (M:4/4 L:1/8)
|
|
389
|
-
* written with M:4/4 L:1/16
|
|
413
|
+
* Adjusts bar lengths and L, M fields - a
|
|
414
|
+
* reel written in the normal way (M:4/4 L:1/8) is written
|
|
415
|
+
* written with M:4/4 and L:1/16 (or M:4/2 and L:1/8, if withSemiquavers is unflagged)
|
|
390
416
|
* Bars are twice as long, and the quick notes are semiquavers
|
|
391
417
|
* rather than quavers.
|
|
392
418
|
* @param {string} reel
|
|
@@ -420,6 +446,56 @@ function convertStandardReel(
|
|
|
420
446
|
return result;
|
|
421
447
|
}
|
|
422
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Adjusts bar lengths and M field to alter a
|
|
451
|
+
* jig written in the normal way (M:6/8) so it’s
|
|
452
|
+
* written with M:12/8.
|
|
453
|
+
* Bars are twice as long.
|
|
454
|
+
* @param {string} jig
|
|
455
|
+
* @param {string} comment - when non falsey, the comment will be injected as an N: header
|
|
456
|
+
|
|
457
|
+
* @returns
|
|
458
|
+
*/
|
|
459
|
+
function convertStandardJig(jig, comment = defaultCommentForJigConversion) {
|
|
460
|
+
const meter = getMeter(jig);
|
|
461
|
+
if (!Array.isArray(meter) || !meter || !meter[0] === 6 || !meter[1] === 8) {
|
|
462
|
+
throw new Error("invalid meter");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let result = //toggleMeter_4_4_to_4_2(reel, meter);
|
|
466
|
+
toggleMeterDoubling(jig, [6, 8], [12, 8], meter);
|
|
467
|
+
if (comment) {
|
|
468
|
+
result = result.replace(/(\nK:)/, `\nN:${comment}$1`);
|
|
469
|
+
}
|
|
470
|
+
return result;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Adjusts bar lengths and M field to alter a
|
|
475
|
+
* hornpipe written in the normal way (M:6/8) so it’s
|
|
476
|
+
* written with M:12/8.
|
|
477
|
+
* Bars are twice as long.
|
|
478
|
+
* @param {string} hornpipe
|
|
479
|
+
* @param {string} comment - when non falsey, the comment will be injected as an N: header
|
|
480
|
+
|
|
481
|
+
* @returns
|
|
482
|
+
*/
|
|
483
|
+
function convertStandardHornpipe(
|
|
484
|
+
hornpipe,
|
|
485
|
+
comment = defaultCommentForHornpipeConversion
|
|
486
|
+
) {
|
|
487
|
+
const meter = getMeter(hornpipe);
|
|
488
|
+
if (!Array.isArray(meter) || !meter || !meter[0] === 4 || !meter[1] === 4) {
|
|
489
|
+
throw new Error("invalid meter");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let result = toggleMeter_4_4_to_4_2(hornpipe, meter);
|
|
493
|
+
|
|
494
|
+
if (comment) {
|
|
495
|
+
result = result.replace(/(\nK:)/, `\nN:${comment}$1`);
|
|
496
|
+
}
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
423
499
|
function doubleBarLength(abc, comment = null) {
|
|
424
500
|
const meter = getMeter(abc);
|
|
425
501
|
if (!Array.isArray(meter) || !meter) {
|
|
@@ -457,7 +533,9 @@ function convertToStandardReel(
|
|
|
457
533
|
withSemiquavers = true
|
|
458
534
|
) {
|
|
459
535
|
if (withSemiquavers) {
|
|
460
|
-
reel = reel
|
|
536
|
+
reel = reel
|
|
537
|
+
.replace(/\nM:\s*4\/4/, "\nM:4/2")
|
|
538
|
+
.replace(/\nL:\s*1\/16/, "\nL:1/8");
|
|
461
539
|
}
|
|
462
540
|
|
|
463
541
|
const unitLength = getUnitLength(reel);
|
|
@@ -476,6 +554,51 @@ function convertToStandardReel(
|
|
|
476
554
|
}
|
|
477
555
|
return result;
|
|
478
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Adjusts bar lengths to rewrite a
|
|
559
|
+
* jig written in the abnormal way (M:12/8) to M:6/8, the normal or standard way.
|
|
560
|
+
* Bars are half as long. Inverse operation to convertStandardJig
|
|
561
|
+
* @param {string} jig
|
|
562
|
+
* @param {string} comment - when non falsey, the comment (as an N:) will removed from the header
|
|
563
|
+
* @param {bool} withSemiquavers - when unflagged, the original jig was written in M:4/2 L:1/8
|
|
564
|
+
* @returns
|
|
565
|
+
*/
|
|
566
|
+
function convertToStandardJig(jig, comment = defaultCommentForJigConversion) {
|
|
567
|
+
const unitLength = getUnitLength(jig);
|
|
568
|
+
if (unitLength.den !== 8) {
|
|
569
|
+
throw new Error("invalid L header");
|
|
570
|
+
}
|
|
571
|
+
const meter = getMeter(jig);
|
|
572
|
+
if (!Array.isArray(meter) || !meter || !meter[0] === 12 || !meter[1] === 8) {
|
|
573
|
+
throw new Error("invalid meter");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let result = toggleMeter_6_8_to_12_8(jig); // toggleMeter_4_4_to_4_2(jig, meter);
|
|
577
|
+
if (comment) {
|
|
578
|
+
result = result.replace(`\nN:${comment}`, "");
|
|
579
|
+
}
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function convertToStandardHornpipe(
|
|
584
|
+
hornpipe,
|
|
585
|
+
comment = defaultCommentForHornpipeConversion
|
|
586
|
+
) {
|
|
587
|
+
const unitLength = getUnitLength(hornpipe);
|
|
588
|
+
if (unitLength.den !== 8) {
|
|
589
|
+
throw new Error("invalid L header");
|
|
590
|
+
}
|
|
591
|
+
const meter = getMeter(hornpipe);
|
|
592
|
+
if (!Array.isArray(meter) || !meter || !meter[0] === 4 || !meter[1] === 2) {
|
|
593
|
+
throw new Error("invalid meter");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
let result = toggleMeter_4_4_to_4_2(hornpipe); // toggleMeter_4_4_to_4_2(jig, meter);
|
|
597
|
+
if (comment) {
|
|
598
|
+
result = result.replace(`\nN:${comment}`, "");
|
|
599
|
+
}
|
|
600
|
+
return result;
|
|
601
|
+
}
|
|
479
602
|
|
|
480
603
|
/**
|
|
481
604
|
* Toggle between M:6/8 and M:12/8 by surgically adding/removing bar lines
|
|
@@ -528,7 +651,7 @@ function getFirstBars(
|
|
|
528
651
|
barNumbers: true,
|
|
529
652
|
isPartial: true,
|
|
530
653
|
cumulativeDuration: true,
|
|
531
|
-
stopAfterBarNumber
|
|
654
|
+
stopAfterBarNumber
|
|
532
655
|
});
|
|
533
656
|
|
|
534
657
|
const enrichedBarLines = barInfo.barLines;
|
|
@@ -692,8 +815,72 @@ function getFirstBars(
|
|
|
692
815
|
)}`;
|
|
693
816
|
}
|
|
694
817
|
|
|
818
|
+
function canDoubleBarLength(abc) {
|
|
819
|
+
const meter = getMeter(abc),
|
|
820
|
+
l = getUnitLength(abc),
|
|
821
|
+
rhythm = getHeaderValue(abc, "R");
|
|
822
|
+
if (
|
|
823
|
+
!rhythm ||
|
|
824
|
+
["reel", "hornpipe", "jig"].indexOf(rhythm.toLowerCase()) < 0
|
|
825
|
+
) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
return (
|
|
829
|
+
!abc.match(/\[M:/) && //inline meter marking
|
|
830
|
+
!abc.match(/\[L:/) &&
|
|
831
|
+
(((rhythm === "reel" || rhythm === "hornpipe") &&
|
|
832
|
+
l.equals(new Fraction(1, 8)) &&
|
|
833
|
+
meter[0] === 4 &&
|
|
834
|
+
meter[1] === 4) ||
|
|
835
|
+
(rhythm === "jig" &&
|
|
836
|
+
l.equals(new Fraction(1, 8)) &&
|
|
837
|
+
meter[0] === 6 &&
|
|
838
|
+
meter[1] === 8))
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
function canHalveBarLength(abc) {
|
|
842
|
+
const meter = getMeter(abc),
|
|
843
|
+
l = getUnitLength(abc),
|
|
844
|
+
rhythm = getHeaderValue(abc, "R");
|
|
845
|
+
if (
|
|
846
|
+
!rhythm ||
|
|
847
|
+
["reel", "hornpipe", "jig"].indexOf(rhythm.toLowerCase()) < 0
|
|
848
|
+
) {
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (
|
|
853
|
+
!rhythm ||
|
|
854
|
+
["reel", "hornpipe", "jig"].indexOf(rhythm.toLowerCase()) < 0
|
|
855
|
+
) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
return (
|
|
859
|
+
!abc.match(/\[M:/) && //inline meter marking
|
|
860
|
+
!abc.match(/\[L:/) &&
|
|
861
|
+
((rhythm === "reel" &&
|
|
862
|
+
l.equals(new Fraction(1, 16)) &&
|
|
863
|
+
meter[0] === 4 &&
|
|
864
|
+
meter[1] === 4) ||
|
|
865
|
+
((rhythm === "reel" || rhythm === "hornpipe") &&
|
|
866
|
+
l.equals(new Fraction(1, 8)) &&
|
|
867
|
+
meter[0] === 4 &&
|
|
868
|
+
meter[1] === 2) ||
|
|
869
|
+
(rhythm === "jig" &&
|
|
870
|
+
l.equals(new Fraction(1, 8)) &&
|
|
871
|
+
meter[0] === 12 &&
|
|
872
|
+
meter[1] === 8))
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
|
|
695
876
|
module.exports = {
|
|
877
|
+
canDoubleBarLength,
|
|
878
|
+
canHalveBarLength,
|
|
879
|
+
convertStandardJig,
|
|
880
|
+
convertStandardHornpipe,
|
|
696
881
|
convertStandardReel,
|
|
882
|
+
convertToStandardJig,
|
|
883
|
+
convertToStandardHornpipe,
|
|
697
884
|
convertToStandardReel,
|
|
698
885
|
defaultCommentForReelConversion,
|
|
699
886
|
doubleBarLength,
|
|
@@ -703,5 +890,5 @@ module.exports = {
|
|
|
703
890
|
normaliseKey,
|
|
704
891
|
toggleMeter_4_4_to_4_2,
|
|
705
892
|
toggleMeter_6_8_to_12_8,
|
|
706
|
-
toggleMeterDoubling
|
|
893
|
+
toggleMeterDoubling
|
|
707
894
|
};
|
package/src/math.js
CHANGED
|
@@ -40,7 +40,7 @@ function parseBarLine(barLineStr) {
|
|
|
40
40
|
const trimmed = barLineStr.trim();
|
|
41
41
|
const result = {
|
|
42
42
|
text: barLineStr,
|
|
43
|
-
trimmed
|
|
43
|
+
trimmed
|
|
44
44
|
};
|
|
45
45
|
// Start repeat
|
|
46
46
|
if (trimmed.match(/:$/)) {
|
|
@@ -60,5 +60,5 @@ function parseBarLine(barLineStr) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
module.exports = {
|
|
63
|
-
parseBarLine
|
|
63
|
+
parseBarLine
|
|
64
64
|
};
|
package/src/parse/getBarInfo.js
CHANGED
|
@@ -21,7 +21,7 @@ function processSkippedBarLines(barLines, skippedBarLineIndexes) {
|
|
|
21
21
|
// Copy any properties from the preceding barLine not already in the skipped barLine
|
|
22
22
|
barLines[skippedIndex] = {
|
|
23
23
|
...barLines[skippedIndex - 1],
|
|
24
|
-
...barLines[skippedIndex]
|
|
24
|
+
...barLines[skippedIndex]
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -101,7 +101,7 @@ function getBarInfo(bars, barLines, meter, options = {}) {
|
|
|
101
101
|
isPartial = true,
|
|
102
102
|
cumulativeDuration = true,
|
|
103
103
|
divideBarsBy = null,
|
|
104
|
-
stopAfterBarNumber = null
|
|
104
|
+
stopAfterBarNumber = null
|
|
105
105
|
} = options;
|
|
106
106
|
|
|
107
107
|
if (divideBarsBy !== null && divideBarsBy !== 2) {
|
|
@@ -205,7 +205,7 @@ function getBarInfo(bars, barLines, meter, options = {}) {
|
|
|
205
205
|
durationSinceLastComplete: durationBeforeVariant.clone(),
|
|
206
206
|
lastCompleteBarLineIdx: lastCompleteBarLineIdx,
|
|
207
207
|
meter: [...currentMeter],
|
|
208
|
-
fullBarDuration: fullBarDuration.clone()
|
|
208
|
+
fullBarDuration: fullBarDuration.clone()
|
|
209
209
|
};
|
|
210
210
|
variantCounter = 0;
|
|
211
211
|
currentVariantId = variantCounter;
|
|
@@ -287,7 +287,7 @@ function getBarInfo(bars, barLines, meter, options = {}) {
|
|
|
287
287
|
processSkippedBarLines(barLines, skippedBarLineIndexes);
|
|
288
288
|
return {
|
|
289
289
|
barLines: barLines.slice(0, barLineIdx + 1),
|
|
290
|
-
midpoints
|
|
290
|
+
midpoints
|
|
291
291
|
};
|
|
292
292
|
}
|
|
293
293
|
}
|
|
@@ -313,7 +313,7 @@ function getBarInfo(bars, barLines, meter, options = {}) {
|
|
|
313
313
|
if (cumulativeDuration) {
|
|
314
314
|
barLine.cumulativeDuration = {
|
|
315
315
|
sinceLastBarLine: barDuration.clone(),
|
|
316
|
-
sinceLastComplete: durationSinceLastComplete.clone()
|
|
316
|
+
sinceLastComplete: durationSinceLastComplete.clone()
|
|
317
317
|
};
|
|
318
318
|
}
|
|
319
319
|
|
|
@@ -425,10 +425,10 @@ function getBarInfo(bars, barLines, meter, options = {}) {
|
|
|
425
425
|
processSkippedBarLines(barLines, skippedBarLineIndexes);
|
|
426
426
|
return {
|
|
427
427
|
barLines,
|
|
428
|
-
midpoints
|
|
428
|
+
midpoints
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
module.exports = {
|
|
433
|
-
getBarInfo
|
|
433
|
+
getBarInfo
|
|
434
434
|
};
|
|
@@ -77,7 +77,13 @@ function getUnitLength(abc) {
|
|
|
77
77
|
* @returns {[string]} - array of titles
|
|
78
78
|
*/
|
|
79
79
|
function getTitles(abc) {
|
|
80
|
-
return [...abc.matchAll(/^(?:T:\s
|
|
80
|
+
return [...abc.matchAll(/^(?:T:\s*(.+)\n)/gm)];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getHeaderValue(abc, header) {
|
|
84
|
+
const r = new RegExp(String.raw`(?:${header}:\s*(.+)\n)`, "m"),
|
|
85
|
+
m = abc.match(r);
|
|
86
|
+
return m ? m[1]?.trim() : null;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
/**
|
|
@@ -138,7 +144,7 @@ function getMusicLines(abc) {
|
|
|
138
144
|
originalLine: line,
|
|
139
145
|
content: trimmed,
|
|
140
146
|
comment,
|
|
141
|
-
hasContinuation
|
|
147
|
+
hasContinuation
|
|
142
148
|
});
|
|
143
149
|
|
|
144
150
|
// Track position where newline would be (unless continuation)
|
|
@@ -155,15 +161,16 @@ function getMusicLines(abc) {
|
|
|
155
161
|
lineMetadata,
|
|
156
162
|
newlinePositions,
|
|
157
163
|
headerLines,
|
|
158
|
-
headerEndIndex
|
|
164
|
+
headerEndIndex
|
|
159
165
|
};
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
module.exports = {
|
|
163
|
-
|
|
169
|
+
getHeaderValue,
|
|
170
|
+
getKey,
|
|
164
171
|
getMeter,
|
|
165
|
-
getUnitLength,
|
|
166
172
|
getMusicLines,
|
|
167
173
|
getTitles,
|
|
168
|
-
|
|
174
|
+
getTonalBase,
|
|
175
|
+
getUnitLength
|
|
169
176
|
};
|
package/src/parse/misc-parser.js
CHANGED
|
@@ -21,7 +21,7 @@ function analyzeSpacing(segment, tokenEndPos) {
|
|
|
21
21
|
whitespace: "",
|
|
22
22
|
backquotes: 0,
|
|
23
23
|
beamBreak: false,
|
|
24
|
-
lineBreak: false
|
|
24
|
+
lineBreak: false
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -35,7 +35,7 @@ function analyzeSpacing(segment, tokenEndPos) {
|
|
|
35
35
|
whitespace: "",
|
|
36
36
|
backquotes: 0,
|
|
37
37
|
beamBreak: false,
|
|
38
|
-
lineBreak: false
|
|
38
|
+
lineBreak: false
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -51,7 +51,7 @@ function analyzeSpacing(segment, tokenEndPos) {
|
|
|
51
51
|
whitespace,
|
|
52
52
|
backquotes,
|
|
53
53
|
beamBreak: whitespace.length > 1 || whitespace.includes("\n"), // Multiple spaces or newline breaks beam
|
|
54
|
-
lineBreak: whitespace.includes("\n")
|
|
54
|
+
lineBreak: whitespace.includes("\n")
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -68,7 +68,7 @@ function parseTuplet(token, isCompoundTimeSignature) {
|
|
|
68
68
|
const pqr = {
|
|
69
69
|
p: parseInt(tupleMatch[1]),
|
|
70
70
|
q: tupleMatch[2],
|
|
71
|
-
r: tupleMatch[3]
|
|
71
|
+
r: tupleMatch[3]
|
|
72
72
|
};
|
|
73
73
|
const { p } = pqr;
|
|
74
74
|
let { q, r } = pqr;
|
|
@@ -107,12 +107,12 @@ function parseTuplet(token, isCompoundTimeSignature) {
|
|
|
107
107
|
isTuple: true,
|
|
108
108
|
p,
|
|
109
109
|
q,
|
|
110
|
-
r
|
|
110
|
+
r
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
115
|
module.exports = {
|
|
116
116
|
analyzeSpacing,
|
|
117
|
-
parseTuplet
|
|
117
|
+
parseTuplet
|
|
118
118
|
};
|
package/src/parse/note-parser.js
CHANGED
|
@@ -38,7 +38,7 @@ function parseDecorations(noteStr) {
|
|
|
38
38
|
T: "trill",
|
|
39
39
|
H: "fermata",
|
|
40
40
|
u: "upbow",
|
|
41
|
-
v: "downbow"
|
|
41
|
+
v: "downbow"
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
for (const [symbol, name] of Object.entries(symbolDecorations)) {
|
|
@@ -82,7 +82,7 @@ function parseAnnotation(noteStr) {
|
|
|
82
82
|
if (annotationMatch) {
|
|
83
83
|
return {
|
|
84
84
|
position: annotationMatch[1],
|
|
85
|
-
text: annotationMatch[2]
|
|
85
|
+
text: annotationMatch[2]
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
return null;
|
|
@@ -190,7 +190,7 @@ function parseChord(chordStr, unitLength) {
|
|
|
190
190
|
}
|
|
191
191
|
return {
|
|
192
192
|
isChord: true,
|
|
193
|
-
notes
|
|
193
|
+
notes
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
|
|
@@ -256,7 +256,7 @@ function parseGraceNotes(graceStr) {
|
|
|
256
256
|
isGraceNote: true,
|
|
257
257
|
duration: new Fraction(0, 1),
|
|
258
258
|
isChord: true,
|
|
259
|
-
chordNotes: chord.notes
|
|
259
|
+
chordNotes: chord.notes
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
262
|
} else {
|
|
@@ -266,7 +266,7 @@ function parseGraceNotes(graceStr) {
|
|
|
266
266
|
graceNotes.push({
|
|
267
267
|
...pitchData,
|
|
268
268
|
isGraceNote: true,
|
|
269
|
-
duration: new Fraction(0, 1)
|
|
269
|
+
duration: new Fraction(0, 1)
|
|
270
270
|
});
|
|
271
271
|
}
|
|
272
272
|
}
|
|
@@ -298,7 +298,7 @@ function parseBrokenRhythm(token) {
|
|
|
298
298
|
return {
|
|
299
299
|
isBrokenRhythm: true,
|
|
300
300
|
direction: symbol[0],
|
|
301
|
-
dots: symbol.length
|
|
301
|
+
dots: symbol.length
|
|
302
302
|
};
|
|
303
303
|
}
|
|
304
304
|
return null;
|
|
@@ -375,7 +375,7 @@ function parseNote(noteStr, unitLength, currentTuple) {
|
|
|
375
375
|
if (cleanStr.match(/^y$/)) {
|
|
376
376
|
return {
|
|
377
377
|
isDummy: true,
|
|
378
|
-
duration: new Fraction(0, 1, decorations, annotation)
|
|
378
|
+
duration: new Fraction(0, 1, decorations, annotation)
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
381
|
|
|
@@ -385,7 +385,7 @@ function parseNote(noteStr, unitLength, currentTuple) {
|
|
|
385
385
|
const duration = getDuration({
|
|
386
386
|
unitLength,
|
|
387
387
|
noteString: cleanStr,
|
|
388
|
-
currentTuple
|
|
388
|
+
currentTuple
|
|
389
389
|
});
|
|
390
390
|
const result = { isSilence: true, duration, text: silenceMatch[0] };
|
|
391
391
|
if (decorations) {
|
|
@@ -427,7 +427,7 @@ function parseNote(noteStr, unitLength, currentTuple) {
|
|
|
427
427
|
const duration = getDuration({
|
|
428
428
|
unitLength,
|
|
429
429
|
noteString: cleanStr,
|
|
430
|
-
currentTuple
|
|
430
|
+
currentTuple
|
|
431
431
|
});
|
|
432
432
|
topNote.duration = duration;
|
|
433
433
|
// Apply duration to all notes in chord
|
|
@@ -442,7 +442,7 @@ function parseNote(noteStr, unitLength, currentTuple) {
|
|
|
442
442
|
chordSymbol: chordSymbol || chord.chordSymbol,
|
|
443
443
|
decorations: decorations || chord.decorations,
|
|
444
444
|
isChord: true,
|
|
445
|
-
tied
|
|
445
|
+
tied
|
|
446
446
|
};
|
|
447
447
|
}
|
|
448
448
|
}
|
|
@@ -453,7 +453,7 @@ function parseNote(noteStr, unitLength, currentTuple) {
|
|
|
453
453
|
const duration = getDuration({
|
|
454
454
|
unitLength,
|
|
455
455
|
noteString: cleanStr,
|
|
456
|
-
currentTuple
|
|
456
|
+
currentTuple
|
|
457
457
|
});
|
|
458
458
|
|
|
459
459
|
const result = { pitch, octave, duration, tied };
|
|
@@ -480,5 +480,5 @@ module.exports = {
|
|
|
480
480
|
parseNote,
|
|
481
481
|
parseBrokenRhythm,
|
|
482
482
|
applyBrokenRhythm,
|
|
483
|
-
parseGraceNotes
|
|
483
|
+
parseGraceNotes
|
|
484
484
|
};
|
package/src/parse/parser.js
CHANGED
|
@@ -4,19 +4,19 @@ const {
|
|
|
4
4
|
getMeter,
|
|
5
5
|
getKey,
|
|
6
6
|
getUnitLength,
|
|
7
|
-
getMusicLines
|
|
7
|
+
getMusicLines
|
|
8
8
|
} = require("./header-parser.js");
|
|
9
9
|
const {
|
|
10
10
|
parseNote,
|
|
11
11
|
parseBrokenRhythm,
|
|
12
12
|
applyBrokenRhythm,
|
|
13
|
-
parseGraceNotes
|
|
13
|
+
parseGraceNotes
|
|
14
14
|
} = require("./note-parser.js");
|
|
15
15
|
const { parseBarLine } = require("./barline-parser.js");
|
|
16
16
|
const {
|
|
17
17
|
getTokenRegex,
|
|
18
18
|
parseInlineField,
|
|
19
|
-
repeat_1Or2
|
|
19
|
+
repeat_1Or2
|
|
20
20
|
} = require("./token-utils.js");
|
|
21
21
|
const { analyzeSpacing, parseTuplet } = require("./misc-parser.js");
|
|
22
22
|
|
|
@@ -338,7 +338,7 @@ function parseAbc(abc, options = {}) {
|
|
|
338
338
|
lineMetadata,
|
|
339
339
|
headerLines,
|
|
340
340
|
headerEndIndex,
|
|
341
|
-
newlinePositions
|
|
341
|
+
newlinePositions
|
|
342
342
|
} = getMusicLines(abc);
|
|
343
343
|
|
|
344
344
|
// Create a Set of newline positions for O(1) lookup
|
|
@@ -413,8 +413,8 @@ function parseAbc(abc, options = {}) {
|
|
|
413
413
|
whitespace: "",
|
|
414
414
|
backquotes: 0,
|
|
415
415
|
beamBreak: false,
|
|
416
|
-
lineBreak: false
|
|
417
|
-
|
|
416
|
+
lineBreak: false
|
|
417
|
+
}
|
|
418
418
|
});
|
|
419
419
|
});
|
|
420
420
|
// Grace notes don’t update previousRealNote
|
|
@@ -432,7 +432,7 @@ function parseAbc(abc, options = {}) {
|
|
|
432
432
|
token: fullToken,
|
|
433
433
|
sourceIndex: tokenStartPos,
|
|
434
434
|
sourceLength: fullToken.length,
|
|
435
|
-
spacing
|
|
435
|
+
spacing
|
|
436
436
|
};
|
|
437
437
|
currentBar.push(inlineFieldObj);
|
|
438
438
|
|
|
@@ -482,7 +482,7 @@ function parseAbc(abc, options = {}) {
|
|
|
482
482
|
|
|
483
483
|
// Add the broken rhythm marker to the bar
|
|
484
484
|
currentBar.push({
|
|
485
|
-
...brokenRhythm
|
|
485
|
+
...brokenRhythm
|
|
486
486
|
});
|
|
487
487
|
|
|
488
488
|
// Add the next note to the bar
|
|
@@ -491,7 +491,7 @@ function parseAbc(abc, options = {}) {
|
|
|
491
491
|
token: nextToken,
|
|
492
492
|
sourceIndex: nextTokenStartPos,
|
|
493
493
|
sourceLength: nextToken.length,
|
|
494
|
-
spacing: nextSpacing
|
|
494
|
+
spacing: nextSpacing
|
|
495
495
|
};
|
|
496
496
|
currentBar.push(nextNoteObj);
|
|
497
497
|
previousRealNote = null; //can’t have successive broken rhythms
|
|
@@ -518,7 +518,7 @@ function parseAbc(abc, options = {}) {
|
|
|
518
518
|
...tuple,
|
|
519
519
|
token: fullToken,
|
|
520
520
|
sourceIndex: tokenStartPos,
|
|
521
|
-
sourceLength: fullToken.length
|
|
521
|
+
sourceLength: fullToken.length
|
|
522
522
|
});
|
|
523
523
|
previousRealNote = null; // Tuplet markers break note sequences
|
|
524
524
|
continue;
|
|
@@ -533,7 +533,7 @@ function parseAbc(abc, options = {}) {
|
|
|
533
533
|
token: fullToken,
|
|
534
534
|
sourceIndex: tokenStartPos,
|
|
535
535
|
sourceLength: fullToken.length,
|
|
536
|
-
spacing
|
|
536
|
+
spacing
|
|
537
537
|
});
|
|
538
538
|
// Chord symbols don’t break note sequences
|
|
539
539
|
continue;
|
|
@@ -547,7 +547,7 @@ function parseAbc(abc, options = {}) {
|
|
|
547
547
|
token: fullToken,
|
|
548
548
|
sourceIndex: tokenStartPos,
|
|
549
549
|
sourceLength: fullToken.length,
|
|
550
|
-
spacing
|
|
550
|
+
spacing
|
|
551
551
|
});
|
|
552
552
|
// Decorations don’t break note sequences
|
|
553
553
|
continue;
|
|
@@ -562,7 +562,7 @@ function parseAbc(abc, options = {}) {
|
|
|
562
562
|
token: fullToken,
|
|
563
563
|
sourceIndex: tokenStartPos,
|
|
564
564
|
sourceLength: fullToken.length,
|
|
565
|
-
spacing
|
|
565
|
+
spacing
|
|
566
566
|
};
|
|
567
567
|
currentBar.push(noteObj);
|
|
568
568
|
// Only track as previous note if it has non-zero duration (for broken rhythms)
|
|
@@ -581,7 +581,7 @@ function parseAbc(abc, options = {}) {
|
|
|
581
581
|
token: fullToken,
|
|
582
582
|
sourceIndex: tokenStartPos,
|
|
583
583
|
sourceLength: fullToken.length,
|
|
584
|
-
spacing
|
|
584
|
+
spacing
|
|
585
585
|
};
|
|
586
586
|
firstOrSecondRepeat = !!fullToken.match(new RegExp(repeat_1Or2));
|
|
587
587
|
if (firstOrSecondRepeat) {
|
|
@@ -606,8 +606,8 @@ function parseAbc(abc, options = {}) {
|
|
|
606
606
|
? { barLineText: musicText, barLinePos: musicText.length }
|
|
607
607
|
: {
|
|
608
608
|
barLineText: fullToken,
|
|
609
|
-
barLinePos: tokenStartPos
|
|
610
|
-
|
|
609
|
+
barLinePos: tokenStartPos
|
|
610
|
+
};
|
|
611
611
|
|
|
612
612
|
//if (lastBarPos > 0) lastBarPos--; //the last character in a barline expression may be needed to match variant endings - eg `|1`
|
|
613
613
|
|
|
@@ -625,7 +625,7 @@ function parseAbc(abc, options = {}) {
|
|
|
625
625
|
sourceIndex: barLinePos,
|
|
626
626
|
sourceLength: barLineText.length,
|
|
627
627
|
//barNumber: barCount,
|
|
628
|
-
hasLineBreak: hasLineBreakAfterBar
|
|
628
|
+
hasLineBreak: hasLineBreakAfterBar
|
|
629
629
|
});
|
|
630
630
|
|
|
631
631
|
// Update the last token in current bar to mark lineBreak if bar line has one
|
|
@@ -681,7 +681,7 @@ function parseAbc(abc, options = {}) {
|
|
|
681
681
|
lineMetadata,
|
|
682
682
|
headerLines,
|
|
683
683
|
headerEndIndex,
|
|
684
|
-
musicText
|
|
684
|
+
musicText
|
|
685
685
|
};
|
|
686
686
|
}
|
|
687
687
|
}
|
|
@@ -768,5 +768,5 @@ module.exports = {
|
|
|
768
768
|
getUnitLength,
|
|
769
769
|
getMusicLines,
|
|
770
770
|
analyzeSpacing,
|
|
771
|
-
parseBarLine
|
|
771
|
+
parseBarLine
|
|
772
772
|
};
|
package/src/parse/token-utils.js
CHANGED
|
@@ -84,7 +84,7 @@ const // captures not only |1 |2, but also :|1 :||1 :|2 :||2
|
|
|
84
84
|
*
|
|
85
85
|
* Strategy: Match `[` only when NOT followed by inline field or digit; match `|` and `]` freely
|
|
86
86
|
*/
|
|
87
|
-
barLine: String.raw`:*\.*(?:[|\]]|\[(?![KLMP]:|[0-9]))*(?:\||::+)(?:[|\]:]|\[(?![KLMP]:|[0-9]))*
|
|
87
|
+
barLine: String.raw`:*\.*(?:[|\]]|\[(?![KLMP]:|[0-9]))*(?:\||::+)(?:[|\]:]|\[(?![KLMP]:|[0-9]))* *`
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
/**
|
|
@@ -133,7 +133,7 @@ const getTokenRegex = (options = {}) => {
|
|
|
133
133
|
s.broken,
|
|
134
134
|
s.variantEnding,
|
|
135
135
|
s.repeat_1Or2,
|
|
136
|
-
s.barLine
|
|
136
|
+
s.barLine
|
|
137
137
|
].join("|");
|
|
138
138
|
|
|
139
139
|
return new RegExp(fullPattern, "g");
|
|
@@ -157,7 +157,7 @@ function parseInlineField(token) {
|
|
|
157
157
|
if (fieldMatch) {
|
|
158
158
|
return {
|
|
159
159
|
field: fieldMatch[1],
|
|
160
|
-
value: fieldMatch[2].trim()
|
|
160
|
+
value: fieldMatch[2].trim()
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
return null;
|
|
@@ -165,5 +165,5 @@ function parseInlineField(token) {
|
|
|
165
165
|
module.exports = {
|
|
166
166
|
repeat_1Or2,
|
|
167
167
|
getTokenRegex,
|
|
168
|
-
parseInlineField
|
|
168
|
+
parseInlineField
|
|
169
169
|
};
|
package/src/sort/contour-sort.js
CHANGED
|
@@ -181,8 +181,8 @@ function getAbcForContour_default(tune) {
|
|
|
181
181
|
tune.incipit
|
|
182
182
|
? tune.incipit
|
|
183
183
|
: Array.isArray(tune.abc)
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
? tune.abc[0]
|
|
185
|
+
: tune.abc
|
|
186
186
|
);
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -200,13 +200,13 @@ function sort(arr, options = {}) {
|
|
|
200
200
|
comparable = [
|
|
201
201
|
["jig", "slide", "single jig", "double jig"],
|
|
202
202
|
["reel", "single reel", "reel (single)", "strathspey", "double reel"],
|
|
203
|
-
["hornpipe", "barndance", "fling"]
|
|
203
|
+
["hornpipe", "barndance", "fling"]
|
|
204
204
|
],
|
|
205
205
|
applySwingTransform = ["hornpipe", "barndance", "fling", "mazurka"],
|
|
206
206
|
getAbc: getAbcForContour = getAbcForContour_default,
|
|
207
207
|
getContourOptions = {
|
|
208
|
-
withSvg: true
|
|
209
|
-
}
|
|
208
|
+
withSvg: true
|
|
209
|
+
}
|
|
210
210
|
} = options;
|
|
211
211
|
|
|
212
212
|
const comparableMap = {};
|
|
@@ -245,16 +245,16 @@ function sort(arr, options = {}) {
|
|
|
245
245
|
? -1
|
|
246
246
|
: 1
|
|
247
247
|
: canBeCompared(a, b)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
248
|
+
? compare(a.contour, b.contour)
|
|
249
|
+
: a.contour && !b.contour
|
|
250
|
+
? -1
|
|
251
|
+
: b.contour && !a.contour
|
|
252
|
+
? 1
|
|
253
|
+
: a.name !== b.name
|
|
254
|
+
? a.name < b.name
|
|
255
|
+
? -1
|
|
256
|
+
: 1
|
|
257
|
+
: 0;
|
|
258
258
|
return comparison;
|
|
259
259
|
});
|
|
260
260
|
arr.forEach((t) => delete t.baseRhythm);
|
|
@@ -296,5 +296,5 @@ module.exports = {
|
|
|
296
296
|
compare,
|
|
297
297
|
sort,
|
|
298
298
|
simpleSort,
|
|
299
|
-
decodeChar
|
|
299
|
+
decodeChar
|
|
300
300
|
};
|
package/src/sort/contour-svg.js
CHANGED
|
@@ -61,7 +61,7 @@ const contourToSvg_defaultConfig = {
|
|
|
61
61
|
yAxisWidth: 0.5,
|
|
62
62
|
yAxisTickLength: 4,
|
|
63
63
|
yAxisTonicTickLength: 6,
|
|
64
|
-
yAxisTickWidth: 0.5
|
|
64
|
+
yAxisTickWidth: 0.5
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -150,7 +150,7 @@ function contourToSvg(contour, svgConfig = {}) {
|
|
|
150
150
|
width: segmentWidth,
|
|
151
151
|
position: decoded.position,
|
|
152
152
|
isHeld: decoded.isHeld,
|
|
153
|
-
isSilence: decoded.isSilence
|
|
153
|
+
isSilence: decoded.isSilence
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
xPosition += segmentWidth;
|
|
@@ -332,5 +332,5 @@ function contourToSvg(contour, svgConfig = {}) {
|
|
|
332
332
|
|
|
333
333
|
module.exports = {
|
|
334
334
|
contourToSvg,
|
|
335
|
-
contourToSvg_defaultConfig
|
|
335
|
+
contourToSvg_defaultConfig
|
|
336
336
|
};
|
package/src/sort/encode.js
CHANGED
|
@@ -52,9 +52,15 @@ function decodeChar(char) {
|
|
|
52
52
|
return { position, isHeld, isSilence: false };
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function shiftChar(char, nbOctaves) {
|
|
56
|
+
const { position, isHeld } = decodeChar(char);
|
|
57
|
+
return encodeToChar(position + nbOctaves * OCTAVE_SHIFT, isHeld);
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
module.exports = {
|
|
56
61
|
calculateModalPosition,
|
|
57
62
|
encodeToChar,
|
|
58
63
|
decodeChar,
|
|
59
|
-
|
|
64
|
+
shiftChar,
|
|
65
|
+
silenceChar
|
|
60
66
|
};
|
package/src/sort/get-contour.js
CHANGED
|
@@ -4,13 +4,14 @@ const {
|
|
|
4
4
|
calculateModalPosition,
|
|
5
5
|
encodeToChar,
|
|
6
6
|
silenceChar,
|
|
7
|
+
shiftChar
|
|
7
8
|
} = require("./encode.js");
|
|
8
9
|
|
|
9
10
|
const {
|
|
10
11
|
getTonalBase,
|
|
11
12
|
getUnitLength,
|
|
12
13
|
parseAbc,
|
|
13
|
-
getMeter
|
|
14
|
+
getMeter
|
|
14
15
|
} = require("../parse/parser.js");
|
|
15
16
|
|
|
16
17
|
const { contourToSvg } = require("./contour-svg.js");
|
|
@@ -28,11 +29,12 @@ const { contourToSvg } = require("./contour-svg.js");
|
|
|
28
29
|
function getContour(
|
|
29
30
|
abc,
|
|
30
31
|
{
|
|
32
|
+
contourShift = null,
|
|
31
33
|
withSvg = false,
|
|
32
34
|
withSwingTransform = false,
|
|
33
|
-
maxNbBars = new Fraction(3,
|
|
35
|
+
maxNbBars = new Fraction(3, 1),
|
|
34
36
|
maxNbUnitLengths = 12,
|
|
35
|
-
svgConfig = {}
|
|
37
|
+
svgConfig = {}
|
|
36
38
|
} = {}
|
|
37
39
|
) {
|
|
38
40
|
const tonalBase = getTonalBase(abc);
|
|
@@ -52,10 +54,10 @@ function getContour(
|
|
|
52
54
|
const maxDuration = maxNbBars.multiply(meterFraction);
|
|
53
55
|
|
|
54
56
|
const {
|
|
55
|
-
bars
|
|
57
|
+
bars
|
|
56
58
|
} = //todo: could add as an argument; default null
|
|
57
59
|
parseAbc(abc, {
|
|
58
|
-
maxBars: Math.ceil(maxNbBars.toNumber())
|
|
60
|
+
maxBars: Math.ceil(maxNbBars.toNumber())
|
|
59
61
|
});
|
|
60
62
|
|
|
61
63
|
let cumulatedDuration = new Fraction(0, 1);
|
|
@@ -65,6 +67,7 @@ function getContour(
|
|
|
65
67
|
let index = 0;
|
|
66
68
|
// get the parsed notes - notes are tokens with a duration
|
|
67
69
|
const notes = [];
|
|
70
|
+
let totalPosition = 0;
|
|
68
71
|
let tied = false,
|
|
69
72
|
previousPosition = null;
|
|
70
73
|
for (let i = 0; i < bars.length; i++) {
|
|
@@ -90,6 +93,10 @@ function getContour(
|
|
|
90
93
|
? { encoded: silenceChar, encodedHeld: silenceChar, position: 0 }
|
|
91
94
|
: getEncodedFromNote(note, tonalBase, tied, previousPosition);
|
|
92
95
|
|
|
96
|
+
if (!isSilence) {
|
|
97
|
+
totalPosition += position;
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
if (note.tied) {
|
|
94
101
|
tied = true;
|
|
95
102
|
previousPosition = position;
|
|
@@ -140,8 +147,27 @@ function getContour(
|
|
|
140
147
|
}
|
|
141
148
|
});
|
|
142
149
|
|
|
150
|
+
// next loops shift the base octave so as to mninimise the average absolute distance
|
|
151
|
+
if (contourShift !== 0) {
|
|
152
|
+
if (Number.isInteger(contourShift))
|
|
153
|
+
sortKey.forEach((c, i) => (sortKey[i] = shiftChar(c, contourShift)));
|
|
154
|
+
else {
|
|
155
|
+
let avg = totalPosition / notes.length;
|
|
156
|
+
if (avg > 0)
|
|
157
|
+
while (avg >= 7) {
|
|
158
|
+
sortKey.forEach((c, i) => (sortKey[i] = shiftChar(c, -1)));
|
|
159
|
+
avg -= 7;
|
|
160
|
+
}
|
|
161
|
+
else
|
|
162
|
+
while (avg < -5) {
|
|
163
|
+
sortKey.forEach((c, i) => (sortKey[i] = shiftChar(c, 1)));
|
|
164
|
+
avg += 7;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
143
169
|
const result = {
|
|
144
|
-
sortKey: sortKey.join("")
|
|
170
|
+
sortKey: sortKey.join("")
|
|
145
171
|
//debugPositions: debugPositions.join(","),
|
|
146
172
|
};
|
|
147
173
|
if (durations.length > 0) {
|
|
@@ -304,7 +330,7 @@ function pushShortNote(
|
|
|
304
330
|
const relativeDuration = duration.divide(unitLength),
|
|
305
331
|
d = {
|
|
306
332
|
i: index,
|
|
307
|
-
d: relativeDuration.den
|
|
333
|
+
d: relativeDuration.den
|
|
308
334
|
};
|
|
309
335
|
if (relativeDuration.num !== 1) {
|
|
310
336
|
d.n = relativeDuration.num;
|
|
@@ -324,10 +350,10 @@ function getEncodedFromNote(note, tonalBase, tied, previousPosition) {
|
|
|
324
350
|
return {
|
|
325
351
|
encoded: tied && position === previousPosition ? encodedHeld : encoded,
|
|
326
352
|
encodedHeld,
|
|
327
|
-
position
|
|
353
|
+
position
|
|
328
354
|
};
|
|
329
355
|
}
|
|
330
356
|
|
|
331
357
|
module.exports = {
|
|
332
|
-
getContour
|
|
358
|
+
getContour
|
|
333
359
|
};
|