abcjs 6.6.1 → 6.6.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/RELEASE.md +20 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +275 -36
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/package.json +1 -1
- package/src/write/creation/abstract-engraver.js +5 -4
- package/src/write/creation/create-note-head.js +6 -5
- package/src/write/creation/elements/relative-element.js +3 -0
- package/src/write/draw/absolute.js +6 -0
- package/src/write/draw/triplet.js +3 -3
- package/src/write/layout/beam.js +40 -10
- package/src/write/layout/layout.js +90 -0
- package/src/write/layout/set-upper-and-lower-elements.js +18 -2
- package/src/write/layout/to-time-and-staff-based.js +35 -0
- package/src/write/layout/triplet.js +61 -14
- package/version.js +1 -1
package/package.json
CHANGED
|
@@ -718,8 +718,9 @@ AbstractEngraver.prototype.addNoteToAbcElement = function (abselem, elem, dot, s
|
|
|
718
718
|
}
|
|
719
719
|
|
|
720
720
|
var hasStem = !nostem && durlog <= -1;
|
|
721
|
+
var chordPos = pp > 1 ? p+1 : null
|
|
721
722
|
var ret = createNoteHead(abselem, c, elem.pitches[p],
|
|
722
|
-
{ dir: dir, extrax: -roomTaken, flag: flag, dot: dot, dotshiftx: dotshiftx, scale: this.voiceScale, accidentalSlot: accidentalSlot, shouldExtendStem: !stemdir, printAccidentals: !voice.isPercussion });
|
|
723
|
+
{ dir: dir, extrax: -roomTaken, flag: flag, dot: dot, dotshiftx: dotshiftx, scale: this.voiceScale, accidentalSlot: accidentalSlot, shouldExtendStem: !stemdir, printAccidentals: !voice.isPercussion, chordPos: chordPos });
|
|
723
724
|
symbolWidth = Math.max(glyphs.getSymbolWidth(c), symbolWidth);
|
|
724
725
|
abselem.extraw -= ret.extraLeft;
|
|
725
726
|
noteHead = ret.notehead;
|
|
@@ -765,7 +766,7 @@ AbstractEngraver.prototype.addNoteToAbcElement = function (abselem, elem, dot, s
|
|
|
765
766
|
return { noteHead: noteHead, roomTaken: roomTaken, roomTakenRight: roomTakenRight, min: min, additionalLedgers: additionalLedgers, dir: dir, symbolWidth: symbolWidth };
|
|
766
767
|
};
|
|
767
768
|
|
|
768
|
-
AbstractEngraver.prototype.addLyric = function (abselem, elem) {
|
|
769
|
+
AbstractEngraver.prototype.addLyric = function (abselem, elem, voiceNumber) {
|
|
769
770
|
var lyricStr = "";
|
|
770
771
|
elem.lyric.forEach(function (ly) {
|
|
771
772
|
var div = ly.divider === ' ' ? "" : ly.divider;
|
|
@@ -773,7 +774,7 @@ AbstractEngraver.prototype.addLyric = function (abselem, elem) {
|
|
|
773
774
|
});
|
|
774
775
|
var lyricDim = this.getTextSize.calc(lyricStr, 'vocalfont', "lyric");
|
|
775
776
|
var position = elem.positioning ? elem.positioning.vocalPosition : 'below';
|
|
776
|
-
abselem.addCentered(new RelativeElement(lyricStr, 0, lyricDim.width, undefined, { type: "lyric", position: position, height: lyricDim.height / spacing.STEP, dim: this.getTextSize.attr('vocalfont', "lyric") }));
|
|
777
|
+
abselem.addCentered(new RelativeElement(lyricStr, 0, lyricDim.width, undefined, { type: "lyric", position: position, height: lyricDim.height / spacing.STEP, dim: this.getTextSize.attr('vocalfont', "lyric"), voiceNumber: voiceNumber }));
|
|
777
778
|
};
|
|
778
779
|
|
|
779
780
|
AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaff, voice) { //stem presence: true for drawing stemless notehead
|
|
@@ -827,7 +828,7 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
|
|
|
827
828
|
}
|
|
828
829
|
|
|
829
830
|
if (elem.lyric !== undefined) {
|
|
830
|
-
this.addLyric(abselem, elem);
|
|
831
|
+
this.addLyric(abselem, elem, voice.voicenumber);
|
|
831
832
|
}
|
|
832
833
|
|
|
833
834
|
if (elem.gracenotes !== undefined) {
|
|
@@ -13,6 +13,7 @@ var createNoteHead = function (abselem, c, pitchelem, options) {
|
|
|
13
13
|
var accidentalSlot = (options.accidentalSlot !== undefined) ? options.accidentalSlot : [];
|
|
14
14
|
var shouldExtendStem = (options.shouldExtendStem !== undefined) ? options.shouldExtendStem : false;
|
|
15
15
|
var printAccidentals = (options.printAccidentals !== undefined) ? options.printAccidentals : true;
|
|
16
|
+
var chordPos = options.chordPos
|
|
16
17
|
|
|
17
18
|
// TODO scale the dot as well
|
|
18
19
|
var pitch = pitchelem.verticalPos;
|
|
@@ -23,14 +24,14 @@ var createNoteHead = function (abselem, c, pitchelem, options) {
|
|
|
23
24
|
if (c === undefined)
|
|
24
25
|
abselem.addFixed(new RelativeElement("pitch is undefined", 0, 0, 0, { type: "debug" }));
|
|
25
26
|
else if (c === "") {
|
|
26
|
-
notehead = new RelativeElement(null, 0, 0, pitch);
|
|
27
|
+
notehead = new RelativeElement(null, 0, 0, pitch, {chordPos:chordPos});
|
|
27
28
|
} else {
|
|
28
29
|
var shiftheadx = headx;
|
|
29
30
|
if (pitchelem.printer_shift) {
|
|
30
31
|
var adjust = (pitchelem.printer_shift === "same") ? 1 : 0;
|
|
31
32
|
shiftheadx = (dir === "down") ? -glyphs.getSymbolWidth(c) * scale + adjust : glyphs.getSymbolWidth(c) * scale - adjust;
|
|
32
33
|
}
|
|
33
|
-
var opts = { scalex: scale, scaley: scale, thickness: glyphs.symbolHeightInPitches(c) * scale, name: pitchelem.name };
|
|
34
|
+
var opts = { scalex: scale, scaley: scale, thickness: glyphs.symbolHeightInPitches(c) * scale, name: pitchelem.name, chordPos: chordPos };
|
|
34
35
|
notehead = new RelativeElement(c, shiftheadx, glyphs.getSymbolWidth(c) * scale, pitch, opts);
|
|
35
36
|
notehead.stemDir = dir;
|
|
36
37
|
if (flag) {
|
|
@@ -44,12 +45,12 @@ var createNoteHead = function (abselem, c, pitchelem, options) {
|
|
|
44
45
|
}
|
|
45
46
|
//if (scale===1 && (dir==="down")?(pos>6):(pos<6)) pos=6;
|
|
46
47
|
var xdelta = (dir === "down") ? headx : headx + notehead.w - 0.6;
|
|
47
|
-
abselem.addRight(new RelativeElement(flag, xdelta, glyphs.getSymbolWidth(flag) * scale, pos, { scalex: scale, scaley: scale }));
|
|
48
|
+
abselem.addRight(new RelativeElement(flag, xdelta, glyphs.getSymbolWidth(flag) * scale, pos, { scalex: scale, scaley: scale, chordPos: chordPos }));
|
|
48
49
|
}
|
|
49
50
|
newDotShiftX = notehead.w + dotshiftx - 2 + 5 * dot;
|
|
50
51
|
for (; dot > 0; dot--) {
|
|
51
52
|
var dotadjusty = (1 - Math.abs(pitch) % 2); //PER: take abs value of the pitch. And the shift still happens on ledger lines.
|
|
52
|
-
abselem.addRight(new RelativeElement("dots.dot", notehead.w + dotshiftx - 2 + 5 * dot, glyphs.getSymbolWidth("dots.dot"), pitch + dotadjusty));
|
|
53
|
+
abselem.addRight(new RelativeElement("dots.dot", notehead.w + dotshiftx - 2 + 5 * dot, glyphs.getSymbolWidth("dots.dot"), pitch + dotadjusty, {chordPos:chordPos}));
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
if (notehead)
|
|
@@ -96,7 +97,7 @@ var createNoteHead = function (abselem, c, pitchelem, options) {
|
|
|
96
97
|
accidentalshiftx = (glyphs.getSymbolWidth(symb) * scale + 2);
|
|
97
98
|
}
|
|
98
99
|
var h = glyphs.symbolHeightInPitches(symb);
|
|
99
|
-
abselem.addExtra(new RelativeElement(symb, accPlace, glyphs.getSymbolWidth(symb), pitch, { scalex: scale, scaley: scale, top: pitch + h / 2, bottom: pitch - h / 2 }));
|
|
100
|
+
abselem.addExtra(new RelativeElement(symb, accPlace, glyphs.getSymbolWidth(symb), pitch, { scalex: scale, scaley: scale, top: pitch + h / 2, bottom: pitch - h / 2, chordPos: chordPos }));
|
|
100
101
|
extraLeft = glyphs.getSymbolWidth(symb) / 2; // TODO-PER: We need a little extra width if there is an accidental, but I'm not sure why it isn't the full width of the accidental.
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -13,6 +13,7 @@ var RelativeElement = function RelativeElement(c, dx, w, pitch, opt) {
|
|
|
13
13
|
this.pitch2 = opt.pitch2;
|
|
14
14
|
this.linewidth = opt.linewidth;
|
|
15
15
|
this.klass = opt.klass;
|
|
16
|
+
this.chordPos = opt.chordPos;
|
|
16
17
|
this.anchor = opt.anchor ? opt.anchor : 'middle'
|
|
17
18
|
this.top = pitch;
|
|
18
19
|
if (this.pitch2 !== undefined && this.pitch2 > this.top) this.top = this.pitch2;
|
|
@@ -32,6 +33,8 @@ var RelativeElement = function RelativeElement(c, dx, w, pitch, opt) {
|
|
|
32
33
|
this.dim = opt.dim;
|
|
33
34
|
if (opt.position)
|
|
34
35
|
this.position = opt.position;
|
|
36
|
+
if (opt.voiceNumber !== undefined)
|
|
37
|
+
this.voiceNumber = opt.voiceNumber
|
|
35
38
|
this.height = opt.height ? opt.height : 4; // The +1 is to give a little bit of padding.
|
|
36
39
|
if (opt.top)
|
|
37
40
|
this.top = opt.top;
|
|
@@ -20,6 +20,12 @@ function drawAbsolute(renderer, params, bartop, selectables, staffPos) {
|
|
|
20
20
|
if (child.type === "symbol" && child.c && child.c.indexOf('notehead') >= 0) {
|
|
21
21
|
el.setAttribute('class', 'abcjs-notehead')
|
|
22
22
|
}
|
|
23
|
+
if (el && child.chordPos && child.name.indexOf('flags.') !== 0) {
|
|
24
|
+
var klass = el.getAttribute("class")
|
|
25
|
+
if (klass) klass = klass + ' abcjs-chord-pos-'+child.chordPos
|
|
26
|
+
else klass = 'abcjs-chord-pos-'+child.chordPos
|
|
27
|
+
el.setAttribute('class', klass)
|
|
28
|
+
}
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
var klass = params.type;
|
|
@@ -6,7 +6,7 @@ var roundNumber = require("./round-number");
|
|
|
6
6
|
function drawTriplet(renderer, params, selectables) {
|
|
7
7
|
renderer.paper.openGroup({ klass: renderer.controller.classes.generate('triplet ' + params.durationClass), "data-name": "triplet" });
|
|
8
8
|
if (!params.hasBeam) {
|
|
9
|
-
drawBracket(renderer, params.anchor1.x, params.startNote, params.anchor2.x + params.anchor2.w, params.endNote);
|
|
9
|
+
drawBracket(renderer, params.anchor1.x, params.startNote, params.anchor2.x + params.anchor2.w, params.endNote, params.up);
|
|
10
10
|
}
|
|
11
11
|
// HACK: adjust the position of "3". It is too high in all cases so we fudge it by subtracting 1 here.
|
|
12
12
|
renderText(renderer, { x: params.xTextPos, y: renderer.calcY(params.yTextPos - 1), text: "" + params.number, type: 'tripletfont', anchor: "middle", centerVertically: true, noClass: true, name: "" + params.number }, true);
|
|
@@ -19,10 +19,10 @@ function drawLine(l, t, r, b) {
|
|
|
19
19
|
return sprintf("M %f %f L %f %f", roundNumber(l), roundNumber(t), roundNumber(r), roundNumber(b));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function drawBracket(renderer, x1, y1, x2, y2) {
|
|
22
|
+
function drawBracket(renderer, x1, y1, x2, y2, up) {
|
|
23
23
|
y1 = renderer.calcY(y1);
|
|
24
24
|
y2 = renderer.calcY(y2);
|
|
25
|
-
var bracketHeight = 5;
|
|
25
|
+
var bracketHeight = up ? 5 : -5;
|
|
26
26
|
|
|
27
27
|
// Draw vertical lines at the beginning and end
|
|
28
28
|
var pathString = "";
|
package/src/write/layout/beam.js
CHANGED
|
@@ -143,6 +143,27 @@ function createStems(elems, asc, beam, dy, mainNote) {
|
|
|
143
143
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
|
|
147
|
+
// Helper function to find the next non-rest element in the array
|
|
148
|
+
function findNextNonRest(elems, startIndex) {
|
|
149
|
+
for (var k = startIndex + 1; k < elems.length; k++) {
|
|
150
|
+
if (!elems[k].abcelem.rest) {
|
|
151
|
+
return k;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return -1; // No non-rest element found
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Helper function to find the previous non-rest element in the array
|
|
158
|
+
function findPrevNonRest(elems, startIndex) {
|
|
159
|
+
for (var k = startIndex - 1; k >= 0; k--) {
|
|
160
|
+
if (!elems[k].abcelem.rest) {
|
|
161
|
+
return k;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return -1; // No non-rest element found
|
|
165
|
+
}
|
|
166
|
+
|
|
146
167
|
function createAdditionalBeams(elems, asc, beam, isGrace, dy) {
|
|
147
168
|
var beams = [];
|
|
148
169
|
var auxBeams = []; // auxbeam will be {x, y, durlog, single} auxbeam[0] should match with durlog=-4 (16th) (j=-4-durlog)
|
|
@@ -182,27 +203,36 @@ function createAdditionalBeams(elems, asc, beam, isGrace, dy) {
|
|
|
182
203
|
}
|
|
183
204
|
|
|
184
205
|
for (var j = auxBeams.length - 1; j >= 0; j--) {
|
|
185
|
-
|
|
206
|
+
// Find the next non-rest element to check if we should end the beam
|
|
207
|
+
var nextNonRestIndex = findNextNonRest(elems, i);
|
|
208
|
+
var shouldEndBeam = (nextNonRestIndex === -1) ||
|
|
209
|
+
(nextNonRestIndex < elems.length && getDurlog(elems[nextNonRestIndex].abcelem.duration) > (-j - 4));
|
|
186
210
|
|
|
211
|
+
if (shouldEndBeam) {
|
|
187
212
|
var auxBeamEndX = x;
|
|
188
213
|
var auxBeamEndY = bary + sy * (j + 1);
|
|
189
214
|
|
|
190
|
-
|
|
191
215
|
if (auxBeams[j].single) {
|
|
192
|
-
|
|
216
|
+
var prevNonRestIndex = findPrevNonRest(elems, i);
|
|
217
|
+
var isFirstNote = (prevNonRestIndex === -1);
|
|
218
|
+
var isLastNote = (nextNonRestIndex === -1);
|
|
219
|
+
|
|
220
|
+
if (isFirstNote) {
|
|
193
221
|
// This is the first note in the group, always draw the beam to the right
|
|
194
222
|
auxBeamEndX = x + 5;
|
|
195
|
-
} else if (
|
|
223
|
+
} else if (isLastNote) {
|
|
196
224
|
// This is the last note in the group, always draw the beam to the left
|
|
197
225
|
auxBeamEndX = x - 5;
|
|
198
226
|
} else {
|
|
199
|
-
// This is a middle note, check the note durations of the notes to the left and right
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
// This is a middle note, check the note durations of the notes to the left and right (skipping rests)
|
|
228
|
+
var prevDuration = elems[prevNonRestIndex].abcelem.duration;
|
|
229
|
+
var nextDuration = elems[nextNonRestIndex].abcelem.duration;
|
|
230
|
+
if (prevDuration === nextDuration) {
|
|
231
|
+
// The notes on either side are the same duration, alternate which side the beam goes to
|
|
232
|
+
auxBeamEndX = i%2 === 0 ? x + 5 : x - 5;
|
|
203
233
|
} else {
|
|
204
|
-
|
|
205
|
-
|
|
234
|
+
// The notes on either side are different durations, draw the beam to the longer note
|
|
235
|
+
auxBeamEndX = prevDuration < nextDuration ? x + 5 : x - 5;
|
|
206
236
|
}
|
|
207
237
|
}
|
|
208
238
|
auxBeamEndY = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, auxBeamEndX) + sy * (j + 1);
|
|
@@ -3,6 +3,7 @@ var setUpperAndLowerElements = require('./set-upper-and-lower-elements');
|
|
|
3
3
|
var layoutStaffGroup = require('./staff-group');
|
|
4
4
|
var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
|
|
5
5
|
var layoutInGrid = require('./layout-in-grid');
|
|
6
|
+
var toTimeAndStaffBased = require("./to-time-and-staff-based");
|
|
6
7
|
|
|
7
8
|
// This sets the "x" attribute on all the children in abctune.lines
|
|
8
9
|
// It also sets the "w" and "startx" attributes on "voices"
|
|
@@ -40,6 +41,16 @@ var layout = function (renderer, abctune, width, space, expandToWidest, timeBase
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// See if there are collisions between voices that need to be tweaked
|
|
45
|
+
var timeBased = toTimeAndStaffBased(abctune.lines)
|
|
46
|
+
for (i = 0; i < abctune.lines.length; i++) {
|
|
47
|
+
abcLine = abctune.lines[i];
|
|
48
|
+
if (abcLine.staffGroup) {
|
|
49
|
+
fixVoiceCollisions(timeBased[i])
|
|
50
|
+
//setUpperAndLowerElements(renderer, abcLine.staffGroup);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
// Set the staff spacing
|
|
44
55
|
// TODO-PER: we should have been able to do this by the time we called setUpperAndLowerElements, but for some reason the "bottom" element seems to be set as a side effect of setting the X spacing.
|
|
45
56
|
for (i = 0; i < abctune.lines.length; i++) {
|
|
@@ -126,4 +137,83 @@ function centerWholeRests(voices) {
|
|
|
126
137
|
}
|
|
127
138
|
}
|
|
128
139
|
|
|
140
|
+
function fixVoiceCollisions(timeBasedLine) {
|
|
141
|
+
for (var s = 0; s < timeBasedLine.length; s++) {
|
|
142
|
+
var timeSlot = timeBasedLine[s]
|
|
143
|
+
// If there is more than one thing happening at the same time,
|
|
144
|
+
// and one of those things is a rest, then:
|
|
145
|
+
// If the rest is in the first element, check to see if the bottom bumps into the top of any of the rest of the elements
|
|
146
|
+
// If the rest is in the last element, check to see if the top bumps into the bottom of any of the rest of the elements.
|
|
147
|
+
// Note: if there are more than two voices the staff will get sloppy, so there is a limit to how much that can be improved, but this should be fine when there are two voices.
|
|
148
|
+
// If there is a collision, move the rest up or down to fix that.
|
|
149
|
+
var keys = Object.keys(timeSlot)
|
|
150
|
+
for (var z = 0; z < keys.length; z++) {
|
|
151
|
+
var slot = timeSlot[keys[z]] // slot is an array of all the things happening at a particular time
|
|
152
|
+
var lastIndex = slot.length - 1
|
|
153
|
+
if (slot.length > 1) {
|
|
154
|
+
var isRealRest = slot[0].abcelem.rest && slot[0].abcelem.rest.type === 'rest' // weed out invisible rests
|
|
155
|
+
var isRealRest2 = slot[lastIndex].abcelem.rest && slot[lastIndex].abcelem.rest.type === 'rest' // weed out invisible rests
|
|
156
|
+
if (isRealRest && !slot[lastIndex].abcelem.rest) {
|
|
157
|
+
// the first voice has a rest and the second doesn't
|
|
158
|
+
var restTop = slot[0].children.find(function (ch) { return ch.name.includes('rest') })
|
|
159
|
+
var otherTop = closeTop(slot[lastIndex])
|
|
160
|
+
if (restTop) {
|
|
161
|
+
var distance1 = restTop.bottom - otherTop
|
|
162
|
+
distance1 -= 2 // give some room between the rest and the note
|
|
163
|
+
if (distance1 < 0 && slot[0].children.length > 0) {
|
|
164
|
+
slot[0].bottom -= distance1
|
|
165
|
+
slot[0].top -= distance1
|
|
166
|
+
slot[0].children[0].bottom -= distance1
|
|
167
|
+
slot[0].children[0].top -= distance1
|
|
168
|
+
slot[0].children[0].pitch -= distance1
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else if (isRealRest2 && !slot[0].abcelem.rest) {
|
|
172
|
+
// the last voice has a rest and the first doesn't
|
|
173
|
+
var restBottom = slot[lastIndex].children.find(function (ch) { return ch.name.includes('rest') })
|
|
174
|
+
if (restBottom) {
|
|
175
|
+
var distance2 = restBottom.top - closeBottom(slot[0])
|
|
176
|
+
distance2 += 2 // give some room between the rest and the note
|
|
177
|
+
if (distance2 > 0 && slot[lastIndex].children.length > 0) {
|
|
178
|
+
slot[lastIndex].bottom -= distance2
|
|
179
|
+
slot[lastIndex].top -= distance2
|
|
180
|
+
slot[lastIndex].children[0].bottom -= distance2
|
|
181
|
+
slot[lastIndex].children[0].top -= distance2
|
|
182
|
+
slot[lastIndex].children[0].pitch -= distance2
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function closeTop(absElem) {
|
|
192
|
+
if (absElem.children) {
|
|
193
|
+
var max = -90 // This is clearly way lower than the max calculated below
|
|
194
|
+
for (var i = 0; i < absElem.children.length; i++) {
|
|
195
|
+
var child = absElem.children[i]
|
|
196
|
+
if (child.type !== 'chord')
|
|
197
|
+
max = Math.max(max, child.top)
|
|
198
|
+
}
|
|
199
|
+
if (max > -90)
|
|
200
|
+
return max
|
|
201
|
+
}
|
|
202
|
+
return absElem.top
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function closeBottom(absElem) {
|
|
206
|
+
if (absElem.children) {
|
|
207
|
+
var min = 90 // This is clearly way higher than the min calculated below
|
|
208
|
+
for (var i = 0; i < absElem.children.length; i++) {
|
|
209
|
+
var child = absElem.children[i]
|
|
210
|
+
if (child.type !== 'lyric')
|
|
211
|
+
min = Math.min(min, child.bottom)
|
|
212
|
+
}
|
|
213
|
+
if (min < 90)
|
|
214
|
+
return min
|
|
215
|
+
}
|
|
216
|
+
return absElem.bottom
|
|
217
|
+
}
|
|
218
|
+
|
|
129
219
|
module.exports = layout;
|
|
@@ -75,7 +75,8 @@ var setUpperAndLowerElements = function (renderer, staffGroup) {
|
|
|
75
75
|
|
|
76
76
|
for (var j = 0; j < staff.voices.length; j++) {
|
|
77
77
|
var voice = staffGroup.voices[staff.voices[j]];
|
|
78
|
-
setUpperAndLowerVoiceElements(positionY, voice, renderer.spacing);
|
|
78
|
+
var diff = setUpperAndLowerVoiceElements(positionY, voice, renderer.spacing);
|
|
79
|
+
staff.bottom -= diff//
|
|
79
80
|
}
|
|
80
81
|
// We might need a little space in between staves if the staves haven't been pushed far enough apart by notes or extra vertical stuff.
|
|
81
82
|
// Only try to put in extra space if this isn't the top staff.
|
|
@@ -112,9 +113,16 @@ function incTop(staff, positionY, item, count) {
|
|
|
112
113
|
function setUpperAndLowerVoiceElements(positionY, voice, spacing) {
|
|
113
114
|
var i;
|
|
114
115
|
var abselem;
|
|
116
|
+
var diff = 0
|
|
115
117
|
for (i = 0; i < voice.children.length; i++) {
|
|
116
118
|
abselem = voice.children[i];
|
|
117
|
-
setUpperAndLowerAbsoluteElements(positionY, abselem, spacing);
|
|
119
|
+
var bottom = setUpperAndLowerAbsoluteElements(positionY, abselem, spacing);
|
|
120
|
+
if (bottom < abselem.bottom) {
|
|
121
|
+
// We're moving things down so tell the staff that it needs to be taller
|
|
122
|
+
diff = abselem.bottom - bottom
|
|
123
|
+
abselem.bottom = bottom//
|
|
124
|
+
voice.bottom = bottom//
|
|
125
|
+
}
|
|
118
126
|
}
|
|
119
127
|
for (i = 0; i < voice.otherchildren.length; i++) {
|
|
120
128
|
abselem = voice.otherchildren[i];
|
|
@@ -138,6 +146,7 @@ function setUpperAndLowerVoiceElements(positionY, voice, spacing) {
|
|
|
138
146
|
break;
|
|
139
147
|
}
|
|
140
148
|
}
|
|
149
|
+
return diff
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
// For each of the relative elements that can't be placed in advance (because their vertical placement depends on everything
|
|
@@ -145,6 +154,7 @@ function setUpperAndLowerVoiceElements(positionY, voice, spacing) {
|
|
|
145
154
|
// hash with the vertical placement (in pitch units) for each type.
|
|
146
155
|
// TODO-PER: I think this needs to be separated by "above" and "below". How do we know that for dynamics at the point where they are being defined, though? We need a pass through all the relative elements to set "above" and "below".
|
|
147
156
|
function setUpperAndLowerAbsoluteElements(specialYResolved, element, spacing) {
|
|
157
|
+
var bottom = element.bottom
|
|
148
158
|
// specialYResolved contains the actual pitch for each of the classes of elements.
|
|
149
159
|
for (var i = 0; i < element.children.length; i++) {
|
|
150
160
|
var child = element.children[i];
|
|
@@ -152,6 +162,11 @@ function setUpperAndLowerAbsoluteElements(specialYResolved, element, spacing) {
|
|
|
152
162
|
if (element.specialY.hasOwnProperty(key)) {
|
|
153
163
|
if (child[key]) { // If this relative element has defined a height for this class of element
|
|
154
164
|
child.pitch = specialYResolved[key];
|
|
165
|
+
if (key === 'lyricHeightBelow' && child.type === 'lyric' && child.voiceNumber) {
|
|
166
|
+
// TODO-PER: This can result in extra unused vertical space if there are lyrics only on the second but not the first voice.
|
|
167
|
+
child.pitch -= child.voiceNumber*child[key]//
|
|
168
|
+
bottom = Math.min(element.bottom, child.pitch)//
|
|
169
|
+
}
|
|
155
170
|
if (child.top === undefined) { // TODO-PER: HACK! Not sure this is the right place to do this.
|
|
156
171
|
if (child.type === 'TempoElement') {
|
|
157
172
|
setUpperAndLowerTempoElement(specialYResolved, child);
|
|
@@ -165,6 +180,7 @@ function setUpperAndLowerAbsoluteElements(specialYResolved, element, spacing) {
|
|
|
165
180
|
}
|
|
166
181
|
}
|
|
167
182
|
}
|
|
183
|
+
return bottom
|
|
168
184
|
}
|
|
169
185
|
|
|
170
186
|
function setUpperAndLowerCrescendoElements(positionY, element) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function toTimeAndStaffBased(abcLines) {
|
|
2
|
+
var results = []
|
|
3
|
+
for (var lin = 0; lin < abcLines.length; lin++) {
|
|
4
|
+
var line = abcLines[lin]
|
|
5
|
+
var staffGroup = line.staffGroup
|
|
6
|
+
|
|
7
|
+
var group = []
|
|
8
|
+
if (staffGroup && staffGroup && staffGroup.staffs) {
|
|
9
|
+
for (var s = 0; s < staffGroup.staffs.length; s++) {
|
|
10
|
+
var staff = staffGroup.staffs[s]
|
|
11
|
+
var timeSlot = {}
|
|
12
|
+
for (var i = 0; i < staff.voices.length; i++) {
|
|
13
|
+
var voice = staffGroup.voices[staff.voices[i]]
|
|
14
|
+
var time = 0
|
|
15
|
+
for (var k = 0; k < voice.children.length; k++) {
|
|
16
|
+
var index = 'T' + Math.round(time*1000) // There can be inexactness when calculating triplets, so we'll round, but we'll make sure that no make sure that we don't lose necessary precision by making it a shorter time than would ever happen
|
|
17
|
+
if (!timeSlot[index])
|
|
18
|
+
timeSlot[index] = []
|
|
19
|
+
if (voice.children[k].abcelem.el_type === 'note') {
|
|
20
|
+
timeSlot[index].push(voice.children[k])
|
|
21
|
+
time += voice.children[k].duration
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Now timeSlot is an object with all the voices on a particular staff that
|
|
26
|
+
// happen at the same time as an array.
|
|
27
|
+
group.push(timeSlot)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
results.push(group)
|
|
31
|
+
}
|
|
32
|
+
return results
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = toTimeAndStaffBased;
|
|
@@ -21,34 +21,60 @@ function layoutTriplet(element) {
|
|
|
21
21
|
if (isAbove(beam))
|
|
22
22
|
element.endingHeightAbove = 4;
|
|
23
23
|
} else {
|
|
24
|
-
// If there isn't a beam, then we need to draw the bracket and the text. The bracket is
|
|
24
|
+
// If there isn't a beam, then we need to draw the bracket and the text. The bracket is either above or below depending on the stem direction of the notes.
|
|
25
|
+
// Above:
|
|
25
26
|
// The bracket is never lower than the 'a' line, but is 4 pitches above the first and last notes. If there is
|
|
26
27
|
// a tall note in the middle, the bracket is horizontal and above the highest note.
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// Below: The bracket is never higher than the 'C' line, and is 4 pitches below.
|
|
29
|
+
|
|
30
|
+
// To decide if the bracket goes above or below, go in the direction of the most stems. If there are the same number it will put the bracket above.
|
|
31
|
+
var up = stemDirectionUp(element)
|
|
32
|
+
element.up = up
|
|
33
|
+
element.startNote = up ? Math.max(element.anchor1.parent.top, 9) + 4 : Math.min(element.anchor1.parent.bottom, 0) - 2
|
|
34
|
+
element.endNote = up ? Math.max(element.anchor2.parent.top, 9) + 4 : Math.min(element.anchor2.parent.bottom, 0) - 2
|
|
35
|
+
|
|
29
36
|
// If it starts or ends on a rest, make the beam horizontal
|
|
30
37
|
if (element.anchor1.parent.type === "rest" && element.anchor2.parent.type !== "rest")
|
|
31
38
|
element.startNote = element.endNote;
|
|
32
39
|
else if (element.anchor2.parent.type === "rest" && element.anchor1.parent.type !== "rest")
|
|
33
40
|
element.endNote = element.startNote;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
element.startNote
|
|
42
|
-
|
|
41
|
+
if (up) {
|
|
42
|
+
// See if the middle note is really high.
|
|
43
|
+
var max = 0;
|
|
44
|
+
for (var i = 0; i < element.middleElems.length; i++) {
|
|
45
|
+
max = Math.max(max, element.middleElems[i].top);
|
|
46
|
+
}
|
|
47
|
+
max += 4;
|
|
48
|
+
if (max > element.startNote || max > element.endNote) {
|
|
49
|
+
element.startNote = max + 3;
|
|
50
|
+
element.endNote = max + 3;
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// See if the middle note is really low.
|
|
54
|
+
var min = 0;
|
|
55
|
+
for (var i = 0; i < element.middleElems.length; i++) {
|
|
56
|
+
min = Math.min(min, element.middleElems[i].bottom-element.middleElems[i].height);
|
|
57
|
+
}
|
|
58
|
+
min -= 3;
|
|
59
|
+
if (min < element.startNote && min < element.endNote) {
|
|
60
|
+
element.startNote = Math.min(min, element.startNote) - 2;
|
|
61
|
+
element.endNote = Math.min(min, element.endNote) - 2;
|
|
62
|
+
}
|
|
43
63
|
}
|
|
44
64
|
if (element.flatBeams) {
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
if (up) {
|
|
66
|
+
element.startNote = Math.max(element.startNote, element.endNote);
|
|
67
|
+
element.endNote = Math.max(element.startNote, element.endNote);
|
|
68
|
+
} else {
|
|
69
|
+
element.startNote = Math.min(element.startNote, element.endNote);
|
|
70
|
+
element.endNote = Math.min(element.startNote, element.endNote);
|
|
71
|
+
}
|
|
47
72
|
}
|
|
48
73
|
|
|
49
74
|
element.yTextPos = element.startNote + (element.endNote - element.startNote) / 2;
|
|
50
75
|
element.xTextPos = element.anchor1.x + (element.anchor2.x + element.anchor2.w - element.anchor1.x) / 2;
|
|
51
76
|
element.top = element.yTextPos + 1;
|
|
77
|
+
element.bottom = element.yTextPos - 2;
|
|
52
78
|
}
|
|
53
79
|
}
|
|
54
80
|
delete element.middleElems;
|
|
@@ -59,6 +85,27 @@ function isAbove(beam) {
|
|
|
59
85
|
return beam.stemsUp;
|
|
60
86
|
}
|
|
61
87
|
|
|
88
|
+
function stemDirectionUp(element) {
|
|
89
|
+
var up = 0
|
|
90
|
+
var down = 0
|
|
91
|
+
if (element.anchor1) {
|
|
92
|
+
if (element.anchor1.stemDir === 'up') up++
|
|
93
|
+
if (element.anchor1.stemDir === 'down') down++
|
|
94
|
+
}
|
|
95
|
+
if (element.anchor2) {
|
|
96
|
+
if (element.anchor2.stemDir === 'up') up++
|
|
97
|
+
if (element.anchor2.stemDir === 'down') down++
|
|
98
|
+
}
|
|
99
|
+
if (element.middleElems) {
|
|
100
|
+
for (var i = 0; i < element.middleElems.length; i++) {
|
|
101
|
+
var elem = element.middleElems[i]
|
|
102
|
+
if (elem.stemDir === 'up') up++
|
|
103
|
+
if (elem.stemDir === 'down') down++
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return up >= down
|
|
107
|
+
}
|
|
108
|
+
|
|
62
109
|
// We can't just use the entire beam for the calculation. The range has to be passed in, because the beam might extend into some unrelated notes. for instance, (3_a'f'e'f'2 when L:16
|
|
63
110
|
function heightAtMidpoint(startX, endX, beam) {
|
|
64
111
|
if (beam.beams.length === 0)
|
package/version.js
CHANGED