UNPKG

abcjs

Version:

Renderer for abc music notation

122 lines (105 loc) 5.47 kB
var VoiceElement = function VoiceElements() { } VoiceElement.beginLayout = function (startx, voice) { voice.i = 0; voice.durationindex = 0; //this.ii=this.children.length; voice.startx = startx; voice.minx = startx; // furthest left to where negatively positioned elements are allowed to go voice.nextx = startx; // x position where the next element of this voice should be placed assuming no other voices and no fixed width constraints voice.spacingduration = 0; // duration left to be laid out in current iteration (omitting additional spacing due to other aspects, such as bars, dots, sharps and flats) }; VoiceElement.layoutEnded = function (voice) { return (voice.i >= voice.children.length); }; VoiceElement.getNextX = function (voice) { return Math.max(voice.minx, voice.nextx); }; // number of spacing units expected for next positioning VoiceElement.getSpacingUnits = function (voice) { return Math.sqrt(voice.spacingduration * 8); }; // Try to layout the element at index this.i // x - position to try to layout the element at // spacing - base spacing // can't call this function more than once per iteration VoiceElement.layoutOneItem = function (x, spacing, voice, minPadding, firstVoice) { var child = voice.children[voice.i]; if (!child) return 0; var er = x - voice.minx; // available extrawidth to the left var pad = voice.durationindex + child.duration > 0 ? minPadding : 0; // only add padding to the items that aren't fixed to the left edge. // See if this item overlaps the item in the first voice. If firstVoice is undefined then there's nothing to compare. if (child.abcelem.el_type === "note" && !child.abcelem.rest && voice.voicenumber !== 0 && firstVoice) { var firstChild = firstVoice.children[firstVoice.i]; // It overlaps if the either the child's top or bottom is inside the firstChild's or at least within 1 // A special case is if the element is on the same line then it can share a note head, if the notehead is the same var overlaps = firstChild && ((child.abcelem.maxpitch <= firstChild.abcelem.maxpitch + 1 && child.abcelem.maxpitch >= firstChild.abcelem.minpitch - 1) || (child.abcelem.minpitch <= firstChild.abcelem.maxpitch + 1 && child.abcelem.minpitch >= firstChild.abcelem.minpitch - 1)) // See if they can share a note head if (overlaps && child.abcelem.minpitch === firstChild.abcelem.minpitch && child.abcelem.maxpitch === firstChild.abcelem.maxpitch && firstChild.heads && firstChild.heads.length > 0 && child.heads && child.heads.length > 0 && firstChild.heads[0].c === child.heads[0].c) overlaps = false; // If this note overlaps the note in the first voice and we haven't moved the note yet (this can be called multiple times) if (overlaps) { // I think that firstChild should always have at least one note head, but defensively make sure. // There was a problem with this being called more than once so if a value is adjusted then it is saved so it is only adjusted once. var firstChildNoteWidth = firstChild.heads && firstChild.heads.length > 0 ? firstChild.heads[0].realWidth : firstChild.fixed.w; if (!child.adjustedWidth) child.adjustedWidth = firstChildNoteWidth + child.w; child.w = child.adjustedWidth for (var j = 0; j < child.children.length; j++) { var relativeChild = child.children[j]; if (relativeChild.name.indexOf("accidental") < 0) { if (!relativeChild.adjustedWidth) relativeChild.adjustedWidth = relativeChild.dx + firstChildNoteWidth; relativeChild.dx = relativeChild.adjustedWidth } } } } var extraWidth = getExtraWidth(child, pad); if (er < extraWidth) { // shift right by needed amount // There's an exception if a bar element is after a Part element, there is no shift. if (voice.i === 0 || child.type !== 'bar' || (voice.children[voice.i - 1].type !== 'part' && voice.children[voice.i - 1].type !== 'tempo')) x += extraWidth - er; } child.setX(x); voice.spacingduration = child.duration; //update minx voice.minx = x + getMinWidth(child); // add necessary layout space if (voice.i !== voice.children.length - 1) voice.minx += child.minspacing; // add minimumspacing except on last elem this.updateNextX(x, spacing, voice); // contribute to staff y position //this.staff.top = Math.max(child.top,this.staff.top); //this.staff.bottom = Math.min(child.bottom,this.staff.bottom); return x; // where we end up having placed the child }; VoiceElement.shiftRight = function (dx, voice) { var child = voice.children[voice.i]; if (!child) return; child.setX(child.x + dx); voice.minx += dx; voice.nextx += dx; }; // call when spacingduration has been updated VoiceElement.updateNextX = function (x, spacing, voice) { voice.nextx = x + (spacing * Math.sqrt(voice.spacingduration * 8)); }; VoiceElement.updateIndices = function (voice) { if (!this.layoutEnded(voice)) { voice.durationindex += voice.children[voice.i].duration; if (voice.children[voice.i].type === 'bar') voice.durationindex = Math.round(voice.durationindex * 64) / 64; // everytime we meet a barline, do rounding to nearest 64th voice.i++; } }; function getExtraWidth(child, minPadding) { // space needed to the left of the note var padding = 0; if (child.type === 'note' || child.type === 'bar') padding = minPadding; return -child.extraw + padding; } function getMinWidth(child) { // absolute space taken to the right of the note return child.w; } module.exports = VoiceElement;