package com.xenoage.zong.musiclayout; import com.xenoage.utils.annotations.Optimized; import com.xenoage.utils.annotations.Optimized.Reason; import com.xenoage.utils.iterators.MultiListIt; import com.xenoage.utils.math.geom.Point2f; import com.xenoage.utils.math.geom.Rectangle2f; import com.xenoage.zong.core.position.MP; import com.xenoage.zong.core.position.Time; import com.xenoage.zong.musiclayout.continued.ContinuedElement; import com.xenoage.zong.musiclayout.spacing.FrameSpacing; import com.xenoage.zong.musiclayout.spacing.SystemSpacing; import com.xenoage.zong.musiclayout.stampings.StaffStamping; import com.xenoage.zong.musiclayout.stampings.Stamping; import com.xenoage.zong.musiclayout.stampings.StampingType; import com.xenoage.zong.musiclayout.stampings.TextStamping; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.util.ArrayList; import java.util.List; import static com.xenoage.utils.collections.CollectionUtils.alist; /** * A score frame layout contains the musical layout of a * score for one score frame. * * This includes the musical stampings as well as the current * selection and playback stampings. A list of continued elements * (e.g. slurs or voltas) to the following frame is also saved. * * @author Andreas Wenger */ @ToString public final class ScoreFrameLayout { /** Information about the systems in this frame. */ @Getter private FrameSpacing frameSpacing; /** The list of all staff stampings of this frame. Staff stampings and * other stampings are divided for performance reasons. */ @Optimized(Reason.Performance) @Getter private ArrayList<StaffStamping> staffStampings; /** The list of all other stampings of this frame. Staff stampings and * other stampings are divided for performance reasons.*/ @Optimized(Reason.Performance) @Getter private ArrayList<Stamping> otherStampings; /** The list of continued elements, that means the unclosed elements * that must also be painted on the next frame. */ @Getter private ArrayList<ContinuedElement> continuedElements; //TODO: move into other class /** The list of the selection stampings of this frame. */ @Getter @Setter private List<? extends Stamping> selectionStampings = alist(); /** The list of the playback stampings of this frame. */ @Getter @Setter private List<? extends Stamping> playbackStampings = alist(); public ScoreFrameLayout(FrameSpacing frameArrangement, ArrayList<StaffStamping> staffStampings, ArrayList<Stamping> otherStampings, ArrayList<ContinuedElement> continuedElements) { this.frameSpacing = frameArrangement; this.staffStampings = staffStampings; this.otherStampings = otherStampings; this.continuedElements = continuedElements; } /** * Gets all musical stampings (staves, notes, ...) of this frame, * but not selections or playback stampings. */ @SuppressWarnings("unchecked") public Iterable<Stamping> getMusicalStampings() { return new MultiListIt<>(staffStampings, otherStampings); } /** * Gets all stampings of this frame, including both the musical * and the status stampings (selection, playback). */ @SuppressWarnings("unchecked") public Iterable<Stamping> getAllStampings() { return new MultiListIt<>(staffStampings, otherStampings, selectionStampings, playbackStampings); } /** * Returns the {@link Stamping} under the given position * in score layout coordinates (mm relative to the * upper left corner) or null, if there is none. */ public Stamping getStampingAt(Point2f point) { Stamping ret = null; int highestLevel = -1; for (Stamping s : getMusicalStampings()) { if (s.getLevel().ordinal() > highestLevel && s.getBoundingShape().contains(point)) { highestLevel = s.getLevel().ordinal(); ret = s; } } return ret; } /** * Returns the {@link StaffStamping} under the given position * in score layout coordinates, or null, if there is none. */ public StaffStamping getStaffStampingAt(Point2f point) { for (StaffStamping s : staffStampings) { if (s.getBoundingShape().contains(point)) return (StaffStamping) s; } return null; } /** * Returns the {@link Stamping} (not a {@link StaffStamping}) which is instance * of the given class under the given position in score layout coordinates, * or null, if there is none. */ public Stamping getOtherStampingAt(Point2f point, StampingType type) { for (Stamping s : otherStampings) { if (s.getType() == type && s.getBoundingShape().contains(point)) return s; } return null; } /** * Computes and returns the appropriate musical position * to the given position in score layout coordinates, or null, if unknown. */ public MP computeMP(Point2f point) { if (point == null) return null; //first test, if there is a staff element. StaffStamping staff = getStaffStampingAt(point); //if there is no staff, return null if (staff == null) { return null; } //otherwise, compute the beat at this position and return it else { float posX = point.x - staff.positionMm.x; return staff.getMpAtX(posX); } } /** * Computes and returns the text stamping to the given * position in score layout coordinates, or null, if unknown. */ public TextStamping computeTextStamping(Point2f point) { if (point == null) return null; return (TextStamping) getOtherStampingAt(point, StampingType.TextStamping); } /** * Gets the staff stamping containing the given measure, or null if not found. */ public StaffStamping getStaffStamping(int staff, int measure) { for (StaffStamping s : staffStampings) if (s.getStaffIndex() == staff && s.system.containsMeasure(measure)) return s; return null; } /** * Computes and returns the bounding rectangle of the system with the given index * (relative to the frame). Only the staves are regarded, not text around them * or notes over the top staff or notes below the bottom staff. * If there are no staves in this system, null is returned. */ public Rectangle2f getSystemBoundaries(int systemIndex) { boolean found = false; float minX = Float.MAX_VALUE; float minY = Float.MAX_VALUE; float maxX = Float.MIN_VALUE; float maxY = Float.MIN_VALUE; for (StaffStamping staff : staffStampings) { if (staff.system.getSystemIndexInFrame() == systemIndex) { found = true; minX = Math.min(minX, staff.positionMm.x); minY = Math.min(minY, staff.positionMm.y); maxX = Math.max(maxX, staff.positionMm.x + staff.lengthMm); maxY = Math.max(maxY, staff.positionMm.y + (staff.linesCount - 1) * staff.is); } } if (found) return new Rectangle2f(minX, minY, maxX - minX, maxY - minY); else return null; } /** * Gets the horizontal position in mm of the given {@link Time} within the given frame, * or 0 if not found (should not happen). */ public float getPositionX(Time time) { //search all systems for the given musical position, beginning at the top staff for (SystemSpacing system : frameSpacing.systems) if (system.containsMeasure(time.measure)) return system.getXMmAt(time); return 0; } }