package com.xenoage.zong.musiclayout.spacing;
import com.xenoage.utils.annotations.Const;
import com.xenoage.utils.annotations.Optimized;
import com.xenoage.utils.annotations.Optimized.Reason;
import com.xenoage.zong.core.music.Staff;
import com.xenoage.zong.musiclayout.SLP;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.List;
import static com.xenoage.utils.NullUtils.notNull;
import static com.xenoage.utils.kernel.Range.range;
/**
* Vertical spacing of the staves in a system.
*
* @author Andreas
*/
@Const @RequiredArgsConstructor @Getter
public final class StavesSpacing {
/** All staves in the system. */
private final List<Staff> staves;
/** The distances between the staves in mm. (#staves-1) items. */
private final float[] distancesMm;
/** The default interline space in mm. */
private final float defaultIs;
@Optimized(Reason.Performance)
private float[] cachedYOffsetsMm = null;
@Optimized(Reason.Performance)
private float cachedTotalHeightMm = Float.NaN;
/**
* Gets the height of the given staff in mm.
*/
public float getStaffHeightMm(int staff) {
return (staves.get(staff).getLinesCount() - 1) *
notNull(staves.get(staff).getInterlineSpace(), defaultIs);
}
/**
* Gets the positive distance between the previous and the given staff in mm.
*/
public float getStaffDistanceMm(int staff) {
return (staff > 0 ? distancesMm[staff - 1] : 0);
}
/**
* Gets the vertical offset in mm of the given staff in system space.
* This is the positive distance between the top line of the first staff and the
* top line of the given staff.
*/
public float getStaffYOffsetMm(int staff) {
if (cachedYOffsetsMm == null) {
cachedYOffsetsMm = new float[staves.size()];
float yMm = 0;
for (int iStaff : range(staves)) {
cachedYOffsetsMm[iStaff] = yMm;
if (iStaff < staves.size() - 1)
yMm += getStaffHeightMm(iStaff) + distancesMm[iStaff];
}
}
return cachedYOffsetsMm[staff];
}
/**
* Gets the total height of all staves in mm.
* This is the positive distance between the top line of the top staff
* and the bottom line of the bottom staff.
*/
public float getTotalHeightMm() {
if (Float.isNaN(cachedTotalHeightMm)) {
int lastStaff = staves.size() - 1;
cachedTotalHeightMm = getStaffYOffsetMm(lastStaff) + getStaffHeightMm(lastStaff);
}
return cachedTotalHeightMm;
}
/**
* Computes and returns the y-coordinate in mm in system space
* of an object on the given {@link SLP}.
*/
public float getYMm(SLP slp) {
return getYMm(slp.staff, slp.lp);
}
/**
* Computes and returns the y-coordinate in mm in system space
* of an object on the given {@link SLP}.
* @deprecated use {@link #getYMm(SLP)} instead
*/
public float getYMm(int staff, float lp) {
float staffLp0Mm = getStaffYOffsetMm(staff) + getStaffHeightMm(staff);
Staff s = staves.get(staff);
float is = notNull(s.getInterlineSpace(), defaultIs);
return staffLp0Mm - lp * is / 2;
}
/**
* Computes and returns the y-coordinate of an object in the given staff
* at the given vertical position in mm in system space as a line position.
*/
public float getLp(int staff, float mm) {
float staffLp0Mm = getStaffYOffsetMm(staff) + getStaffHeightMm(staff);
float is = notNull(staves.get(staff).getInterlineSpace(), defaultIs);
return (staffLp0Mm - mm) * 2 / is;
}
}