package com.xenoage.zong.musiclayout.stamper;
import com.xenoage.utils.math.MathUtils.LinearInterpolationPoints;
import com.xenoage.zong.core.music.format.SP;
import com.xenoage.zong.musiclayout.notation.beam.Fragment;
import com.xenoage.zong.musiclayout.notation.beam.Fragments;
import com.xenoage.zong.musiclayout.spacing.BeamSpacing;
import com.xenoage.zong.musiclayout.stampings.BeamStamping;
import com.xenoage.zong.musiclayout.stampings.StaffStamping;
import lombok.val;
import java.util.List;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.utils.math.MathUtils.interpolateLinear;
import static com.xenoage.zong.core.music.format.SP.sp;
import static com.xenoage.zong.musiclayout.notation.BeamNotation.hookLengthIs;
import static com.xenoage.zong.musiclayout.notation.BeamNotation.lineHeightIs;
import static com.xenoage.zong.musiclayout.notation.beam.Fragment.*;
/**
* This strategy creates the {@link BeamStamping}s for a beam.
*
* @author Andreas Wenger
*/
public class BeamStamper {
public static final BeamStamper beamStamper = new BeamStamper();
/**
* Computes the stampings for the given beam and returns them.
* @param beam the beam to stamp
* @param staff the staff stamping of the first chord of the beam
*/
public List<BeamStamping> stamp(BeamSpacing beam, StaffStamping staff) {
int beamSize = beam.chords.size();
val leftEndSp = beam.getLeftSp();
val rightEndSp = beam.getRightSp();
val primaryStemDir = beam.getStemDirection(0);
//number of beam lines
int linesCount = beam.notation.getLinesCount();
List<BeamStamping> ret = alist(linesCount); //first guess of the size (is correct for simple beams)
//first line (8th line) is always continuous
val beam8th = new BeamStamping(beam, staff,
leftEndSp, rightEndSp, primaryStemDir);
ret.add(beam8th);
//the next lines can be broken, if there are different rhythms or beam subdivisions.
//this is stored in the notation
for (int i : range(beam.notation.linesFragments)) {
int line = i + 1;
float lineLp = -1 * primaryStemDir.getSign() * (lineHeightIs + beam.notation.gapIs) * 2 * line;
val beamLinePoints = new LinearInterpolationPoints(
leftEndSp.lp + lineLp, rightEndSp.lp + lineLp, leftEndSp.xMm, rightEndSp.xMm);
//create the line stampings
float startXMm = 0;
Fragments fragments = beam.notation.linesFragments.get(i);
for (int iChord : range(fragments)) {
Fragment fragment = fragments.get(iChord);
float stemXMm = beam.getStemEndSp(iChord).xMm;
if (fragment == Start) {
//begin a new beam line
startXMm = stemXMm;
}
else if (fragment == Stop) {
//end the beam line and stem it
float stopXMm = stemXMm;
SP leftSp = sp(startXMm, interpolateLinear(beamLinePoints, startXMm));
SP rightSp = sp(stopXMm, interpolateLinear(beamLinePoints, stopXMm));
val stamping = new BeamStamping(beam, staff, leftSp, rightSp, primaryStemDir);
ret.add(stamping);
}
else if (fragment == HookLeft || fragment == HookRight) {
//left or right hook
float lengthMm = hookLengthIs * staff.is;
float x1Mm = (fragment == HookLeft ? stemXMm - lengthMm : stemXMm);
float x2Mm = (fragment == HookLeft ? stemXMm : stemXMm + lengthMm);
SP leftSp = sp(x1Mm, interpolateLinear(beamLinePoints, x1Mm));
SP rightSp = sp(x2Mm, interpolateLinear(beamLinePoints, x2Mm));
val stamping = new BeamStamping(beam, staff, leftSp, rightSp, primaryStemDir);
ret.add(stamping);
}
}
}
return ret;
}
}