abcjs 6.6.0 → 6.6.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/README.md +2 -2
- package/RELEASE.md +20 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +164 -28
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/package.json +1 -1
- package/src/parse/chord-grid.js +13 -2
- package/src/parse/tune-builder.js +86 -17
- package/src/synth/abc_midi_flattener.js +1 -1
- package/src/synth/repeats.js +40 -4
- package/src/write/creation/abstract-engraver.js +1 -0
- package/src/write/creation/decoration.js +30 -0
- package/src/write/draw/chord-grid.js +2 -0
- package/version.js +1 -1
package/dist/abcjs-basic.js
CHANGED
|
@@ -9465,12 +9465,14 @@ function flattenVoices(staves) {
|
|
|
9465
9465
|
});
|
|
9466
9466
|
}
|
|
9467
9467
|
if (!element.rest || element.rest.type !== 'spacer') {
|
|
9468
|
-
|
|
9468
|
+
// if the duration is zero and it is a note, then it is stemless and should count as a quarter note
|
|
9469
|
+
var dur = element.duration === 0 && !element.rest ? 0.25 : element.duration;
|
|
9470
|
+
var thisDuration = Math.floor(dur * 4);
|
|
9469
9471
|
if (thisDuration > 4) {
|
|
9470
9472
|
measureNum += Math.floor(thisDuration / 4);
|
|
9471
9473
|
beatNum = 0;
|
|
9472
9474
|
} else {
|
|
9473
|
-
var thisBeat =
|
|
9475
|
+
var thisBeat = dur * 4;
|
|
9474
9476
|
if (element.tripletMultiplier) thisBeat *= element.tripletMultiplier;
|
|
9475
9477
|
beatNum += thisBeat;
|
|
9476
9478
|
}
|
|
@@ -9481,6 +9483,14 @@ function flattenVoices(staves) {
|
|
|
9481
9483
|
nextBarEnding = "";
|
|
9482
9484
|
}
|
|
9483
9485
|
addDecoration(element, currentBar);
|
|
9486
|
+
if (element.chord) {
|
|
9487
|
+
element.chord.forEach(function (ch) {
|
|
9488
|
+
if (ch.position !== 'default') {
|
|
9489
|
+
if (!currentBar.annotations) currentBar.annotations = [];
|
|
9490
|
+
currentBar.annotations.push(ch.name);
|
|
9491
|
+
}
|
|
9492
|
+
});
|
|
9493
|
+
}
|
|
9484
9494
|
if (element.type === 'bar_dbl_repeat' || element.type === 'bar_left_repeat') currentBar.hasStartRepeat = true;
|
|
9485
9495
|
if (element.type === 'bar_dbl_repeat' || element.type === 'bar_right_repeat') currentBar.hasEndRepeat = true;
|
|
9486
9496
|
if (element.startEnding) nextBarEnding = element.startEnding;
|
|
@@ -9864,9 +9874,21 @@ var TuneBuilder = function TuneBuilder(tune) {
|
|
|
9864
9874
|
}
|
|
9865
9875
|
|
|
9866
9876
|
// If there are overlays, create new voices for them.
|
|
9877
|
+
var hadOverlays = false;
|
|
9867
9878
|
while (resolveOverlays(tune)) {
|
|
9879
|
+
hadOverlays = true;
|
|
9868
9880
|
// keep resolving overlays as long as any are found.
|
|
9869
9881
|
}
|
|
9882
|
+
|
|
9883
|
+
if (hadOverlays) {
|
|
9884
|
+
// remove any blank lines that got inserted - not sure how that happened.
|
|
9885
|
+
var voiceNum = 0;
|
|
9886
|
+
var isUseful = voiceUseful(tune.lines, voiceNum);
|
|
9887
|
+
while (isUseful !== 'not-found') {
|
|
9888
|
+
isUseful = voiceUseful(tune.lines, voiceNum);
|
|
9889
|
+
if (!isUseful) deleteVoice(tune.lines, voiceNum);else voiceNum++;
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9870
9892
|
for (var i = 0; i < tune.lines.length; i++) {
|
|
9871
9893
|
var staff = tune.lines[i].staff;
|
|
9872
9894
|
if (staff) {
|
|
@@ -10247,8 +10269,13 @@ function simplifyMetaText(tune) {
|
|
|
10247
10269
|
// }
|
|
10248
10270
|
|
|
10249
10271
|
function resolveOverlays(tune) {
|
|
10272
|
+
// TODO-PER: maybe a better algorithm than the following:
|
|
10273
|
+
// do a pass to find all the overlays - return a count
|
|
10274
|
+
// (this first pass also removes the overlays and returns them)
|
|
10275
|
+
// do a pass of creating voices with all the measures with invisible rests for all lines
|
|
10276
|
+
// do a pass of inserting the overlays
|
|
10277
|
+
|
|
10250
10278
|
var madeChanges = false;
|
|
10251
|
-
var durationsPerLines = [];
|
|
10252
10279
|
for (var i = 0; i < tune.lines.length; i++) {
|
|
10253
10280
|
var line = tune.lines[i];
|
|
10254
10281
|
if (line.staff) {
|
|
@@ -10262,10 +10289,8 @@ function resolveOverlays(tune) {
|
|
|
10262
10289
|
voice: [],
|
|
10263
10290
|
snip: []
|
|
10264
10291
|
});
|
|
10265
|
-
durationsPerLines[i] = 0;
|
|
10266
10292
|
var durationThisBar = 0;
|
|
10267
10293
|
var inOverlay = false;
|
|
10268
|
-
var overlayDuration = 0;
|
|
10269
10294
|
var snipStart = -1;
|
|
10270
10295
|
for (var kk = 0; kk < voice.length; kk++) {
|
|
10271
10296
|
var event = voice[kk];
|
|
@@ -10274,20 +10299,33 @@ function resolveOverlays(tune) {
|
|
|
10274
10299
|
inOverlay = true;
|
|
10275
10300
|
snipStart = kk;
|
|
10276
10301
|
overlayVoice[k].hasOverlay = true;
|
|
10277
|
-
|
|
10278
|
-
//
|
|
10279
|
-
// So, if the next voice doesn't appear in a previous line, create it
|
|
10302
|
+
|
|
10303
|
+
// TODO-PER: This looks like it can create a completely blank voice but I'm not sure how. That doesn't seem to hurt anything, though, because I filter that out immediately afterward.
|
|
10280
10304
|
for (var ii = 0; ii < i; ii++) {
|
|
10281
|
-
if (
|
|
10282
|
-
tune.lines[ii].staff
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10305
|
+
if (tune.lines[ii].staff) {
|
|
10306
|
+
tune.lines[ii].staff.forEach(function (s) {
|
|
10307
|
+
if (staff.voices.length >= s.voices.length) {
|
|
10308
|
+
s.voices.forEach(function (v) {
|
|
10309
|
+
var nv = [];
|
|
10310
|
+
v.forEach(function (ev) {
|
|
10311
|
+
if (ev.el_type === "bar") {
|
|
10312
|
+
nv.push(ev);
|
|
10313
|
+
} else if (ev.el_type === "note") {
|
|
10314
|
+
nv.push({
|
|
10315
|
+
el_type: "note",
|
|
10316
|
+
duration: ev.duration,
|
|
10317
|
+
rest: {
|
|
10318
|
+
type: "invisible"
|
|
10319
|
+
},
|
|
10320
|
+
startChar: ev.startChar,
|
|
10321
|
+
endChar: ev.endChar
|
|
10322
|
+
});
|
|
10323
|
+
}
|
|
10324
|
+
});
|
|
10325
|
+
s.voices.push(nv);
|
|
10326
|
+
});
|
|
10327
|
+
}
|
|
10328
|
+
});
|
|
10291
10329
|
}
|
|
10292
10330
|
}
|
|
10293
10331
|
} else if (event.el_type === "bar") {
|
|
@@ -10318,7 +10356,6 @@ function resolveOverlays(tune) {
|
|
|
10318
10356
|
overlayVoice[k].voice.push(event);
|
|
10319
10357
|
} else if (!event.rest || event.rest.type !== 'spacer') {
|
|
10320
10358
|
durationThisBar += event.duration;
|
|
10321
|
-
durationsPerLines[i] += event.duration;
|
|
10322
10359
|
}
|
|
10323
10360
|
} else if (event.el_type === "scale" || event.el_type === "stem" || event.el_type === "overlay" || event.el_type === "style" || event.el_type === "transpose" || event.el_type === "color") {
|
|
10324
10361
|
// These types of events are duplicated on the overlay layer.
|
|
@@ -10370,7 +10407,6 @@ function resolveOverlays(tune) {
|
|
|
10370
10407
|
}
|
|
10371
10408
|
return madeChanges;
|
|
10372
10409
|
}
|
|
10373
|
-
;
|
|
10374
10410
|
function findLastBar(voice, start) {
|
|
10375
10411
|
for (var i = start - 1; i > 0 && voice[i].el_type !== "bar"; i--) {}
|
|
10376
10412
|
return i;
|
|
@@ -10773,6 +10809,42 @@ function createLine(self, tune, params) {
|
|
|
10773
10809
|
};
|
|
10774
10810
|
createStaff(self, tune, params);
|
|
10775
10811
|
}
|
|
10812
|
+
function voiceUseful(lines, voiceNum) {
|
|
10813
|
+
var isUseful = false;
|
|
10814
|
+
var voiceExists = false;
|
|
10815
|
+
for (var line = 0; line < lines.length; line++) {
|
|
10816
|
+
var staves = lines[line].staff;
|
|
10817
|
+
if (staves) {
|
|
10818
|
+
for (var s = 0; s < staves.length; s++) {
|
|
10819
|
+
var staff = staves[s];
|
|
10820
|
+
if (voiceNum < staff.voices.length) {
|
|
10821
|
+
voiceExists = true;
|
|
10822
|
+
var voice = staff.voices[voiceNum];
|
|
10823
|
+
var output = [];
|
|
10824
|
+
for (var e = 0; e < voice.length; e++) {
|
|
10825
|
+
var el = voice[e];
|
|
10826
|
+
if (el.el_type === 'note' && (!el.rest || el.chord)) isUseful = true;
|
|
10827
|
+
}
|
|
10828
|
+
}
|
|
10829
|
+
}
|
|
10830
|
+
}
|
|
10831
|
+
}
|
|
10832
|
+
if (!voiceExists) return 'not-found';
|
|
10833
|
+
return isUseful;
|
|
10834
|
+
}
|
|
10835
|
+
function deleteVoice(lines, voiceNum) {
|
|
10836
|
+
for (var line = 0; line < lines.length; line++) {
|
|
10837
|
+
var staves = lines[line].staff;
|
|
10838
|
+
if (staves) {
|
|
10839
|
+
for (var s = 0; s < staves.length; s++) {
|
|
10840
|
+
var staff = staves[s];
|
|
10841
|
+
if (voiceNum < staff.voices.length) {
|
|
10842
|
+
staff.voices.splice(voiceNum, 1);
|
|
10843
|
+
}
|
|
10844
|
+
}
|
|
10845
|
+
}
|
|
10846
|
+
}
|
|
10847
|
+
}
|
|
10776
10848
|
module.exports = TuneBuilder;
|
|
10777
10849
|
|
|
10778
10850
|
/***/ }),
|
|
@@ -12011,7 +12083,7 @@ var pitchesToPerc = __webpack_require__(/*! ./pitches-to-perc */ "./src/synth/pi
|
|
|
12011
12083
|
break;
|
|
12012
12084
|
default:
|
|
12013
12085
|
// This should never happen
|
|
12014
|
-
console.log("MIDI creation. Unknown el_type: " + element.el_type + "\n");
|
|
12086
|
+
console.log("MIDI creation. Unknown el_type: " + element.el_type + "\n");
|
|
12015
12087
|
break;
|
|
12016
12088
|
}
|
|
12017
12089
|
}
|
|
@@ -16096,10 +16168,17 @@ function Repeats(voice) {
|
|
|
16096
16168
|
var isStartRepeat = elem.type === "bar_left_repeat" || elem.type === "bar_dbl_repeat";
|
|
16097
16169
|
var isEndRepeat = elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat";
|
|
16098
16170
|
var startEnding = elem.startEnding ? startEndingNumbers(elem.startEnding) : undefined;
|
|
16099
|
-
if (isEndRepeat)
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16171
|
+
if (isEndRepeat) {
|
|
16172
|
+
// If there are two endRepeats in a row, that is a notation error, but we'll recover by pretending there was a startRepeat right before it.
|
|
16173
|
+
if (this.sections.length > 0 && this.sections[this.sections.length - 1].type === 'endRepeat') this.sections.push({
|
|
16174
|
+
type: "startRepeat",
|
|
16175
|
+
index: this.sections[this.sections.length - 1].index
|
|
16176
|
+
});
|
|
16177
|
+
this.sections.push({
|
|
16178
|
+
type: "endRepeat",
|
|
16179
|
+
index: thisIndex
|
|
16180
|
+
});
|
|
16181
|
+
}
|
|
16103
16182
|
if (isStartRepeat) this.sections.push({
|
|
16104
16183
|
type: "startRepeat",
|
|
16105
16184
|
index: thisIndex
|
|
@@ -16167,7 +16246,7 @@ function Repeats(voice) {
|
|
|
16167
16246
|
}
|
|
16168
16247
|
}
|
|
16169
16248
|
if (lastUsed < section.index - 1) {
|
|
16170
|
-
console.log("gap", voice.slice(lastUsed
|
|
16249
|
+
//console.log("gap", voice.slice(lastUsed+1, section.index))
|
|
16171
16250
|
repeatInstructions.push({
|
|
16172
16251
|
common: {
|
|
16173
16252
|
start: lastUsed + 1,
|
|
@@ -16252,8 +16331,31 @@ function Repeats(voice) {
|
|
|
16252
16331
|
}
|
|
16253
16332
|
function duplicateSpan(input, output, start, end) {
|
|
16254
16333
|
//console.log("dup", {start, end})
|
|
16334
|
+
if (start < 0) start = 0;
|
|
16335
|
+
// If there is a bar at the end of a line and a bar to start the next line, it would be duplicated.
|
|
16336
|
+
if (output.length > 0 && input[start].el_type === 'bar' && output[output.length - 1].el_type === 'bar') start++;
|
|
16255
16337
|
for (var i = start; i <= end; i++) {
|
|
16256
|
-
|
|
16338
|
+
// If there is a beginning element, it might be duplicated.
|
|
16339
|
+
var index;
|
|
16340
|
+
var skip = false;
|
|
16341
|
+
if (input[i].el_type === 'key' || input[i].el_type === 'meter' || input[i].el_type === 'tempo' || input[i].el_type === 'instrument') {
|
|
16342
|
+
index = output.length - 1;
|
|
16343
|
+
while (index >= 0 && output[index].el_type !== input[i].el_type) {
|
|
16344
|
+
index--;
|
|
16345
|
+
}
|
|
16346
|
+
if (index >= 0) {
|
|
16347
|
+
if (input[i].el_type === 'key' && areKeysEqual(input[i], output[index])) {
|
|
16348
|
+
skip = true;
|
|
16349
|
+
} else if (input[i].el_type === 'meter' && input[i].num === output[index].num && input[i].den === output[index].den) {
|
|
16350
|
+
skip = true;
|
|
16351
|
+
} else if (input[i].el_type === 'instrument' && input[i].program === output[index].program) {
|
|
16352
|
+
skip = true;
|
|
16353
|
+
} else if (input[i].el_type === 'tempo' && input[i].qpm === output[index].qpm) {
|
|
16354
|
+
skip = true;
|
|
16355
|
+
}
|
|
16356
|
+
}
|
|
16357
|
+
}
|
|
16358
|
+
if (!skip) output.push(duplicateItem(input[i]));
|
|
16257
16359
|
}
|
|
16258
16360
|
}
|
|
16259
16361
|
function duplicateItem(src) {
|
|
@@ -16261,6 +16363,11 @@ function duplicateItem(src) {
|
|
|
16261
16363
|
if (item.pitches) item.pitches = parseCommon.cloneArray(item.pitches);
|
|
16262
16364
|
return item;
|
|
16263
16365
|
}
|
|
16366
|
+
function areKeysEqual(el1, el2) {
|
|
16367
|
+
if (!el1.accidentals || !el2.accidentals) return false; // this shouldn't happen, but if so, we don't want to skip the element
|
|
16368
|
+
|
|
16369
|
+
return JSON.stringify(el1.accidentals) === JSON.stringify(el2.accidentals);
|
|
16370
|
+
}
|
|
16264
16371
|
function startEndingNumbers(startEnding) {
|
|
16265
16372
|
// The ending can be in four different types: "random-string", "number", "number-number", "number,number"
|
|
16266
16373
|
// If we don't get a number out of it then we will just skip the ending - we don't know what to do with it.
|
|
@@ -18450,6 +18557,7 @@ AbstractEngraver.prototype.createABCVoice = function (abcline, tempo, s, v, isSi
|
|
|
18450
18557
|
}
|
|
18451
18558
|
pos += ret.count;
|
|
18452
18559
|
}
|
|
18560
|
+
this.decoration.endLine(voice);
|
|
18453
18561
|
this.pushCrossLineElems(s, v);
|
|
18454
18562
|
};
|
|
18455
18563
|
AbstractEngraver.prototype.saveState = function () {
|
|
@@ -20168,6 +20276,16 @@ function leftDecoration(decoration, abselem, roomtaken) {
|
|
|
20168
20276
|
}
|
|
20169
20277
|
}
|
|
20170
20278
|
}
|
|
20279
|
+
Decoration.prototype.endLine = function (voice) {
|
|
20280
|
+
if (this.startDiminuendoX) {
|
|
20281
|
+
voice.addOther(new CrescendoElem(this.startDiminuendoX, lastNote(voice.children), ">", this.dynamicPositioning));
|
|
20282
|
+
this.startDiminuendoX = undefined;
|
|
20283
|
+
}
|
|
20284
|
+
if (this.startCrescendoX) {
|
|
20285
|
+
voice.addOther(new CrescendoElem(this.startCrescendoX, lastNote(voice.children), "<", this.dynamicPositioning));
|
|
20286
|
+
this.startCrescendoX = undefined;
|
|
20287
|
+
}
|
|
20288
|
+
};
|
|
20171
20289
|
Decoration.prototype.dynamicDecoration = function (voice, decoration, abselem, positioning) {
|
|
20172
20290
|
var diminuendo;
|
|
20173
20291
|
var crescendo;
|
|
@@ -20176,9 +20294,11 @@ Decoration.prototype.dynamicDecoration = function (voice, decoration, abselem, p
|
|
|
20176
20294
|
switch (decoration[i]) {
|
|
20177
20295
|
case "diminuendo(":
|
|
20178
20296
|
this.startDiminuendoX = abselem;
|
|
20297
|
+
this.dynamicPositioning = positioning;
|
|
20179
20298
|
diminuendo = undefined;
|
|
20180
20299
|
break;
|
|
20181
20300
|
case "diminuendo)":
|
|
20301
|
+
if (!this.startDiminuendoX) this.startDiminuendoX = firstNote(voice.children);
|
|
20182
20302
|
diminuendo = {
|
|
20183
20303
|
start: this.startDiminuendoX,
|
|
20184
20304
|
stop: abselem
|
|
@@ -20187,9 +20307,11 @@ Decoration.prototype.dynamicDecoration = function (voice, decoration, abselem, p
|
|
|
20187
20307
|
break;
|
|
20188
20308
|
case "crescendo(":
|
|
20189
20309
|
this.startCrescendoX = abselem;
|
|
20310
|
+
this.dynamicPositioning = positioning;
|
|
20190
20311
|
crescendo = undefined;
|
|
20191
20312
|
break;
|
|
20192
20313
|
case "crescendo)":
|
|
20314
|
+
if (!this.startCrescendoX) this.startCrescendoX = firstNote(voice.children);
|
|
20193
20315
|
crescendo = {
|
|
20194
20316
|
start: this.startCrescendoX,
|
|
20195
20317
|
stop: abselem
|
|
@@ -20221,6 +20343,16 @@ Decoration.prototype.dynamicDecoration = function (voice, decoration, abselem, p
|
|
|
20221
20343
|
voice.addOther(new GlissandoElem(glissando.start, glissando.stop));
|
|
20222
20344
|
}
|
|
20223
20345
|
};
|
|
20346
|
+
function firstNote(els) {
|
|
20347
|
+
for (var i = 0; i < els.length; i++) {
|
|
20348
|
+
if (els[i].abcelem.pitches) return els[i];
|
|
20349
|
+
}
|
|
20350
|
+
return null;
|
|
20351
|
+
}
|
|
20352
|
+
function lastNote(els) {
|
|
20353
|
+
// The end point doesn't need to be a note - we end at the end of the line
|
|
20354
|
+
return els[els.length - 1];
|
|
20355
|
+
}
|
|
20224
20356
|
Decoration.prototype.createDecoration = function (voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, positioning, hasVocals, accentAbove) {
|
|
20225
20357
|
if (!positioning) positioning = {
|
|
20226
20358
|
ornamentPosition: 'above',
|
|
@@ -22785,6 +22917,9 @@ function drawChordGrid(renderer, parts, leftMargin, pageWidth, fonts) {
|
|
|
22785
22917
|
var PART_MARGIN_TOP = 10;
|
|
22786
22918
|
var PART_MARGIN_BOTTOM = 20;
|
|
22787
22919
|
var TEXT_MARGIN = 16;
|
|
22920
|
+
renderer.paper.openGroup({
|
|
22921
|
+
klass: 'abcjs-chord-grid'
|
|
22922
|
+
});
|
|
22788
22923
|
parts.forEach(function (part) {
|
|
22789
22924
|
switch (part.type) {
|
|
22790
22925
|
case "text":
|
|
@@ -22868,6 +23003,7 @@ function drawChordGrid(renderer, parts, leftMargin, pageWidth, fonts) {
|
|
|
22868
23003
|
break;
|
|
22869
23004
|
}
|
|
22870
23005
|
});
|
|
23006
|
+
renderer.paper.closeGroup();
|
|
22871
23007
|
}
|
|
22872
23008
|
function drawPercent(renderer, x, y, offset) {
|
|
22873
23009
|
var lineX1 = x - 10;
|
|
@@ -27815,7 +27951,7 @@ module.exports = Svg;
|
|
|
27815
27951
|
\********************/
|
|
27816
27952
|
/***/ (function(module) {
|
|
27817
27953
|
|
|
27818
|
-
var version = '6.6.
|
|
27954
|
+
var version = '6.6.1';
|
|
27819
27955
|
module.exports = version;
|
|
27820
27956
|
|
|
27821
27957
|
/***/ })
|