abcjs 6.4.1 → 6.4.3
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/LICENSE.md +1 -1
- package/RELEASE.md +46 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic-min.js.LICENSE +1 -1
- package/dist/abcjs-basic.js +1271 -1179
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/dist/abcjs-plugin-min.js.LICENSE +1 -1
- package/index.js +1 -1
- package/license.js +1 -1
- package/package.json +1 -1
- package/plugin.js +1 -1
- package/src/api/abc_tunebook.js +1 -2
- package/src/api/abc_tunebook_svg.js +0 -1
- package/src/data/abc_tune.js +2 -0
- package/src/midi/abc_midi_create.js +22 -7
- package/src/parse/abc_common.js +3 -11
- package/src/parse/abc_parse.js +1 -1
- package/src/parse/abc_parse_directive.js +44 -3
- package/src/parse/abc_parse_header.js +6 -4
- package/src/parse/abc_parse_key_voice.js +10 -6
- package/src/parse/abc_parse_music.js +54 -22
- package/src/parse/tune-builder.js +675 -643
- package/src/synth/abc_midi_flattener.js +3 -1
- package/src/synth/abc_midi_sequencer.js +18 -3
- package/src/synth/chord-track.js +90 -18
- package/src/synth/create-synth-control.js +1 -2
- package/src/tablatures/abc_tablatures.js +184 -0
- package/src/tablatures/instruments/string-patterns.js +266 -268
- package/src/tablatures/instruments/string-tablature.js +38 -35
- package/src/tablatures/instruments/tab-note.js +186 -181
- package/src/tablatures/instruments/tab-notes.js +30 -35
- package/src/tablatures/instruments/tab-string.js +43 -25
- package/src/tablatures/render/tab-absolute-elements.js +303 -0
- package/src/tablatures/render/tab-renderer.js +244 -0
- package/src/test/abc_parser_lint.js +2 -3
- package/src/write/creation/abstract-engraver.js +1 -1
- package/src/write/creation/elements/tie-element.js +26 -0
- package/src/write/engraver-controller.js +1 -1
- package/src/write/layout/set-upper-and-lower-elements.js +8 -0
- package/test.js +1 -1
- package/types/index.d.ts +2 -2
- package/version.js +1 -1
- package/src/api/abc_tablatures.js +0 -184
- package/src/tablatures/instruments/tab-string-patterns.js +0 -23
- package/src/tablatures/tab-absolute-elements.js +0 -301
- package/src/tablatures/tab-common.js +0 -29
- package/src/tablatures/tab-renderer.js +0 -259
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tablature Absolute elements factory
|
|
3
|
+
*/
|
|
4
|
+
var AbsoluteElement = require('../../write/creation/elements/absolute-element');
|
|
5
|
+
var RelativeElement = require('../../write/creation/elements/relative-element');
|
|
6
|
+
|
|
7
|
+
function isObject(a) { return a != null && a.constructor === Object; }
|
|
8
|
+
function cloneObject(dest, src) {
|
|
9
|
+
for (var prop in src) {
|
|
10
|
+
if (src.hasOwnProperty(prop)) {
|
|
11
|
+
if (!(Array.isArray(src[prop]) || isObject(src[prop]))) {
|
|
12
|
+
dest[prop] = src[prop];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cloneAbsolute(absSrc) {
|
|
19
|
+
var returned = new AbsoluteElement('', 0, 0, '', 0);
|
|
20
|
+
cloneObject(returned, absSrc);
|
|
21
|
+
returned.top = 0;
|
|
22
|
+
returned.bottom = -1;
|
|
23
|
+
if (absSrc.abcelem) {
|
|
24
|
+
returned.abcelem = {};
|
|
25
|
+
cloneObject(returned.abcelem, absSrc.abcelem);
|
|
26
|
+
if (returned.abcelem.el_type === "note")
|
|
27
|
+
returned.abcelem.el_type = 'tabNumber';
|
|
28
|
+
}
|
|
29
|
+
// TODO-PER: This fixes the classes because the element isn't created at the right time.
|
|
30
|
+
absSrc.cloned = returned
|
|
31
|
+
return returned;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cloneAbsoluteAndRelatives(absSrc, plugin) {
|
|
35
|
+
var returned = cloneAbsolute(absSrc);
|
|
36
|
+
if (plugin) {
|
|
37
|
+
var children = absSrc.children;
|
|
38
|
+
// proceed with relative as well
|
|
39
|
+
var first = true;
|
|
40
|
+
for (var ii = 0; ii < children.length; ii++) {
|
|
41
|
+
var child = children[ii];
|
|
42
|
+
var relative = new RelativeElement('', 0, 0, 0, '');
|
|
43
|
+
cloneObject(relative, child);
|
|
44
|
+
first = plugin.tablature.setRelative(child, relative, first);
|
|
45
|
+
returned.children.push(relative);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return returned;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildTabAbsolute(plugin, absX, relX) {
|
|
52
|
+
var tabIcon = 'tab.tiny';
|
|
53
|
+
var tabYPos = 7.5;
|
|
54
|
+
if (plugin.isTabBig) {
|
|
55
|
+
tabIcon = 'tab.big';
|
|
56
|
+
tabYPos = 10;
|
|
57
|
+
}
|
|
58
|
+
var element = {
|
|
59
|
+
el_type: "tab",
|
|
60
|
+
icon: tabIcon,
|
|
61
|
+
Ypos: tabYPos
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Offset the TAB symbol position if specified in the tab description
|
|
65
|
+
tabYPos += plugin.tabSymbolOffset;
|
|
66
|
+
|
|
67
|
+
// For tablature like whistle tab where you want the TAB symbol hidden
|
|
68
|
+
if (!plugin.hideTabSymbol) {
|
|
69
|
+
|
|
70
|
+
var tabAbsolute = new AbsoluteElement(element, 0, 0, "symbol", 0);
|
|
71
|
+
tabAbsolute.x = absX;
|
|
72
|
+
var tabRelative = new RelativeElement(tabIcon, 0, 0, 7.5, "tab");
|
|
73
|
+
tabRelative.x = relX;
|
|
74
|
+
tabAbsolute.children.push(tabRelative);
|
|
75
|
+
if (tabAbsolute.abcelem.el_type == 'tab') {
|
|
76
|
+
tabRelative.pitch = tabYPos;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
return tabAbsolute;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function lyricsDim(abs) {
|
|
84
|
+
if (abs.extra) {
|
|
85
|
+
for (var ii = 0; ii < abs.extra.length; ii++) {
|
|
86
|
+
var extra = abs.extra[ii];
|
|
87
|
+
if (extra.type == 'lyric') {
|
|
88
|
+
return {
|
|
89
|
+
bottom: extra.bottom,
|
|
90
|
+
height: extra.height
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function TabAbsoluteElements() {
|
|
98
|
+
//console.log("RENDER TabAbsoluteElements constructor")
|
|
99
|
+
this.accidentals = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getInitialStaffSize(staffGroup) {
|
|
103
|
+
var returned = 0;
|
|
104
|
+
for (var ii = 0; ii < staffGroup.length; ii++) {
|
|
105
|
+
if (!staffGroup[ii].tabNameInfos) returned++;
|
|
106
|
+
}
|
|
107
|
+
return returned;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildRelativeTabNote(plugin, relX, def, curNote, isGrace) {
|
|
111
|
+
var strNote = curNote.num;
|
|
112
|
+
if (curNote.note.quarter != null) {
|
|
113
|
+
// add tab quarter => needs to string conversion then
|
|
114
|
+
strNote = strNote.toString();
|
|
115
|
+
strNote += curNote.note.quarter;
|
|
116
|
+
}
|
|
117
|
+
var pitch = plugin.semantics.stringToPitch(curNote.str);
|
|
118
|
+
def.notes.push({ num: strNote, str: curNote.str, pitch: curNote.note.emit() });
|
|
119
|
+
var opt = {
|
|
120
|
+
type: 'tabNumber'
|
|
121
|
+
};
|
|
122
|
+
var tabNoteRelative = new RelativeElement(
|
|
123
|
+
strNote, 0, 0, pitch + 0.3, opt);
|
|
124
|
+
tabNoteRelative.x = relX;
|
|
125
|
+
tabNoteRelative.isGrace = isGrace;
|
|
126
|
+
tabNoteRelative.isAltered = curNote.note.isAltered;
|
|
127
|
+
return tabNoteRelative;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getXGrace(abs, index) {
|
|
131
|
+
var found = 0;
|
|
132
|
+
if (abs.extra) {
|
|
133
|
+
for (var ii = 0; ii < abs.extra.length; ii++) {
|
|
134
|
+
if (abs.extra[ii].c.indexOf('noteheads') >= 0) {
|
|
135
|
+
if (found === index) {
|
|
136
|
+
return abs.extra[ii].x + abs.extra[ii].w / 2;
|
|
137
|
+
} else {
|
|
138
|
+
found++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return -1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function graceInRest(absElem) {
|
|
147
|
+
if (absElem.abcelem) {
|
|
148
|
+
var elem = absElem.abcelem;
|
|
149
|
+
if (elem.rest) {
|
|
150
|
+
return elem.gracenotes;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function convertToNumber(plugin, pitches, graceNotes) {
|
|
157
|
+
var tabPos = plugin.semantics.notesToNumber(pitches, graceNotes);
|
|
158
|
+
if (tabPos.error) {
|
|
159
|
+
plugin.setError(tabPos.error);
|
|
160
|
+
return tabPos; // give up on error here
|
|
161
|
+
}
|
|
162
|
+
if (tabPos.graces && tabPos.notes) {
|
|
163
|
+
// add graces to last note in notes
|
|
164
|
+
var posNote = tabPos.notes.length - 1;
|
|
165
|
+
tabPos.notes[posNote].graces = tabPos.graces;
|
|
166
|
+
}
|
|
167
|
+
return tabPos;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildGraceRelativesForRest(plugin, abs, absChild, graceNotes, tabVoice) {
|
|
171
|
+
for (var mm = 0; mm < graceNotes.length; mm++) {
|
|
172
|
+
var defGrace = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [], grace: true };
|
|
173
|
+
var graceX = getXGrace(absChild, mm);
|
|
174
|
+
var curGrace = graceNotes[mm];
|
|
175
|
+
var tabGraceRelative = buildRelativeTabNote(plugin, graceX, defGrace, curGrace, true);
|
|
176
|
+
abs.children.push(tabGraceRelative);
|
|
177
|
+
tabVoice.push(defGrace);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Build tab absolutes by scanning current staff line absolute array
|
|
183
|
+
* @param {*} staffAbsolute
|
|
184
|
+
*/
|
|
185
|
+
TabAbsoluteElements.prototype.build = function (plugin,
|
|
186
|
+
staffAbsolute,
|
|
187
|
+
tabVoice,
|
|
188
|
+
voiceIndex,
|
|
189
|
+
staffIndex,
|
|
190
|
+
keySig,
|
|
191
|
+
tabVoiceIndex) {
|
|
192
|
+
//console.log("RENDER TabAbsoluteElements build")
|
|
193
|
+
var staffSize = getInitialStaffSize(staffAbsolute);
|
|
194
|
+
var source = staffAbsolute[staffIndex + voiceIndex];
|
|
195
|
+
var dest = staffAbsolute[tabVoiceIndex];
|
|
196
|
+
var tabPos = null;
|
|
197
|
+
var defNote = null;
|
|
198
|
+
if (source.children[0].abcelem.el_type != 'clef') {
|
|
199
|
+
// keysig missing => provide one for tabs
|
|
200
|
+
if (keySig != 'none') {
|
|
201
|
+
source.children.splice(0, 0, keySig);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (var ii = 0; ii < source.children.length; ii++) {
|
|
205
|
+
var absChild = source.children[ii];
|
|
206
|
+
var absX = absChild.x;
|
|
207
|
+
var relX = absX;
|
|
208
|
+
// if (absChild.children.length > 0) {
|
|
209
|
+
// relX = absChild.children[0].x;
|
|
210
|
+
// }
|
|
211
|
+
if ((absChild.isClef)) {
|
|
212
|
+
dest.children.push(buildTabAbsolute(plugin, absX, relX));
|
|
213
|
+
if (absChild.abcelem.type.indexOf('-8') >= 0) plugin.semantics.clefTranspose = -12
|
|
214
|
+
if (absChild.abcelem.type.indexOf('+8') >= 0) plugin.semantics.clefTranspose = 12
|
|
215
|
+
}
|
|
216
|
+
switch (absChild.type) {
|
|
217
|
+
case 'staff-extra key-signature':
|
|
218
|
+
// refresh key accidentals
|
|
219
|
+
this.accidentals = absChild.abcelem.accidentals;
|
|
220
|
+
plugin.semantics.accidentals = this.accidentals;
|
|
221
|
+
break;
|
|
222
|
+
case 'bar':
|
|
223
|
+
plugin.semantics.measureAccidentals = {}
|
|
224
|
+
var lastBar = false;
|
|
225
|
+
if (ii === source.children.length - 1) {
|
|
226
|
+
// used for final line bar drawing
|
|
227
|
+
// for multi tabs / multi staves
|
|
228
|
+
lastBar = true;
|
|
229
|
+
}
|
|
230
|
+
var cloned = cloneAbsoluteAndRelatives(absChild, plugin);
|
|
231
|
+
if (cloned.abcelem.barNumber) {
|
|
232
|
+
delete cloned.abcelem.barNumber;
|
|
233
|
+
for (var bn = 0; bn < cloned.children.length; bn++) {
|
|
234
|
+
if (cloned.children[bn].type === "barNumber") {
|
|
235
|
+
cloned.children.splice(bn, 1);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
cloned.abcelem.lastBar = lastBar;
|
|
241
|
+
dest.children.push(cloned);
|
|
242
|
+
tabVoice.push({
|
|
243
|
+
el_type: absChild.abcelem.el_type,
|
|
244
|
+
type: absChild.abcelem.type,
|
|
245
|
+
endChar: absChild.abcelem.endChar,
|
|
246
|
+
startChar: absChild.abcelem.startChar,
|
|
247
|
+
abselem: cloned
|
|
248
|
+
});
|
|
249
|
+
break;
|
|
250
|
+
case 'rest':
|
|
251
|
+
var restGraces = graceInRest(absChild);
|
|
252
|
+
if (restGraces) {
|
|
253
|
+
// to number conversion
|
|
254
|
+
tabPos = convertToNumber(plugin, null, restGraces);
|
|
255
|
+
if (tabPos.error) return;
|
|
256
|
+
// build relative for grace
|
|
257
|
+
defGrace = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [], grace: true };
|
|
258
|
+
buildGraceRelativesForRest(plugin, abs, absChild, tabPos.graces, tabVoice);
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
case 'note':
|
|
262
|
+
var abs = cloneAbsolute(absChild);
|
|
263
|
+
abs.x = absChild.heads[0].x + absChild.heads[0].w / 2; // center the number
|
|
264
|
+
abs.lyricDim = lyricsDim(absChild);
|
|
265
|
+
var pitches = absChild.abcelem.pitches;
|
|
266
|
+
var graceNotes = absChild.abcelem.gracenotes;
|
|
267
|
+
abs.type = 'tabNumber';
|
|
268
|
+
// to number conversion
|
|
269
|
+
tabPos = convertToNumber(plugin, pitches, graceNotes);
|
|
270
|
+
if (tabPos.error) return;
|
|
271
|
+
if (tabPos.graces) {
|
|
272
|
+
// add graces to last note in notes
|
|
273
|
+
var posNote = tabPos.notes.length - 1;
|
|
274
|
+
tabPos.notes[posNote].graces = tabPos.graces;
|
|
275
|
+
}
|
|
276
|
+
// build relative
|
|
277
|
+
defNote = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [] };
|
|
278
|
+
for (var ll = 0; ll < tabPos.notes.length; ll++) {
|
|
279
|
+
var curNote = tabPos.notes[ll];
|
|
280
|
+
if (curNote.graces) {
|
|
281
|
+
for (var mm = 0; mm < curNote.graces.length; mm++) {
|
|
282
|
+
var defGrace = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [], grace: true };
|
|
283
|
+
var graceX = getXGrace(absChild, mm);
|
|
284
|
+
var curGrace = curNote.graces[mm];
|
|
285
|
+
var tabGraceRelative = buildRelativeTabNote(plugin, graceX, defGrace, curGrace, true);
|
|
286
|
+
abs.children.push(tabGraceRelative);
|
|
287
|
+
tabVoice.push(defGrace);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
var tabNoteRelative = buildRelativeTabNote(plugin, abs.x + absChild.heads[ll].dx, defNote, curNote, false);
|
|
291
|
+
abs.children.push(tabNoteRelative);
|
|
292
|
+
}
|
|
293
|
+
if (defNote.notes.length > 0) {
|
|
294
|
+
defNote.abselem = abs;
|
|
295
|
+
tabVoice.push(defNote);
|
|
296
|
+
dest.children.push(abs);
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
module.exports = TabAbsoluteElements;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/* eslint-disable no-debugger */
|
|
2
|
+
var VoiceElement = require('../../write/creation/elements/voice-element');
|
|
3
|
+
var TabAbsoluteElements = require('./tab-absolute-elements');
|
|
4
|
+
var spacing = require('../../write/helpers/spacing');
|
|
5
|
+
|
|
6
|
+
function initSpecialY() {
|
|
7
|
+
return {
|
|
8
|
+
tempoHeightAbove: 0,
|
|
9
|
+
partHeightAbove: 0,
|
|
10
|
+
volumeHeightAbove: 0,
|
|
11
|
+
dynamicHeightAbove: 0,
|
|
12
|
+
endingHeightAbove: 0,
|
|
13
|
+
chordHeightAbove: 0,
|
|
14
|
+
lyricHeightAbove: 0,
|
|
15
|
+
lyricHeightBelow: 0,
|
|
16
|
+
chordHeightBelow: 0,
|
|
17
|
+
volumeHeightBelow: 0,
|
|
18
|
+
dynamicHeightBelow: 0
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getLyricHeight(voice) {
|
|
23
|
+
var maxLyricHeight = 0;
|
|
24
|
+
for (var ii = 0; ii < voice.children.length; ii++) {
|
|
25
|
+
var curAbs = voice.children[ii];
|
|
26
|
+
if (curAbs.specialY) {
|
|
27
|
+
if (curAbs.specialY.lyricHeightBelow > maxLyricHeight) {
|
|
28
|
+
maxLyricHeight = curAbs.specialY.lyricHeightBelow;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return maxLyricHeight; // add spacing
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildTabName(plugin, renderer, dest) {
|
|
36
|
+
var stringSemantics = plugin.semantics;
|
|
37
|
+
var textSize = renderer.controller.getTextSize;
|
|
38
|
+
var tabName = stringSemantics.tabInfos(plugin);
|
|
39
|
+
var suppress = stringSemantics.suppress(plugin);
|
|
40
|
+
var doDraw = true;
|
|
41
|
+
|
|
42
|
+
if (suppress) {
|
|
43
|
+
doDraw = false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if (doDraw) {
|
|
48
|
+
var size = textSize.calc(tabName, 'tablabelfont', 'text instrumentname');
|
|
49
|
+
dest.tabNameInfos = {
|
|
50
|
+
textSize: { height: size.height, width: size.width },
|
|
51
|
+
name: tabName
|
|
52
|
+
};
|
|
53
|
+
return size.height;
|
|
54
|
+
}
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function islastTabInStaff(index, staffGroup) {
|
|
60
|
+
if (staffGroup[index].isTabStaff) {
|
|
61
|
+
if (index === staffGroup.length - 1) return true;
|
|
62
|
+
if (staffGroup[index + 1].isTabStaff) {
|
|
63
|
+
return false;
|
|
64
|
+
} else {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getStaffNumbers(staffs) {
|
|
72
|
+
var nbStaffs = 0;
|
|
73
|
+
for (var ii = 0; ii < staffs.length; ii++) {
|
|
74
|
+
if (!staffs[ii].isTabStaff) {
|
|
75
|
+
nbStaffs++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return nbStaffs;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getParentStaffIndex(staffs, index) {
|
|
82
|
+
for (var ii = index; ii >= 0; ii--) {
|
|
83
|
+
if (!staffs[ii].isTabStaff) {
|
|
84
|
+
return ii;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return -1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
function linkStaffAndTabs(staffs) {
|
|
92
|
+
for (var ii = 0; ii < staffs.length; ii++) {
|
|
93
|
+
if (staffs[ii].isTabStaff) {
|
|
94
|
+
// link to parent staff
|
|
95
|
+
var parentIndex = getParentStaffIndex(staffs, ii);
|
|
96
|
+
staffs[ii].hasStaff = staffs[parentIndex];
|
|
97
|
+
if (!staffs[parentIndex].hasTab) staffs[parentIndex].hasTab = [];
|
|
98
|
+
staffs[parentIndex].hasTab.push(staffs[ii]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isMultiVoiceSingleStaff(staffs, parent) {
|
|
104
|
+
if (getStaffNumbers(staffs) === 1) {
|
|
105
|
+
if (parent.voices.length > 1) return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
function getNextTabPos(tabIndex, staffGroup) {
|
|
112
|
+
var startIndex = 0;
|
|
113
|
+
var handledVoices = 0;
|
|
114
|
+
var inProgress = true;
|
|
115
|
+
var nbVoices = 0;
|
|
116
|
+
while (inProgress) {
|
|
117
|
+
//for (var ii = 0; ii < staffGroup.length; ii++) {
|
|
118
|
+
if (!staffGroup[startIndex])
|
|
119
|
+
return -1;
|
|
120
|
+
if (!staffGroup[startIndex].isTabStaff) {
|
|
121
|
+
nbVoices = staffGroup[startIndex].voices.length; // get number of staff voices
|
|
122
|
+
}
|
|
123
|
+
if (staffGroup[startIndex].isTabStaff) {
|
|
124
|
+
handledVoices++;
|
|
125
|
+
if (islastTabInStaff(startIndex, staffGroup)) {
|
|
126
|
+
if (handledVoices < nbVoices) return startIndex + 1;
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
handledVoices = 0;
|
|
130
|
+
if (startIndex >= tabIndex) {
|
|
131
|
+
if (startIndex + 1 == staffGroup.length) return startIndex + 1;
|
|
132
|
+
if (!staffGroup[startIndex + 1].isTabStaff) return startIndex + 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
startIndex++;
|
|
136
|
+
// out of space case
|
|
137
|
+
if (startIndex > staffGroup.length) return -1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getLastStaff(staffs, lastTab) {
|
|
142
|
+
for (var ii = lastTab; ii >= 0; ii--) {
|
|
143
|
+
if (!staffs[ii].isTabStaff) {
|
|
144
|
+
return staffs[ii];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function checkVoiceKeySig(voices, ii) {
|
|
151
|
+
var curVoice = voices[ii];
|
|
152
|
+
// on multivoice multistaff only the first voice has key signature
|
|
153
|
+
// folling consecutive do not have one => we should provide the first voice key sig back then
|
|
154
|
+
var elem0 = curVoice.children[0].abcelem;
|
|
155
|
+
if (elem0.el_type === 'clef') return null;
|
|
156
|
+
if (ii == 0) {
|
|
157
|
+
// not found => clef=none case
|
|
158
|
+
return 'none';
|
|
159
|
+
}
|
|
160
|
+
return voices[ii - 1].children[0];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function tabRenderer(plugin, renderer, line, staffIndex) {
|
|
164
|
+
//console.log("RENDER tabRenderer")
|
|
165
|
+
var absolutes = new TabAbsoluteElements();
|
|
166
|
+
var tabStaff = {
|
|
167
|
+
clef: {
|
|
168
|
+
type: 'TAB'
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var tabSize = (plugin.linePitch * plugin.nbLines);
|
|
172
|
+
var staffs = line.staff;
|
|
173
|
+
if (staffs) {
|
|
174
|
+
// give up on staffline=0 in key
|
|
175
|
+
var firstStaff = staffs[0];
|
|
176
|
+
if (firstStaff) {
|
|
177
|
+
if (firstStaff.clef) {
|
|
178
|
+
if (firstStaff.clef.stafflines == 0) {
|
|
179
|
+
plugin.setError("No tablatures when stafflines=0");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
staffs.splice(
|
|
185
|
+
staffs.length, 0,
|
|
186
|
+
tabStaff
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
var staffGroup = line.staffGroup;
|
|
190
|
+
|
|
191
|
+
var voices = staffGroup.voices;
|
|
192
|
+
var firstVoice = voices[0];
|
|
193
|
+
// take lyrics into account if any
|
|
194
|
+
var lyricsHeight = getLyricHeight(firstVoice);
|
|
195
|
+
var padd = 3;
|
|
196
|
+
var prevIndex = staffIndex;
|
|
197
|
+
var previousStaff = staffGroup.staffs[prevIndex];
|
|
198
|
+
var tabTop = tabSize + padd - previousStaff.bottom - lyricsHeight;
|
|
199
|
+
if (previousStaff.isTabStaff) {
|
|
200
|
+
tabTop = previousStaff.top;
|
|
201
|
+
}
|
|
202
|
+
var staffGroupInfos = {
|
|
203
|
+
bottom: -1,
|
|
204
|
+
isTabStaff: true,
|
|
205
|
+
specialY: initSpecialY(),
|
|
206
|
+
lines: plugin.nbLines,
|
|
207
|
+
linePitch: plugin.linePitch,
|
|
208
|
+
dy: 0.15,
|
|
209
|
+
top: tabTop,
|
|
210
|
+
};
|
|
211
|
+
var nextTabPos = getNextTabPos(staffIndex, staffGroup.staffs);
|
|
212
|
+
if (nextTabPos === -1)
|
|
213
|
+
return;
|
|
214
|
+
staffGroupInfos.parentIndex = nextTabPos - 1;
|
|
215
|
+
staffGroup.staffs.splice(nextTabPos, 0, staffGroupInfos);
|
|
216
|
+
// staffGroup.staffs.push(staffGroupInfos);
|
|
217
|
+
staffGroup.height += tabSize + padd;
|
|
218
|
+
var parentStaff = getLastStaff(staffGroup.staffs, nextTabPos);
|
|
219
|
+
var nbVoices = 1;
|
|
220
|
+
if (isMultiVoiceSingleStaff(staffGroup.staffs, parentStaff)) {
|
|
221
|
+
nbVoices = parentStaff.voices.length;
|
|
222
|
+
}
|
|
223
|
+
// build from staff
|
|
224
|
+
tabStaff.voices = [];
|
|
225
|
+
for (var ii = 0; ii < nbVoices; ii++) {
|
|
226
|
+
var tabVoice = new VoiceElement(0, 0);
|
|
227
|
+
if (ii > 0) tabVoice.duplicate = true;
|
|
228
|
+
var nameHeight = buildTabName(plugin, renderer, tabVoice) / spacing.STEP;
|
|
229
|
+
nameHeight = Math.max(nameHeight, 1) // If there is no label for the tab line, then there needs to be a little padding
|
|
230
|
+
// This was pushing down the top staff by the tab label height
|
|
231
|
+
//staffGroup.staffs[staffIndex].top += nameHeight;
|
|
232
|
+
staffGroup.staffs[staffIndex].top += 1;
|
|
233
|
+
staffGroup.height += nameHeight;
|
|
234
|
+
tabVoice.staff = staffGroupInfos;
|
|
235
|
+
var tabVoiceIndex = voices.length
|
|
236
|
+
voices.splice(voices.length, 0, tabVoice);
|
|
237
|
+
var keySig = checkVoiceKeySig(voices, ii + staffIndex);
|
|
238
|
+
tabStaff.voices[ii] = [];
|
|
239
|
+
absolutes.build(plugin, voices, tabStaff.voices[ii], ii, staffIndex, keySig, tabVoiceIndex);
|
|
240
|
+
}
|
|
241
|
+
linkStaffAndTabs(staffGroup.staffs); // crossreference tabs and staff
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = tabRenderer;
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
// Expanded:
|
|
49
49
|
// MIDI is now { cmd, param }
|
|
50
50
|
|
|
51
|
-
var parseCommon = require('../parse/abc_common');
|
|
52
51
|
var JSONSchema = require('./jsonschema-b4');
|
|
53
52
|
|
|
54
53
|
var ParserLint = function() {
|
|
@@ -76,7 +75,7 @@ var ParserLint = function() {
|
|
|
76
75
|
};
|
|
77
76
|
|
|
78
77
|
var appendPositioning = function(properties) {
|
|
79
|
-
var ret =
|
|
78
|
+
var ret = Object.assign({},properties);
|
|
80
79
|
ret.startChar = { type: 'number' }; //, output: 'hidden' };
|
|
81
80
|
ret.endChar = { type: 'number' }; //, output: 'hidden' };
|
|
82
81
|
return ret;
|
|
@@ -580,7 +579,7 @@ var ParserLint = function() {
|
|
|
580
579
|
};
|
|
581
580
|
|
|
582
581
|
var addProhibits = function(obj, arr) {
|
|
583
|
-
var ret =
|
|
582
|
+
var ret = Object.assign({},obj);
|
|
584
583
|
ret.prohibits = arr;
|
|
585
584
|
return ret;
|
|
586
585
|
};
|
|
@@ -833,7 +833,7 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
|
|
|
833
833
|
if (elem.decoration) {
|
|
834
834
|
// TODO-PER: nostem is true if this is beamed. In that case we don't know where to place the decoration yet so just make a guess. This should be refactored to not place decorations until after the beams are determined.
|
|
835
835
|
// This should probably be combined with moveDecorations()
|
|
836
|
-
var bottom = nostem ? Math.min(-3, abselem.bottom - 6) : abselem.bottom
|
|
836
|
+
var bottom = nostem && dir !== 'up' ? Math.min(-3, abselem.bottom - 6) : abselem.bottom
|
|
837
837
|
this.decoration.createDecoration(voice, elem.decoration, abselem.top, (notehead) ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove);
|
|
838
838
|
}
|
|
839
839
|
|
|
@@ -225,4 +225,30 @@ TieElem.prototype.avoidCollisionAbove = function () {
|
|
|
225
225
|
}
|
|
226
226
|
};
|
|
227
227
|
|
|
228
|
+
TieElem.prototype.getYBounds = function () {
|
|
229
|
+
var lineStartX = 10 // TODO-PER: I'm not sure where to get this number from but it probably doesn't matter much
|
|
230
|
+
var lineEndX = 1000 // TODO-PER: I'm not sure where to get this number from but it probably doesn't matter much
|
|
231
|
+
if (this.isTie) {
|
|
232
|
+
this.calcTieDirection();
|
|
233
|
+
this.calcX(lineStartX, lineEndX);
|
|
234
|
+
this.calcTieY();
|
|
235
|
+
|
|
236
|
+
} else {
|
|
237
|
+
this.calcSlurDirection();
|
|
238
|
+
this.calcX(lineStartX, lineEndX);
|
|
239
|
+
this.calcSlurY();
|
|
240
|
+
}
|
|
241
|
+
var top;
|
|
242
|
+
var bottom;
|
|
243
|
+
// TODO-PER: It's hard to tell how far the arc is, so I'm just using 3 as the max
|
|
244
|
+
if (this.above) {
|
|
245
|
+
bottom = Math.min(this.startY, this.endY)
|
|
246
|
+
top = bottom + 3
|
|
247
|
+
} else {
|
|
248
|
+
top = Math.min(this.startY, this.endY)
|
|
249
|
+
bottom = top - 3
|
|
250
|
+
}
|
|
251
|
+
return [ top, bottom ]
|
|
252
|
+
};
|
|
253
|
+
|
|
228
254
|
module.exports = TieElem;
|
|
@@ -16,7 +16,7 @@ var Classes = require('./helpers/classes');
|
|
|
16
16
|
var GetFontAndAttr = require('./helpers/get-font-and-attr');
|
|
17
17
|
var GetTextSize = require('./helpers/get-text-size');
|
|
18
18
|
var draw = require('./draw/draw');
|
|
19
|
-
var tablatures = require('../
|
|
19
|
+
var tablatures = require('../tablatures/abc_tablatures');
|
|
20
20
|
var findSelectableElement = require('./interactive/find-selectable-element');
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -128,6 +128,14 @@ function setUpperAndLowerVoiceElements(positionY, voice, spacing) {
|
|
|
128
128
|
case 'EndingElem':
|
|
129
129
|
setUpperAndLowerEndingElements(positionY, abselem);
|
|
130
130
|
break;
|
|
131
|
+
case 'TieElem':
|
|
132
|
+
// If a tie element is the highest or lowest thing then space might need to make room for it.
|
|
133
|
+
var yBounds = abselem.getYBounds()
|
|
134
|
+
voice.staff.top = Math.max(voice.staff.top, yBounds[0])
|
|
135
|
+
voice.staff.top = Math.max(voice.staff.top, yBounds[1])
|
|
136
|
+
voice.staff.bottom = Math.min(voice.staff.bottom, yBounds[0])
|
|
137
|
+
voice.staff.bottom = Math.min(voice.staff.bottom, yBounds[1])
|
|
138
|
+
break;
|
|
131
139
|
}
|
|
132
140
|
}
|
|
133
141
|
}
|
package/test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**!
|
|
2
|
-
Copyright (c) 2009-
|
|
2
|
+
Copyright (c) 2009-2024 Paul Rosen and Gregory Dyke
|
|
3
3
|
|
|
4
4
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
5
|
of this software and associated documentation files (the "Software"), to deal
|
package/types/index.d.ts
CHANGED
|
@@ -379,7 +379,7 @@ declare module 'abcjs' {
|
|
|
379
379
|
chordprog?: number;
|
|
380
380
|
chordvol?: number;
|
|
381
381
|
gchord?: string;
|
|
382
|
-
}
|
|
382
|
+
}
|
|
383
383
|
|
|
384
384
|
export interface SynthVisualOptions {
|
|
385
385
|
displayLoop?: boolean;
|
|
@@ -886,7 +886,7 @@ declare module 'abcjs' {
|
|
|
886
886
|
getBarLength: NumberFunction;
|
|
887
887
|
getBeatLength: NumberFunction;
|
|
888
888
|
getBeatsPerMeasure: NumberFunction;
|
|
889
|
-
getBpm:
|
|
889
|
+
getBpm: (tempo?:TempoProperties) => number;
|
|
890
890
|
getMeter: () => Meter;
|
|
891
891
|
getMeterFraction: () => MeterFraction;
|
|
892
892
|
getPickupLength: NumberFunction;
|
package/version.js
CHANGED