package com.xenoage.zong.musiclayout.spacer.beam.placement;
import com.xenoage.utils.annotations.Const;
import com.xenoage.zong.core.music.chord.StemDirection;
import com.xenoage.zong.musiclayout.SLP;
import com.xenoage.zong.musiclayout.notation.BeamNotation;
import com.xenoage.zong.musiclayout.spacer.beam.Slant;
import com.xenoage.zong.musiclayout.spacer.beam.stem.BeamedStems;
import com.xenoage.zong.musiclayout.spacing.SystemSpacing;
import lombok.AllArgsConstructor;
import static com.xenoage.zong.musiclayout.SLP.slp;
import static com.xenoage.zong.musiclayout.spacer.beam.placement.SingleStaffBeamPlacer.singleStaffBeamPlacer;
/**
* Computes the {@link Placement} of a beam on two adjacent staffStampings, given its {@link Slant}.
*
* We look at the dictator stem of the upper staff (pointing down) and the dictator stem of
* the lower staff (pointing up). The beam is centered between these positions.
*
* @author Andreas Wenger
*/
public class TwoStavesBeamPlacer {
public static final TwoStavesBeamPlacer twoStavesBeamPlacer = new TwoStavesBeamPlacer();
/**
* Vertical placement of a beam on two adjacent staves,
* defined by the {@link SLP}s of the left and the right stem.
*
* @author Andreas Wenger
*/
@Const @AllArgsConstructor
public static final class Placement {
public final SLP leftSlp, rightSlp;
}
/**
* Computes the {@link Placement} of a beam on two adjacent staves.
* @param slant the slant for this beam
*/
public Placement compute(Slant slant, BeamedStems stems, BeamNotation beam, SystemSpacing system) {
//find the dictator stems of the upper and lower staff
float slantIs = slant.getMaxIs();
int upperStaffIndex = beam.element.getUpperStaffIndex();
int upperDictatorStemIndex = singleStaffBeamPlacer.getDictatorStemIndex(StemDirection.Down, stems, slantIs);
int lowerDictatorStemIndex = singleStaffBeamPlacer.getDictatorStemIndex(StemDirection.Up, stems, slantIs);
//compute their end positions in mm, to make it independent from the staff (may have different sizes),
//and place the beam in the middle of these two endpoints
float upperDictatorYMm = system.getYMm(slp(upperStaffIndex, stems.get(upperDictatorStemIndex).endSlp.lp));
float lowerDictatorYMm = system.getYMm(slp(upperStaffIndex + 1, stems.get(lowerDictatorStemIndex).endSlp.lp));
float upperDictatorXMm = stems.get(upperDictatorStemIndex).xIs * system.getInterlineSpace(upperStaffIndex);
float lowerDictatorXMm = stems.get(lowerDictatorStemIndex).xIs * system.getInterlineSpace(upperStaffIndex + 1);
float middleXmm = (upperDictatorXMm + lowerDictatorXMm) / 2;
float middleYMm = (upperDictatorYMm + lowerDictatorYMm) / 2;
//lengthen the stem by the half of the height of the beam lines, to center the whole beam and not its end
middleYMm -= stems.primaryStemDir.getSign() * beam.getTotalHeightIs() *
system.getInterlineSpace(stems.getFirst().noteSlp.staff) / 2;
//transform the positions in mm into IS/LP makle them staff-dependent again
float middleXIsFromUpperStaff = middleXmm / system.getInterlineSpace(upperStaffIndex);
float middleXIsFromLowerStaff = middleXmm / system.getInterlineSpace(upperStaffIndex + 1);
float middleYLpFromUpperStaff = system.getLp(upperStaffIndex, middleYMm);
float middleYLpFromLowerStaff = system.getLp(upperStaffIndex + 1, middleYMm);
//compute the end LPs at the left and right side
boolean isLeftUpperStaff = (stems.getFirst().dir == StemDirection.Down);
boolean isRightUpperStaff = (stems.getLast().dir == StemDirection.Down);
float leftMiddleXIs = (isLeftUpperStaff ? middleXIsFromUpperStaff : middleXIsFromLowerStaff);
float rightMiddleXIs = (isRightUpperStaff ? middleXIsFromUpperStaff : middleXIsFromLowerStaff);
float leftYLp = (isLeftUpperStaff ? middleYLpFromUpperStaff : middleYLpFromLowerStaff);
float rightYLp = (isRightUpperStaff ? middleYLpFromUpperStaff : middleYLpFromLowerStaff);
float beamWidthIs = stems.rightXIs - stems.leftXIs;
float leftLp = singleStaffBeamPlacer.getBeamLpAtXIs(stems.leftXIs, leftMiddleXIs, leftYLp, slantIs, beamWidthIs);
float rightLp = singleStaffBeamPlacer.getBeamLpAtXIs(stems.rightXIs, rightMiddleXIs, rightYLp, slantIs, beamWidthIs);
int leftStaffIndex = upperStaffIndex + (isLeftUpperStaff ? 0 : 1);
int rightStaffIndex = upperStaffIndex + (isRightUpperStaff ? 0 : 1);
return new Placement(slp(leftStaffIndex, leftLp), slp(rightStaffIndex, rightLp));
}
}