abcjs 6.4.3 → 6.5.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.
- package/RELEASE.md +58 -1
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +445 -149
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/package.json +1 -1
- package/src/parse/abc_parse_directive.js +36 -4
- package/src/parse/abc_parse_header.js +3 -3
- package/src/parse/abc_parse_key_voice.js +41 -6
- package/src/parse/abc_parse_music.js +10 -4
- package/src/parse/abc_parse_settings.js +1 -0
- package/src/parse/abc_tokenizer.js +11 -3
- package/src/parse/abc_transpose.js +1 -1
- package/src/parse/transpose-chord.js +9 -1
- package/src/parse/tune-builder.js +14 -2
- package/src/str/output.js +4 -4
- package/src/synth/abc_midi_flattener.js +61 -21
- package/src/synth/abc_midi_sequencer.js +26 -6
- package/src/synth/chord-track.js +1 -1
- package/src/synth/create-synth.js +74 -65
- package/src/synth/place-note.js +1 -1
- package/src/test/abc_midi_sequencer_lint.js +1 -1
- package/src/test/abc_parser_lint.js +1 -1
- package/src/write/creation/abstract-engraver.js +11 -7
- package/src/write/creation/decoration.js +2 -0
- package/src/write/creation/elements/absolute-element.js +27 -16
- package/src/write/creation/elements/free-text.js +6 -1
- package/src/write/draw/draw.js +8 -0
- package/src/write/draw/ending.js +12 -3
- package/src/write/draw/text.js +8 -1
- package/src/write/interactive/selection.js +7 -1
- package/src/write/svg.js +23 -2
- package/types/index.d.ts +1 -1
- package/version.js +1 -1
|
@@ -26,7 +26,7 @@ function CreateSynth() {
|
|
|
26
26
|
self.audioBuffers = []; // cache of the buffers so starting play can be fast.
|
|
27
27
|
self.duration = undefined; // the duration of the tune in seconds.
|
|
28
28
|
self.isRunning = false; // whether there is currently a sound buffer running.
|
|
29
|
-
self.options =
|
|
29
|
+
self.options = {} // Thx tomohirohiratsuka
|
|
30
30
|
self.pickupLength = 0
|
|
31
31
|
|
|
32
32
|
// Load and cache all needed sounds
|
|
@@ -299,82 +299,91 @@ function CreateSynth() {
|
|
|
299
299
|
return Promise.reject(new Error(notSupportedMessage));
|
|
300
300
|
if (self.debugCallback)
|
|
301
301
|
self.debugCallback("prime called");
|
|
302
|
-
return new Promise(function(resolve) {
|
|
303
|
-
var startTime = activeAudioContext().currentTime;
|
|
304
|
-
var tempoMultiplier = self.millisecondsPerMeasure / 1000 / self.meterSize;
|
|
305
|
-
self.duration = self.flattened.totalDuration * tempoMultiplier;
|
|
306
|
-
if(self.duration <= 0) {
|
|
307
|
-
self.audioBuffers = [];
|
|
308
|
-
return resolve({ status: "empty", seconds: 0});
|
|
309
|
-
}
|
|
310
|
-
self.duration += fadeTimeSec;
|
|
311
|
-
var totalSamples = Math.floor(activeAudioContext().sampleRate * self.duration);
|
|
312
302
|
|
|
313
|
-
|
|
314
|
-
|
|
303
|
+
return new Promise(function(resolve, reject) {
|
|
304
|
+
try {
|
|
305
|
+
var startTime = activeAudioContext().currentTime;
|
|
306
|
+
var tempoMultiplier = self.millisecondsPerMeasure / 1000 / self.meterSize;
|
|
307
|
+
self.duration = self.flattened.totalDuration * tempoMultiplier;
|
|
308
|
+
if (self.duration <= 0) {
|
|
309
|
+
self.audioBuffers = [];
|
|
310
|
+
return resolve({status: "empty", seconds: 0});
|
|
311
|
+
}
|
|
312
|
+
self.duration += fadeTimeSec;
|
|
313
|
+
var totalSamples = Math.floor(activeAudioContext().sampleRate * self.duration);
|
|
315
314
|
|
|
316
|
-
|
|
315
|
+
// There might be a previous run that needs to be turned off.
|
|
316
|
+
self.stop();
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength)
|
|
318
|
+
var noteMapTracks = createNoteMap(self.flattened);
|
|
320
319
|
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
if (self.options.swing)
|
|
321
|
+
addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength)
|
|
323
322
|
|
|
324
|
-
|
|
323
|
+
if (self.sequenceCallback)
|
|
324
|
+
self.sequenceCallback(noteMapTracks, self.callbackContext);
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
var
|
|
331
|
-
|
|
332
|
-
var
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
326
|
+
var panDistances = setPan(noteMapTracks.length, self.pan);
|
|
327
|
+
|
|
328
|
+
// Create a simple list of all the unique sounds in this music and where they should be placed.
|
|
329
|
+
// There appears to be a limit on how many audio buffers can be created at once so this technique limits the number needed.
|
|
330
|
+
var uniqueSounds = {};
|
|
331
|
+
noteMapTracks.forEach(function (noteMap, trackNumber) {
|
|
332
|
+
var panDistance = panDistances && panDistances.length > trackNumber ? panDistances[trackNumber] : 0;
|
|
333
|
+
noteMap.forEach(function (note) {
|
|
334
|
+
var key = note.instrument + ':' + note.pitch + ':' + note.volume + ':' + Math.round((note.end - note.start) * 1000) / 1000 + ':' + panDistance + ':' + tempoMultiplier + ':' + (note.cents ? note.cents : 0);
|
|
335
|
+
if (self.debugCallback)
|
|
336
|
+
self.debugCallback("noteMapTrack " + key)
|
|
337
|
+
if (!uniqueSounds[key])
|
|
338
|
+
uniqueSounds[key] = [];
|
|
339
|
+
uniqueSounds[key].push(note.start);
|
|
340
|
+
});
|
|
338
341
|
});
|
|
339
|
-
});
|
|
340
342
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
343
|
+
// Now that we know what we are trying to create, construct the audio buffer by creating each sound and placing it.
|
|
344
|
+
var allPromises = [];
|
|
345
|
+
var audioBuffer = activeAudioContext().createBuffer(2, totalSamples, activeAudioContext().sampleRate);
|
|
346
|
+
for (var key2 = 0; key2 < Object.keys(uniqueSounds).length; key2++) {
|
|
347
|
+
var k = Object.keys(uniqueSounds)[key2];
|
|
348
|
+
var parts = k.split(":");
|
|
349
|
+
var cents = parts[6] !== undefined ? parseFloat(parts[6]) : 0;
|
|
350
|
+
parts = {instrument: parts[0], pitch: parseInt(parts[1], 10), volume: parseInt(parts[2], 10), len: parseFloat(parts[3]), pan: parseFloat(parts[4]), tempoMultiplier: parseFloat(parts[5]), cents: cents};
|
|
351
|
+
allPromises.push(placeNote(audioBuffer, activeAudioContext().sampleRate, parts, uniqueSounds[k], self.soundFontVolumeMultiplier, self.programOffsets[parts.instrument], fadeTimeSec, self.noteEnd / 1000, self.debugCallback));
|
|
352
|
+
}
|
|
353
|
+
self.audioBuffers = [audioBuffer];
|
|
352
354
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
})
|
|
368
|
-
} else if (activeAudioContext().state === "interrupted") {
|
|
369
|
-
activeAudioContext().suspend().then(function () {
|
|
355
|
+
if (self.debugCallback) {
|
|
356
|
+
self.debugCallback("sampleRate = " + activeAudioContext().sampleRate);
|
|
357
|
+
self.debugCallback("totalSamples = " + totalSamples);
|
|
358
|
+
self.debugCallback("creationTime = " + Math.floor((activeAudioContext().currentTime - startTime) * 1000) + "ms");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function resolveData(me) {
|
|
362
|
+
var duration = me && me.audioBuffers && me.audioBuffers.length > 0 ? me.audioBuffers[0].duration : 0;
|
|
363
|
+
return {status: activeAudioContext().state, duration: duration}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
Promise.all(allPromises).then(function () {
|
|
367
|
+
// Safari iOS can mess with the audioContext state, so resume if needed.
|
|
368
|
+
if (activeAudioContext().state === "suspended") {
|
|
370
369
|
activeAudioContext().resume().then(function () {
|
|
371
370
|
resolve(resolveData(self));
|
|
372
371
|
})
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
372
|
+
} else if (activeAudioContext().state === "interrupted") {
|
|
373
|
+
activeAudioContext().suspend().then(function () {
|
|
374
|
+
activeAudioContext().resume().then(function () {
|
|
375
|
+
resolve(resolveData(self));
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
} else {
|
|
379
|
+
resolve(resolveData(self));
|
|
380
|
+
}
|
|
381
|
+
}).catch(function (error) {
|
|
382
|
+
reject(error)
|
|
383
|
+
});
|
|
384
|
+
} catch (error) {
|
|
385
|
+
reject(error)
|
|
386
|
+
}
|
|
378
387
|
});
|
|
379
388
|
};
|
|
380
389
|
|
package/src/synth/place-note.js
CHANGED
|
@@ -101,7 +101,7 @@ function placeNote(outputAudioBuffer, sampleRate, sound, startArray, volumeMulti
|
|
|
101
101
|
.catch(function (error) {
|
|
102
102
|
if (debugCallback)
|
|
103
103
|
debugCallback('placeNote catch: '+error.message)
|
|
104
|
-
return Promise.
|
|
104
|
+
return Promise.reject(error)
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -53,7 +53,7 @@ var JSONSchema = require('./jsonschema-b4');
|
|
|
53
53
|
var ParserLint = function() {
|
|
54
54
|
"use strict";
|
|
55
55
|
var decorationList = { type: 'array', optional: true, items: { type: 'string', Enum: [
|
|
56
|
-
"trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
|
|
56
|
+
"trill", "trillh", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
|
|
57
57
|
"fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
|
|
58
58
|
"open", "thumb", "snap", "turn", "roll", "irishroll", "breath", "shortphrase", "mediumphrase", "longphrase",
|
|
59
59
|
"segno", "coda", "D.S.", "D.C.", "fine", "crescendo(", "crescendo)", "diminuendo(", "diminuendo)", "glissando(", "glissando)",
|
|
@@ -943,7 +943,8 @@ AbstractEngraver.prototype.addMeasureNumber = function (number, abselem) {
|
|
|
943
943
|
var dx = 0;
|
|
944
944
|
if (abselem.isClef) // If this is a clef rather than bar line, then the number shouldn't be centered because it could overlap the left side. This is an easy way to let it be centered but move it over, too.
|
|
945
945
|
dx += measureNumDim.width / 2
|
|
946
|
-
|
|
946
|
+
// MAE 1 Oct 2024 - Change 13 to 13.5 since previously bar numbers were very slightly overlapping the top of the clef
|
|
947
|
+
var vert = measureNumDim.width > 10 && abselem.abcelem.type === "treble" ? 13.5 : 11
|
|
947
948
|
abselem.addFixed(new RelativeElement(number, dx, measureNumDim.width, vert + measureNumDim.height / spacing.STEP, { type: "barNumber", dim: this.getTextSize.attr("measurefont", 'bar-number') }));
|
|
948
949
|
};
|
|
949
950
|
|
|
@@ -1025,13 +1026,16 @@ AbstractEngraver.prototype.createBarLine = function (voice, elem, isFirstStaff)
|
|
|
1025
1026
|
abselem.addRight(new RelativeElement("dots.dot", dx, 1, 5));
|
|
1026
1027
|
} // 2 is hardcoded
|
|
1027
1028
|
|
|
1028
|
-
if (elem.startEnding && isFirstStaff) {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1029
|
+
if (elem.startEnding && isFirstStaff) {
|
|
1030
|
+
// MAE 17 May 2025 - Fixes drawing issue
|
|
1031
|
+
if (voice.voicenumber === 0){
|
|
1032
|
+
// only put the first & second ending marks on the first staff
|
|
1033
|
+
var textWidth = this.getTextSize.calc(elem.startEnding, "repeatfont", '').width;
|
|
1034
|
+
abselem.minspacing += textWidth + 10; // Give plenty of room for the ending number.
|
|
1035
|
+
this.partstartelem = new EndingElem(elem.startEnding, anchor, null);
|
|
1036
|
+
voice.addOther(this.partstartelem);
|
|
1037
|
+
}
|
|
1033
1038
|
}
|
|
1034
|
-
|
|
1035
1039
|
// Add a little space to the left of the bar line so that nothing can crowd it.
|
|
1036
1040
|
abselem.extraw -= 5;
|
|
1037
1041
|
|
|
@@ -175,6 +175,7 @@ var stackedDecoration = function (decoration, width, abselem, yPos, positioning,
|
|
|
175
175
|
"mediumphrase": "scripts.mediumphrase",
|
|
176
176
|
"longphrase": "scripts.longphrase",
|
|
177
177
|
"trill": "scripts.trill",
|
|
178
|
+
"trillh": "scripts.trill",
|
|
178
179
|
"roll": "scripts.roll",
|
|
179
180
|
"irishroll": "scripts.roll",
|
|
180
181
|
"marcato": "scripts.umarcato",
|
|
@@ -237,6 +238,7 @@ var stackedDecoration = function (decoration, width, abselem, yPos, positioning,
|
|
|
237
238
|
case "mediumphrase":
|
|
238
239
|
case "longphrase":
|
|
239
240
|
case "trill":
|
|
241
|
+
case "trillh":
|
|
240
242
|
case "roll":
|
|
241
243
|
case "irishroll":
|
|
242
244
|
case "marcato":
|
|
@@ -177,22 +177,33 @@ AbsoluteElement.prototype.setLimit = function (member, child) {
|
|
|
177
177
|
};
|
|
178
178
|
|
|
179
179
|
AbsoluteElement.prototype._addChild = function (child) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
180
|
+
// console.log("Relative:",child);
|
|
181
|
+
|
|
182
|
+
// MAE 30 Sep 2024 - To avoid extra space for chords if there is only a bar number on the clef
|
|
183
|
+
var okToPushTop = true;
|
|
184
|
+
if ((this.abcelem.el_type == "clef") && (child.type == "barNumber")){
|
|
185
|
+
okToPushTop = false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
child.parent = this;
|
|
189
|
+
this.children[this.children.length] = child;
|
|
190
|
+
|
|
191
|
+
if (okToPushTop){
|
|
192
|
+
this.pushTop(child.top);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.pushBottom(child.bottom);
|
|
196
|
+
this.setLimit('tempoHeightAbove', child);
|
|
197
|
+
this.setLimit('partHeightAbove', child);
|
|
198
|
+
this.setLimit('volumeHeightAbove', child);
|
|
199
|
+
this.setLimit('dynamicHeightAbove', child);
|
|
200
|
+
this.setLimit('endingHeightAbove', child);
|
|
201
|
+
this.setLimit('chordHeightAbove', child);
|
|
202
|
+
this.setLimit('lyricHeightAbove', child);
|
|
203
|
+
this.setLimit('lyricHeightBelow', child);
|
|
204
|
+
this.setLimit('chordHeightBelow', child);
|
|
205
|
+
this.setLimit('volumeHeightBelow', child);
|
|
206
|
+
this.setLimit('dynamicHeightBelow', child);
|
|
196
207
|
};
|
|
197
208
|
|
|
198
209
|
AbsoluteElement.prototype.pushTop = function (top) {
|
|
@@ -10,7 +10,12 @@ function FreeText(info, vskip, getFontAndAttr, paddingLeft, width, getTextSize)
|
|
|
10
10
|
} else if (typeof text === 'string') {
|
|
11
11
|
this.rows.push({ move: hash.attr['font-size'] / 2 }); // TODO-PER: move down some - the y location should be the top of the text, but we output text specifying the center line.
|
|
12
12
|
this.rows.push({ left: paddingLeft, text: text, font: 'textfont', klass: 'defined-text', anchor: "start", startChar: info.startChar, endChar: info.endChar, absElemType: "freeText", name: "free-text" });
|
|
13
|
-
|
|
13
|
+
// MAE 9 May 2025 - Force blank text lines in a text block to have height
|
|
14
|
+
function replaceStandaloneNewlinesForTextBlocks(input) {
|
|
15
|
+
return input.replace(/^[ \t]*\n/gm, 'X\n');;
|
|
16
|
+
}
|
|
17
|
+
var textForSize = replaceStandaloneNewlinesForTextBlocks(text);
|
|
18
|
+
size = getTextSize.calc(textForSize, 'textfont', 'defined-text'); // was text
|
|
14
19
|
this.rows.push({ move: size.height });
|
|
15
20
|
} else if (text) {
|
|
16
21
|
var maxHeight = 0;
|
package/src/write/draw/draw.js
CHANGED
|
@@ -15,10 +15,18 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
|
|
|
15
15
|
renderer.paper.closeGroup()
|
|
16
16
|
renderer.moveY(renderer.spacing.music);
|
|
17
17
|
var staffgroups = [];
|
|
18
|
+
var nStaves = 0;
|
|
18
19
|
for (var line = 0; line < abcTune.lines.length; line++) {
|
|
19
20
|
classes.incrLine();
|
|
20
21
|
var abcLine = abcTune.lines[line];
|
|
21
22
|
if (abcLine.staff) {
|
|
23
|
+
// MAE 26 May 2025 - for incipits staff count limiting
|
|
24
|
+
nStaves++;
|
|
25
|
+
if (abcTune.formatting.maxStaves){
|
|
26
|
+
if (nStaves > abcTune.formatting.maxStaves){
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
22
30
|
if (classes.shouldAddClasses)
|
|
23
31
|
groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber
|
|
24
32
|
renderer.paper.openGroup(groupClasses)
|
package/src/write/draw/ending.js
CHANGED
|
@@ -24,9 +24,18 @@ function drawEnding(renderer, params, linestartx, lineendx, selectables) {
|
|
|
24
24
|
|
|
25
25
|
pathString += sprintf("M %f %f L %f %f ",
|
|
26
26
|
linestartx, y, lineendx, y);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
renderer.paper.openGroup({
|
|
28
|
+
klass: renderer.controller.classes.generate("ending"),
|
|
29
|
+
// MAE 17 May 2025 - Ending numbers not being drawn in correct color
|
|
30
|
+
fill: renderer.foregroundColor,
|
|
31
|
+
"data-name": "ending"
|
|
32
|
+
});
|
|
33
|
+
printPath(renderer, {
|
|
34
|
+
path: pathString,
|
|
35
|
+
stroke: renderer.foregroundColor,
|
|
36
|
+
fill: renderer.foregroundColor,
|
|
37
|
+
"data-name": "line"
|
|
38
|
+
});
|
|
30
39
|
if (params.anchor1)
|
|
31
40
|
renderText(renderer, {
|
|
32
41
|
x: roundNumber(linestartx + 5),
|
package/src/write/draw/text.js
CHANGED
|
@@ -37,7 +37,14 @@ function renderText(renderer, params, alreadyInGroup) {
|
|
|
37
37
|
hash.attr.cursor = params.cursor;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// MAE 9 May 2025 for free text blocks
|
|
41
|
+
var text;
|
|
42
|
+
if (params.name == "free-text"){
|
|
43
|
+
text = params.text.replace(/^[ \t]*\n/gm, ' \n');
|
|
44
|
+
}
|
|
45
|
+
else{
|
|
46
|
+
text = params.text.replace(/\n\n/g, "\n \n");
|
|
47
|
+
}
|
|
41
48
|
text = text.replace(/^\n/, "\xA0\n");
|
|
42
49
|
|
|
43
50
|
if (hash.font.box) {
|
|
@@ -130,8 +130,14 @@ function keyboardSelection(ev) {
|
|
|
130
130
|
function findElementInHistory(selectables, el) {
|
|
131
131
|
if (!el)
|
|
132
132
|
return -1;
|
|
133
|
+
// This should always exist, but it occasionally causes an exception, so check first.
|
|
134
|
+
var dataset = el.dataset
|
|
135
|
+
if (!dataset)
|
|
136
|
+
return -1
|
|
137
|
+
var index = dataset.index
|
|
133
138
|
for (var i = 0; i < selectables.length; i++) {
|
|
134
|
-
|
|
139
|
+
var svgDataset = selectables[i].svgEl.dataset
|
|
140
|
+
if (svgDataset && index === svgDataset.index)
|
|
135
141
|
return i;
|
|
136
142
|
}
|
|
137
143
|
return -1;
|
package/src/write/svg.js
CHANGED
|
@@ -176,8 +176,14 @@ Svg.prototype.text = function (text, attr, target) {
|
|
|
176
176
|
el.setAttribute(key, attr[key]);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
var isFreeText = (attr["data-name"] == "free-text");
|
|
179
180
|
var lines = ("" + text).split("\n");
|
|
180
181
|
for (var i = 0; i < lines.length; i++) {
|
|
182
|
+
if (isFreeText && (lines[i] == "")){
|
|
183
|
+
// Don't draw empty lines
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
181
187
|
var line = document.createElementNS(svgNS, 'tspan');
|
|
182
188
|
line.setAttribute("x", attr.x ? attr.x : 0);
|
|
183
189
|
if (i !== 0)
|
|
@@ -200,8 +206,23 @@ Svg.prototype.text = function (text, attr, target) {
|
|
|
200
206
|
ts3.textContent = parts[2];
|
|
201
207
|
line.appendChild(ts3);
|
|
202
208
|
}
|
|
203
|
-
}
|
|
204
|
-
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
{
|
|
212
|
+
// MAE 9 May 2025 - For improved block text
|
|
213
|
+
if (isFreeText){
|
|
214
|
+
// Fixes issue where blank lines in text blocks didn't take up any vertical
|
|
215
|
+
if (lines[i].trim() == ""){
|
|
216
|
+
line.innerHTML = " ";
|
|
217
|
+
}
|
|
218
|
+
else{
|
|
219
|
+
line.textContent = lines[i];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else{
|
|
223
|
+
line.textContent = lines[i];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
205
226
|
el.appendChild(line);
|
|
206
227
|
}
|
|
207
228
|
if (target)
|
package/types/index.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ declare module 'abcjs' {
|
|
|
61
61
|
|
|
62
62
|
export type NoteHeadType = 'normal' | 'harmonic' | 'rhythm' | 'x' | 'triangle';
|
|
63
63
|
|
|
64
|
-
export type Decorations = "trill" | "lowermordent" | "uppermordent" | "mordent" | "pralltriller" | "accent" |
|
|
64
|
+
export type Decorations = "trill" | "trillh" | "lowermordent" | "uppermordent" | "mordent" | "pralltriller" | "accent" |
|
|
65
65
|
"fermata" | "invertedfermata" | "tenuto" | "0" | "1" | "2" | "3" | "4" | "5" | "+" | "wedge" |
|
|
66
66
|
"open" | "thumb" | "snap" | "turn" | "roll" | "irishroll" | "breath" | "shortphrase" | "mediumphrase" | "longphrase" |
|
|
67
67
|
"segno" | "coda" | "D.S." | "D.C." | "fine" | "crescendo(" | "crescendo)" | "diminuendo(" | "diminuendo)" |"glissando(" | "glissando)" |
|
package/version.js
CHANGED