package com.xenoage.zong.musiclayout; import com.xenoage.utils.annotations.MaybeEmpty; import com.xenoage.utils.math.geom.Point2f; import com.xenoage.zong.core.Score; import com.xenoage.zong.core.position.MP; import com.xenoage.zong.io.selection.ScoreSelection; import com.xenoage.zong.musiclayout.settings.LayoutSettings; 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.symbols.SymbolPool; import lombok.ToString; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import static com.xenoage.utils.kernel.Range.range; /** * A {@link ScoreLayout} stores the layout of a score, * distributed across a list of {@link ScoreFrameLayout}s. * * @author Andreas Wenger */ @ToString public class ScoreLayout { /** The score */ public final Score score; /** The musical layouts of the frames */ public final List<ScoreFrameLayout> frames; /** The used pool of symbols */ public final SymbolPool symbolPool; /** The used layout settings */ public final LayoutSettings layoutSettings; /** * Creates a {@link ScoreLayout} with the given frame layouts. * @param score the score which is shown * @param frames a list of frame layouts (may be empty) * @param symbolPool the used pool of symbols * @param layoutSettings the used layout settings */ public ScoreLayout(Score score, @MaybeEmpty List<ScoreFrameLayout> frames, SymbolPool symbolPool, LayoutSettings layoutSettings) { this.score = score; this.frames = frames; this.symbolPool = symbolPool; this.layoutSettings = layoutSettings; } /** * Computes and returns the appropriate musical position * to the given metric position, or null, if unknown. */ public MP getMP(ScoreLayoutPos coordinates) { if (coordinates == null) return null; //first test, if there is a staff element. ScoreFrameLayout frame = frames.get(coordinates.frameIndex); StaffStamping staff = frame.getStaffStampingAt(coordinates.pMm); //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 = coordinates.pMm.x - staff.positionMm.x; return staff.getMpAtX(posX); } } /** * Computes and returns the {@link StaffStampingPosition} * at the given musical position. */ public StaffStampingPosition getStaffStampingPosition(MP mp) { //go through all staff stampings and look for the given staff index //and measure index //TODO: find a much nicer way to do this, e.g. by storing an allocation //table for [staff index, measure index] -> [staff stamping] //within the layout for (int iFrame = 0; iFrame < frames.size(); iFrame++) { ScoreFrameLayout frame = frames.get(iFrame); for (StaffStamping s : frame.getStaffStampings()) { if (s.getStaffIndex() == mp.staff && s.system.containsMeasure(mp.measure)) { //we found it. return staff and position float posX = s.system.getXMmAt(mp.getTime()); return new StaffStampingPosition(s, iFrame, posX); } } } //we found nothing. return null return null; } /** * Computes the index of the frame containing the measure with the * given global index. If not found, -1 is returned. */ public int getFrameIndexOf(int measure) { //go through all frames for (int iFrame : range(frames)) { FrameSpacing frameArr = frames.get(iFrame).getFrameSpacing(); if (frameArr.getStartMeasure() <= measure && frameArr.getEndMeasure() >= measure) return iFrame; } //we found nothing return -1; } /** * Computes the local index of the system (relative to the frame, * thus beginning at 0 for each frame) within the frame with the given index, * containing the measure with the given global index. If not found, -1 is returned. */ public int getSystemIndexOf(int frame, int measure) { FrameSpacing frameArr = frames.get(frame).getFrameSpacing(); //go through all systems of this frame for (int iSystem : range(frameArr.systems)) { SystemSpacing system = frameArr.systems.get(iSystem); if (system.containsMeasure(measure)) return iSystem; } //we found nothing return -1; } /** * Updates the selection stampings of this layout, according to the * given {@link ScoreSelection}. */ public void updateSelections(ScoreSelection selection) { //selections ArrayList<LinkedList<Stamping>> selections = new ArrayList<>( this.frames.size()); for (int i = 0; i < this.frames.size(); i++) { selections.add(new LinkedList<>()); } /* TODO if (selection != null && selection instanceof Cursor) { Cursor cursor = (Cursor) selection; StaffStampingPosition ssp = computeStaffStampingPosition(cursor.getMP()); if (ssp != null) { StaffCursorStamping scs = new StaffCursorStamping(ssp.getStaff(), ssp.getPositionX(), -0.5f); selections.get(ssp.getFrameIndex()).add(scs); } } */ //stampings for (int i = 0; i < this.frames.size(); i++) { //TODO //this.frames.get(i).setSelectionStampings(selections.get(i)); } } /** * Creates an layout for showing an error. * This can be used if the layouter could not finish its work successfully. * Currently, the error layout is completely empty.. */ public static ScoreLayout createErrorLayout(Score score, SymbolPool symbolPool) { return new ScoreLayout(score, new ArrayList<>(), symbolPool, null); } /** * Gets the {@link ScoreFrameLayout} that contains the given measure, * or null, if not found. */ public ScoreFrameLayout getScoreFrameLayout(int measure) { for (ScoreFrameLayout frame : frames) { FrameSpacing spacing = frame.getFrameSpacing(); if (measure >= spacing.getStartMeasure() && measure <= spacing.getEndMeasure()) return frame; } return null; } /** * Computes the {@link ScoreLayoutPos} of the given {@link MP} at the given line position. * If not found, null is returned. */ public ScoreLayoutPos getScoreLP(MP mp, float lp) { int iFrame = getFrameIndexOf(mp.measure); if (iFrame > -1) { ScoreFrameLayout sfl = frames.get(iFrame); StaffStamping ss; if (mp.staff != MP.unknown) ss = sfl.getStaffStamping(mp.staff, mp.measure); else ss = sfl.getStaffStamping(0, mp.measure); if (ss != null) { float x = ss.positionMm.x + ss.system.getXMmAt(mp.getTime()); float y = ss.computeYMm(lp); return new ScoreLayoutPos(iFrame, new Point2f(x, y)); } } return null; } }