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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abcjs",
3
- "version": "6.6.1",
3
+ "version": "6.6.3",
4
4
  "description": "Renderer for abc music notation",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -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 = "";
@@ -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
- if (i === elems.length - 1 || getDurlog(elems[i + 1].abcelem.duration) > (-j - 4)) {
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
- if(i === 0) {
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 (i === elems.length - 1) {
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
- if(elems[i-1].duration === elems[i+1].duration) {
201
- // The notes on either side are the same duration, alternate which side the beam goes to
202
- auxBeamEndX = i%2 === 0 ? x + 5 : x - 5;
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
- // The notes on either side are different durations, draw the beam to the shorter note
205
- auxBeamEndX = elems[i-1].duration > elems[i+1].duration ? x + 5 : x - 5;
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 always above.
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
- element.startNote = Math.max(element.anchor1.parent.top, 9) + 4;
28
- element.endNote = Math.max(element.anchor2.parent.top, 9) + 4;
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
- // See if the middle note is really high.
35
- var max = 0;
36
- for (var i = 0; i < element.middleElems.length; i++) {
37
- max = Math.max(max, element.middleElems[i].top);
38
- }
39
- max += 4;
40
- if (max > element.startNote || max > element.endNote) {
41
- element.startNote = max;
42
- element.endNote = max;
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
- element.startNote = Math.max(element.startNote, element.endNote);
46
- element.endNote = Math.max(element.startNote, element.endNote);
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
@@ -1,3 +1,3 @@
1
- var version = '6.6.1';
1
+ var version = '6.6.3';
2
2
 
3
3
  module.exports = version;