|
|
var RelativeElement = require('../creation/elements/relative-element'); |
|
|
var spacing = require('../helpers/spacing'); |
|
|
var getBarYAt = require('./get-bar-y-at'); |
|
|
|
|
|
var layoutBeam = function (beam) { |
|
|
if (beam.elems.length === 0 || beam.allrests) return; |
|
|
|
|
|
var dy = calcDy(beam.stemsUp, beam.isgrace); |
|
|
|
|
|
|
|
|
var firstElement = beam.elems[0]; |
|
|
var lastElement = beam.elems[beam.elems.length - 1]; |
|
|
var minStemHeight = 0; |
|
|
var referencePitch = beam.stemsUp ? firstElement.abcelem.maxpitch : firstElement.abcelem.minpitch; |
|
|
minStemHeight = minStem(firstElement, beam.stemsUp, referencePitch, minStemHeight); |
|
|
minStemHeight = minStem(lastElement, beam.stemsUp, referencePitch, minStemHeight); |
|
|
minStemHeight = Math.max(beam.stemHeight, minStemHeight + 3); |
|
|
var yPos = calcYPos(beam.average, beam.elems.length, minStemHeight, beam.stemsUp, firstElement.abcelem.averagepitch, lastElement.abcelem.averagepitch, beam.isflat, beam.min, beam.max, beam.isgrace); |
|
|
var xPos = calcXPos(beam.stemsUp, firstElement, lastElement); |
|
|
beam.addBeam({ startX: xPos[0], endX: xPos[1], startY: yPos[0], endY: yPos[1], dy: dy }); |
|
|
|
|
|
|
|
|
var beams = createAdditionalBeams(beam.elems, beam.stemsUp, beam.beams[0], beam.isgrace, dy); |
|
|
for (var i = 0; i < beams.length; i++) |
|
|
beam.addBeam(beams[i]); |
|
|
|
|
|
|
|
|
createStems(beam.elems, beam.stemsUp, beam.beams[0], dy, beam.mainNote); |
|
|
}; |
|
|
|
|
|
var getDurlog = function (duration) { |
|
|
|
|
|
|
|
|
if (duration === undefined) { |
|
|
return 0; |
|
|
} |
|
|
|
|
|
return Math.floor(Math.log(duration) / Math.log(2)); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function minStem(element, stemsUp, referencePitch, minStemHeight) { |
|
|
if (!element.children) |
|
|
return minStemHeight; |
|
|
for (var i = 0; i < element.children.length; i++) { |
|
|
var elem = element.children[i]; |
|
|
if (stemsUp && elem.top !== undefined && elem.c === "flags.ugrace") |
|
|
minStemHeight = Math.max(minStemHeight, elem.top - referencePitch); |
|
|
else if (!stemsUp && elem.bottom !== undefined && elem.c === "flags.ugrace") |
|
|
minStemHeight = Math.max(minStemHeight, referencePitch - elem.bottom + 7); |
|
|
} |
|
|
return minStemHeight; |
|
|
} |
|
|
|
|
|
function calcSlant(leftAveragePitch, rightAveragePitch, numStems, isFlat) { |
|
|
if (isFlat) |
|
|
return 0; |
|
|
var slant = leftAveragePitch - rightAveragePitch; |
|
|
var maxSlant = numStems / 2; |
|
|
|
|
|
if (slant > maxSlant) slant = maxSlant; |
|
|
if (slant < -maxSlant) slant = -maxSlant; |
|
|
return slant; |
|
|
} |
|
|
|
|
|
function calcDy(asc, isGrace) { |
|
|
var dy = (asc) ? spacing.STEP : -spacing.STEP; |
|
|
if (isGrace) dy = dy * 0.4; |
|
|
return dy; |
|
|
} |
|
|
|
|
|
function calcXPos(asc, firstElement, lastElement) { |
|
|
var starthead = firstElement.heads[asc ? 0 : firstElement.heads.length - 1]; |
|
|
var endhead = lastElement.heads[asc ? 0 : lastElement.heads.length - 1]; |
|
|
var startX = starthead.x; |
|
|
if (asc) startX += starthead.w - 0.6; |
|
|
var endX = endhead.x; |
|
|
endX += (asc) ? endhead.w : 0.6; |
|
|
return [startX, endX]; |
|
|
} |
|
|
|
|
|
function calcYPos(average, numElements, stemHeight, asc, firstAveragePitch, lastAveragePitch, isFlat, minPitch, maxPitch, isGrace) { |
|
|
var barpos = stemHeight - 2; |
|
|
var barminpos = stemHeight - 2; |
|
|
var pos = Math.round(asc ? Math.max(average + barpos, maxPitch + barminpos) : Math.min(average - barpos, minPitch - barminpos)); |
|
|
|
|
|
var slant = calcSlant(firstAveragePitch, lastAveragePitch, numElements, isFlat); |
|
|
var startY = pos + Math.floor(slant / 2); |
|
|
var endY = pos + Math.floor(-slant / 2); |
|
|
|
|
|
|
|
|
if (!isGrace) { |
|
|
if (asc && pos < 6) { |
|
|
startY = 6; |
|
|
endY = 6; |
|
|
} else if (!asc && pos > 6) { |
|
|
startY = 6; |
|
|
endY = 6; |
|
|
} |
|
|
} |
|
|
|
|
|
return [startY, endY]; |
|
|
} |
|
|
|
|
|
function createStems(elems, asc, beam, dy, mainNote) { |
|
|
for (var i = 0; i < elems.length; i++) { |
|
|
var elem = elems[i]; |
|
|
if (elem.abcelem.rest) |
|
|
continue; |
|
|
|
|
|
var isGrace = elem.addExtra ? false : true; |
|
|
var parent = isGrace ? mainNote : elem; |
|
|
var furthestHead = elem.heads[(asc) ? 0 : elem.heads.length - 1]; |
|
|
var ovalDelta = 1 / 5; |
|
|
var pitch = furthestHead.pitch + ((asc) ? ovalDelta : -ovalDelta); |
|
|
var dx = asc ? furthestHead.w : 0; |
|
|
if (!isGrace) |
|
|
dx += furthestHead.dx; |
|
|
var x = furthestHead.x + dx; |
|
|
var bary = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, x); |
|
|
var lineWidth = (asc) ? -0.6 : 0.6; |
|
|
if (!asc) |
|
|
bary -= (dy / 2) / spacing.STEP; |
|
|
if (isGrace) |
|
|
dx += elem.heads[0].dx; |
|
|
|
|
|
if (furthestHead.c === 'noteheads.slash.quarter') { |
|
|
if (asc) |
|
|
pitch += 1; |
|
|
else |
|
|
pitch -= 1; |
|
|
} |
|
|
var stem = new RelativeElement(null, dx, 0, pitch, { |
|
|
"type": "stem", |
|
|
"pitch2": bary, |
|
|
linewidth: lineWidth |
|
|
}); |
|
|
stem.setX(parent.x); |
|
|
parent.addRight(stem); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
function createAdditionalBeams(elems, asc, beam, isGrace, dy) { |
|
|
var beams = []; |
|
|
var auxBeams = []; |
|
|
for (var i = 0; i < elems.length; i++) { |
|
|
var elem = elems[i]; |
|
|
if (elem.abcelem.rest) |
|
|
continue; |
|
|
var furthestHead = elem.heads[(asc) ? 0 : elem.heads.length - 1]; |
|
|
var x = furthestHead.x + ((asc) ? furthestHead.w : 0); |
|
|
var bary = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, x); |
|
|
|
|
|
var sy = (asc) ? -1.5 : 1.5; |
|
|
if (isGrace) sy = sy * 2 / 3; |
|
|
var duration = elem.abcelem.duration; |
|
|
if (duration === 0) duration = 0.25; |
|
|
for (var durlog = getDurlog(duration); durlog < -3; durlog++) { |
|
|
var index = -4 - durlog; |
|
|
if (auxBeams[index]) { |
|
|
auxBeams[index].single = false; |
|
|
} else { |
|
|
auxBeams[index] = { |
|
|
x: x + ((asc) ? -0.6 : 0), y: bary + sy * (index + 1), |
|
|
durlog: durlog, single: true |
|
|
}; |
|
|
} |
|
|
if (i > 0 && elem.abcelem.beambr && elem.abcelem.beambr <= (index + 1)) { |
|
|
if (!auxBeams[index].split) |
|
|
auxBeams[index].split = [auxBeams[index].x]; |
|
|
var xPos = calcXPos(asc, elems[i - 1], elem); |
|
|
if (auxBeams[index].split[auxBeams[index].split.length - 1] >= xPos[0]) { |
|
|
|
|
|
xPos[0] += elem.w; |
|
|
} |
|
|
auxBeams[index].split.push(xPos[0]); |
|
|
auxBeams[index].split.push(xPos[1]); |
|
|
} |
|
|
} |
|
|
|
|
|
for (var j = auxBeams.length - 1; j >= 0; j--) { |
|
|
if (i === elems.length - 1 || getDurlog(elems[i + 1].abcelem.duration) > (-j - 4)) { |
|
|
|
|
|
var auxBeamEndX = x; |
|
|
var auxBeamEndY = bary + sy * (j + 1); |
|
|
|
|
|
|
|
|
if (auxBeams[j].single) { |
|
|
auxBeamEndX = (i === 0) ? x + 5 : x - 5; |
|
|
auxBeamEndY = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, auxBeamEndX) + sy * (j + 1); |
|
|
} |
|
|
var b = { startX: auxBeams[j].x, endX: auxBeamEndX, startY: auxBeams[j].y, endY: auxBeamEndY, dy: dy } |
|
|
if (auxBeams[j].split !== undefined) { |
|
|
var split = auxBeams[j].split; |
|
|
if (b.endX <= split[split.length - 1]) { |
|
|
|
|
|
split[split.length - 1] -= elem.w; |
|
|
} |
|
|
split.push(b.endX); |
|
|
b.split = auxBeams[j].split; |
|
|
} |
|
|
beams.push(b); |
|
|
auxBeams = auxBeams.slice(0, j); |
|
|
} |
|
|
} |
|
|
} |
|
|
return beams; |
|
|
} |
|
|
|
|
|
module.exports = layoutBeam; |
|
|
|