package com.xenoage.zong.core.header; import com.xenoage.utils.annotations.MaybeNull; import com.xenoage.utils.annotations.NonNull; import com.xenoage.zong.core.Score; import com.xenoage.zong.core.format.Break; import com.xenoage.zong.core.format.StaffLayout; import com.xenoage.zong.core.format.SystemLayout; import com.xenoage.zong.core.music.layout.SystemBreak; import com.xenoage.zong.core.music.time.TimeSignature; import com.xenoage.zong.core.position.MP; import com.xenoage.zong.utils.exceptions.IllegalMPException; import lombok.Data; import java.util.ArrayList; import java.util.List; import static com.xenoage.utils.collections.CollectionUtils.setExtend; import static com.xenoage.utils.kernel.Range.rangeReverse; /** * This class contains general information * about the musical data in a score. * * It contains a list of elements that are used in all staves. * Such elements are key signatures, time signatures and * tempo changes for example. * * There is also layout information, like system and * page breaks and system and staff distances. * * @author Andreas Wenger * @author Uli Teschemacher */ @Data public final class ScoreHeader { /** The parent score. */ @NonNull private final Score parent; /** List of system layouts. May contain null elements for standardly layouted systems. */ @NonNull private List<SystemLayout> systemLayouts; /** List of system layouts. Must have the same size as the number of measures in the parent score. */ @NonNull private List<ColumnHeader> columnHeaders; /** * Creates an empty {@link ScoreHeader}. */ public static ScoreHeader scoreHeader(Score parent) { return new ScoreHeader(parent, new ArrayList<>(), new ArrayList<>()); } /** * Gets layout information for the staff with the given index * within the system with the given index, or null if undefined. */ @MaybeNull public StaffLayout getStaffLayout(int systemIndex, int staffIndex) { SystemLayout systemLayout = getSystemLayout(systemIndex); if (systemLayout != null) { return systemLayout.getStaffLayout(staffIndex); } return null; } /** * Gets layout information for the system with the given index, * or null if undefined. */ @MaybeNull public SystemLayout getSystemLayout(int systemIndex) { if (systemIndex >= 0 && systemIndex < systemLayouts.size()) { return systemLayouts.get(systemIndex); } return null; } /** * Gets the system index of the measure with the given index. * Only forced system breaks are considered. */ public int getSystemIndex(int measureIndex) { int ret = 0; for (int i = 0; i <= measureIndex; i++) { Break br = getColumnHeader(i).getMeasureBreak(); if (br != null && br.getSystemBreak() == SystemBreak.NewSystem) ret++; } return ret; } /** * Sets the {@link SystemLayout} with the given index. */ public void setSystemLayout(int systemIndex, SystemLayout systemLayout) { setExtend(systemLayouts, systemIndex, systemLayout, null); } /** * Gets the {@link ColumnHeader} at the given index, or null if there is none. */ public ColumnHeader getColumnHeader(int measureIndex) { if (measureIndex >= columnHeaders.size()) { throw new IllegalMPException(MP.atMeasure(measureIndex)); } return columnHeaders.get(measureIndex); } /** * Sets the {@link ColumnHeader} for the measure column with the given index. */ public void setColumnHeader(ColumnHeader columnHeader, int measureIndex) { setExtend(columnHeaders, measureIndex, columnHeader, null); } /** * Adds the given number of empty measure columns. */ public void addEmptyMeasures(int measuresCount) { for (int i = 0; i < measuresCount; i++) { columnHeaders.add(new ColumnHeader(parent, columnHeaders.size())); } } /** * Gets the last {@link TimeSignature} that is defined at or before the measure * with the given index. If there is none, {@link TimeSignature#implicitSenzaMisura} returned. * @param measureIndex the index of the measure. May also be 1 measure after the last measure. */ public TimeSignature getTimeAtOrBefore(int measureIndex) { if (measureIndex == columnHeaders.size()) measureIndex--; //search for last time for (int iMeasure : rangeReverse(measureIndex, 0)) { ColumnHeader column = columnHeaders.get(iMeasure); if (column.getTime() != null) return column.getTime(); } //no key found. return default time. return TimeSignature.implicitSenzaMisura; } }