package com.xenoage.zong.musiclayout.stamper;
import com.xenoage.utils.kernel.Tuple2;
import com.xenoage.utils.math.MathUtils;
import com.xenoage.utils.math.VSide;
import com.xenoage.zong.core.music.format.BezierPoint;
import com.xenoage.zong.core.music.format.SP;
import com.xenoage.zong.core.music.slur.Slur;
import com.xenoage.zong.core.music.slur.SlurType;
import com.xenoage.zong.core.music.slur.SlurWaypoint;
import com.xenoage.zong.musiclayout.continued.ContinuedSlur;
import com.xenoage.zong.musiclayout.layouter.cache.util.SlurCache;
import com.xenoage.zong.musiclayout.notation.ChordNotation;
import com.xenoage.zong.musiclayout.stampings.SlurStamping;
import com.xenoage.zong.musiclayout.stampings.StaffStamping;
import static com.xenoage.utils.kernel.Tuple2.t;
import static com.xenoage.zong.core.music.format.SP.sp;
/**
* Creates the {@link SlurStamping}s for {@link Slur}s.
*
* @author Andreas Wenger
*/
public class SlurStamper {
public static final SlurStamper slurStamper = new SlurStamper();
/**
* Computes the additional distance of the startpoint or endpoint of a slur
* to the notehead. This value depends on articulations or stems at the same side.
*/
public float getAdditionalDistanceIs(ChordNotation chord, VSide slurSide) {
return chord.articulations.heightIs;
}
/**
* Gets the explicit or default {@link VSide} of the given slur.
* TODO : move into other class use and better algorithm
*/
public VSide getSide(Slur slur /*, Notations notations */) {
if (slur.getSide() != null)
return slur.getSide();
//val chord = notations.getChord(slur.getStart().getChord());
//return chord.stemDirection == StemDirection.Up ? VSide.Bottom : VSide.Top;
return VSide.Top;
}
/**
* Creates a {@link SlurStamping} for the first part (or only part,
* if simple slur without system breaks) of a slur or tie.
*
* If the slur continues to another system, the second return value is true,
* otherwise false.
*/
public Tuple2<SlurStamping, Boolean> createSlurStampingStart(SlurCache slurCache) {
//is one staff enough?
if (slurCache.getStartSystem() == slurCache.getStopSystem()) {
//simple case. just create it.
SlurStamping cls = createForSingleSystem(slurCache);
return t(cls, false);
}
else {
//we need at least two staves.
//first staff: begin at the notehead, go to the end of the system
SlurStamping cls = createStartForFirstSystem(slurCache.getStartStaff(), slurCache.getDefaultStartSp(),
slurCache.getSlur());
//remember this curved line to be continued
return t(cls, true);
}
}
/**
* Creates a {@link SlurStamping} for a middle part of a slur
* that spans at least three systems (ties never do that).
*
* The appropriate staff stamping must be given.
*/
public SlurStamping createSlurStampingMiddle(ContinuedSlur continuedSlur) {
return createForMiddleSystem(continuedSlur.staff, continuedSlur.element);
}
/**
* Creates a {@link SlurStamping} for a last part of a slur or tie
* that spans at least two systems.
*/
public SlurStamping createStopForLastSystem(SlurCache slurCache) {
return createStopForLastSystem(slurCache.getStopStaff(), slurCache.getDefaultStopSp(),
slurCache.getSlur());
}
/**
* Creates a {@link SlurStamping} for a curved line that
* uses only a single system. The slur may span over multiple staves.
*/
SlurStamping createForSingleSystem(SlurCache slurCache) {
Slur slur = slurCache.getSlur();
SlurWaypoint wp1 = slur.getStart();
SlurWaypoint wp2 = slur.getStop();
//end points of the bezier curve
VSide side = slurCache.getSide();
SP p1 = computeEndPoint(slur, slurCache.getDefaultStartSp(), wp1.getBezierPoint());
SP p2 = computeEndPoint(slur, slurCache.getDefaultStopSp(), wp2.getBezierPoint());
//control points of the bezier curve
BezierPoint b1 = wp1.getBezierPoint();
BezierPoint b2 = wp2.getBezierPoint();
SP c1 = (b1 != null && b1.getControl() != null ? b1.getControl() : //custom formatting
computeLeftControlPoint(slur, p1, p2, slurCache.getStartStaff())); //default formatting
SP c2 = (b2 != null && b2.getControl() != null ? b2.getControl() : //custom formatting
computeRightControlPoint(slur, p1, p2, slurCache.getStopStaff())); //default formatting
return new SlurStamping(slur, p1, p2, c1, c2, slurCache.getStartStaff(),
slurCache.getStopStaff());
}
/**
* Creates a {@link SlurStamping} for a curved line that
* starts at this system but spans at least one other system.
*/
SlurStamping createStartForFirstSystem(StaffStamping staff, SP defaultSp, Slur slur) {
SlurWaypoint wp1 = slur.getStart();
//end points of the bezier curve
SP p1 = computeEndPoint(slur, defaultSp, wp1.getBezierPoint());
SP p2 = sp(staff.positionMm.x + staff.lengthMm, p1.lp);
//control points of the bezier curve
BezierPoint b1 = wp1.getBezierPoint();
SP c1 = (b1 != null && b1.getControl() != null ? b1.getControl() : //custom formatting
computeLeftControlPoint(slur, p1, p2, staff)); //default formatting
SP c2 = computeRightControlPoint(slur, p1, p2, staff); //default formatting
return new SlurStamping(slur, p1, p2, c1, c2, staff, staff);
}
/**
* Creates a {@link SlurStamping} for a curved line that
* starts at an earlier system and ends at a later system, but
* spans also the given system.
*/
SlurStamping createForMiddleSystem(StaffStamping staff, Slur slur) {
if (slur.getType() == SlurType.Tie) {
//ties can not have middle staves
return null;
}
//end points of the bezier curve
float p1x = staff.positionMm.x + staff.system.getMeasureStartAfterLeadingMm(
staff.system.getStartMeasure()) - 5; //TODO
float p2x = staff.positionMm.x + staff.lengthMm;
float lp;
if (slur.getSide() == VSide.Top)
lp = (staff.linesCount - 1) * 2 + 2; //1 IS over the top staff line
else
lp = -2; //1 IS below the bottom staff line
SP p1 = sp(p1x, lp);
SP p2 = sp(p2x, lp);
//control points of the bezier curve
SP c1 = computeLeftControlPoint(slur, p1, p2, staff); //default formatting
SP c2 = computeRightControlPoint(slur, p1, p2, staff); //default formatting
return new SlurStamping(slur, p1, p2, c1, c2, staff, staff);
}
/**
* Creates a {@link SlurStamping} for a last part of a slur or tie
* that spans at least two systems.
*/
SlurStamping createStopForLastSystem(StaffStamping staff, SP defaultSp, Slur slur) {
SlurWaypoint wp2 = slur.getStop();
//end points of the bezier curve
SP p2 = computeEndPoint(slur, defaultSp, wp2.getBezierPoint());
SP p1 = sp(staff.positionMm.x + staff.system.getMeasureStartAfterLeadingMm(
staff.system.getStartMeasure()) - 5, p2.lp); //TODO
//control points of the bezier curve
BezierPoint b2 = wp2.getBezierPoint();
SP c1 = computeLeftControlPoint(slur, p1, p2, staff); //default formatting
SP c2 = (b2 != null && b2.getControl() != null ? b2.getControl() : //custom formatting
computeRightControlPoint(slur, p1, p2, staff)); //default formatting
return new SlurStamping(slur, p1, p2, c1, c2, staff, staff);
}
/**
* Computes the end position of a slur or tie, dependent on its corresponding default position
* and the bezier information (may be null for default formatting).
*/
SP computeEndPoint(Slur slur, SP defaultSp, BezierPoint bezierPoint) {
int dir = getSide(slur).getDir();
if (bezierPoint == null || bezierPoint.getPoint() == null) {
//default formatting
float distanceLP = (slur.getType() == SlurType.Slur ? 2 : 1.5f); //slur is 2 LP away from note center, tie 1.5
float lp = defaultSp.lp + dir * distanceLP;
return defaultSp.withLp(lp);
}
else {
//custom formatting
return defaultSp.add(bezierPoint.point);
}
}
/**
* Computes the position of the left bezier control point,
* relative to the left end point.
* @param slur the slur or tie
* @param p1 the position of the left end point of the slur
* @param p2 the position of the right end point of the slur
* @param staff the staff stamping
*/
SP computeLeftControlPoint(Slur slur, SP p1, SP p2, StaffStamping staff) {
return (slur.getType() == SlurType.Slur ? computeLeftSlurControlPoint(p1, p2, getSide(slur), staff)
: computeLeftTieControlPoint(p1, p2, getSide(slur), staff));
}
/**
* Computes the position of the right bezier control point,
* relative to the left end point.
* @param slur the slur or tie
* @param p1 the position of the left end point of the slur
* @param p2 the position of the right end point of the slur
* @param staff the staff stamping
*/
SP computeRightControlPoint(Slur slur, SP p1, SP p2, StaffStamping staff) {
return (slur.getType() == SlurType.Slur ? computeRightSlurControlPoint(p1, p2, getSide(slur), staff)
: computeRightTieControlPoint(p1, p2, getSide(slur), staff));
}
/**
* Computes the position of the left bezier control point of a slur,
* relative to the left end point.
* @param p1 the position of the left end point of the slur
* @param p2 the position of the right end point of the slur
* @param side the vertical side where to create the slur (above or below)
* @param staff the staff stamping
*/
SP computeLeftSlurControlPoint(SP p1, SP p2, VSide side, StaffStamping staff) {
//slur: longer and higher curve than tie
float distanceX = Math.abs(p2.xMm - p1.xMm);
float retX = distanceX / 4;
float retY = MathUtils.clamp(0.3f * distanceX / staff.is, 0, 8) * side.getDir();
return sp(retX, retY);
}
/**
* Computes the position of the right bezier control point of a slur,
* relative to the right end point.
* @param p1 the position of the left end point of the slur
* @param p2 the position of the right end point of the slur
* @param side the vertical side where to create the slur (above or below)
* @param staff the staff stamping
*/
SP computeRightSlurControlPoint(SP p1, SP p2, VSide side, StaffStamping staff) {
SP sp = computeLeftSlurControlPoint(p1, p2, side, staff);
return sp(-1 * sp.xMm, sp.lp);
}
/**
* Computes the position of the left bezier control point of a tie,
* relative to the left end point.
* @param p1 the position of the left end point of the tie
* @param p2 the position of the right end point of the tie
* @param side the vertical side where to create the tie (above or below)
* @param staff the staff stamping
*/
SP computeLeftTieControlPoint(SP p1, SP p2, VSide side, StaffStamping staff) {
//slur: longer and higher curve than tie
float distanceX = Math.abs(p2.xMm - p1.xMm);
float retX = 1;
float retY = MathUtils.clamp(0.3f * distanceX / staff.is, 0, 8) * side.getDir();
return sp(retX, retY);
}
/**
* Computes the position of the right bezier control point of a tie,
* relative to the right end point.
* @param p1 the position of the left end point of the tie
* @param p2 the position of the right end point of the tie
* @param side the vertical side where to create the tie (above or below)
* @param staff the staff stamping
*/
SP computeRightTieControlPoint(SP p1, SP p2, VSide side, StaffStamping staff) {
SP sp = computeLeftTieControlPoint(p1, p2, side, staff);
return sp(-1 * sp.xMm, sp.lp);
}
}