UNPKG

abcjs

Version:

Renderer for abc music notation

147 lines (126 loc) 5.96 kB
var layoutVoiceElements = require('./voice-elements'); function checkLastBarX(voices) { var maxX = 0; for (var i = 0; i < voices.length; i++) { var curVoice = voices[i]; if (curVoice.children.length > 0) { var lastChild = curVoice.children.length - 1; var maxChild = curVoice.children[lastChild]; if (maxChild.abcelem.el_type === 'bar') { var barX = maxChild.children[0].x; if (barX > maxX) { maxX = barX; } else { maxChild.children[0].x = maxX; } } } } } var layoutStaffGroup = function (spacing, renderer, debug, staffGroup, leftEdge) { var epsilon = 0.0000001; // Fudging for inexactness of floating point math. var spacingunits = 0; // number of times we will have ended up using the spacing distance (as opposed to fixed width distances) var minspace = 1000; // a big number to start off with - used to find out what the smallest space between two notes is -- GD 2014.1.7 var x = leftEdge; staffGroup.startx = x; var i; var currentduration = 0; if (debug) console.log("init layout", spacing); for (i = 0; i < staffGroup.voices.length; i++) { layoutVoiceElements.beginLayout(x, staffGroup.voices[i]); } var spacingunit = 0; // number of spacingunits coming from the previously laid out element to this one while (!finished(staffGroup.voices)) { // find first duration level to be laid out among candidates across voices currentduration = null; // candidate smallest duration level for (i = 0; i < staffGroup.voices.length; i++) { if (!layoutVoiceElements.layoutEnded(staffGroup.voices[i]) && (!currentduration || getDurationIndex(staffGroup.voices[i]) < currentduration)) currentduration = getDurationIndex(staffGroup.voices[i]); } // isolate voices at current duration level var currentvoices = []; var othervoices = []; for (i = 0; i < staffGroup.voices.length; i++) { var durationIndex = getDurationIndex(staffGroup.voices[i]); // PER: Because of the inexactness of JS floating point math, we just get close. if (durationIndex - currentduration > epsilon) { othervoices.push(staffGroup.voices[i]); //console.log("out: voice ",i); } else { currentvoices.push(staffGroup.voices[i]); //if (debug) console.log("in: voice ",i); } } // among the current duration level find the one which needs starting furthest right spacingunit = 0; // number of spacingunits coming from the previously laid out element to this one var spacingduration = 0; for (i = 0; i < currentvoices.length; i++) { //console.log("greatest spacing unit", x, layoutVoiceElements.getNextX(currentvoices[i]), layoutVoiceElements.getSpacingUnits(currentvoices[i]), currentvoices[i].spacingduration); if (layoutVoiceElements.getNextX(currentvoices[i]) > x) { x = layoutVoiceElements.getNextX(currentvoices[i]); spacingunit = layoutVoiceElements.getSpacingUnits(currentvoices[i]); spacingduration = currentvoices[i].spacingduration; } } spacingunits += spacingunit; minspace = Math.min(minspace, spacingunit); if (debug) console.log("currentduration: ", currentduration, spacingunits, minspace); var lastTopVoice = undefined; for (i = 0; i < currentvoices.length; i++) { var v = currentvoices[i]; if (v.voicenumber === 0) lastTopVoice = i; var topVoice = (lastTopVoice !== undefined && currentvoices[lastTopVoice].voicenumber !== v.voicenumber) ? currentvoices[lastTopVoice] : undefined; if (!isSameStaff(v, topVoice)) topVoice = undefined; var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v, renderer.minPadding, topVoice); var dx = voicechildx - x; if (dx > 0) { x = voicechildx; //update x for (var j = 0; j < i; j++) { // shift over all previously laid out elements layoutVoiceElements.shiftRight(dx, currentvoices[j]); } } } // remove the value of already counted spacing units in other voices (e.g. if a voice had planned to use up 5 spacing units but is not in line to be laid out at this duration level - where we've used 2 spacing units - then we must use up 3 spacing units, not 5) for (i = 0; i < othervoices.length; i++) { othervoices[i].spacingduration -= spacingduration; layoutVoiceElements.updateNextX(x, spacing, othervoices[i]); // adjust other voices expectations } // update indexes of currently laid out elems for (i = 0; i < currentvoices.length; i++) { var voice = currentvoices[i]; layoutVoiceElements.updateIndices(voice); } } // finished laying out // find the greatest remaining x as a base for the width for (i = 0; i < staffGroup.voices.length; i++) { if (layoutVoiceElements.getNextX(staffGroup.voices[i]) > x) { x = layoutVoiceElements.getNextX(staffGroup.voices[i]); spacingunit = layoutVoiceElements.getSpacingUnits(staffGroup.voices[i]); } } // adjust lastBar when needed (multi staves) checkLastBarX(staffGroup.voices); //console.log("greatest remaining",spacingunit,x); spacingunits += spacingunit; staffGroup.setWidth(x); return { spacingUnits: spacingunits, minSpace: minspace }; }; function finished(voices) { for (var i = 0; i < voices.length; i++) { if (!layoutVoiceElements.layoutEnded(voices[i])) return false; } return true; } function getDurationIndex(element) { return element.durationindex - (element.children[element.i] && (element.children[element.i].duration > 0) ? 0 : 0.0000005); // if the ith element doesn't have a duration (is not a note), its duration index is fractionally before. This enables CLEF KEYSIG TIMESIG PART, etc. to be laid out before we get to the first note of other voices } function isSameStaff(voice1, voice2) { if (!voice1 || !voice1.staff || !voice1.staff.voices || voice1.staff.voices.length === 0) return false; if (!voice2 || !voice2.staff || !voice2.staff.voices || voice2.staff.voices.length === 0) return false; return (voice1.staff.voices[0] === voice2.staff.voices[0]); } module.exports = layoutStaffGroup;