//----------------------------------------------------------------------------// // // // M e a s u r e I d // // // //----------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. // // This software is released under the GNU General Public License. // // Goto http://kenai.com/projects/audiveris to report bugs or suggestions. // //----------------------------------------------------------------------------// // </editor-fold> package omr.score.entity; import omr.score.Score; import omr.util.TreeNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ListIterator; /** * Class {@code MeasureId} is a non-mutable class meant to handle the * specificities of a measure id, since ids are recorded as {@link PageBased} * instances:<ol> * <li>Initial ids assigned are page-based and start from 1</li> * <li>Final ids, assigned by {@link omr.score.MeasureFixer}, are page-based, * start from 1 (or 0 for a pickup measure), and have a special value (Xn) for * second half repeats.</li> * <li>Ids, as displayed in score view or exported in MusicXML, combine * the page-based ids to provide score-based ids.</li> * <li>Ids, as used by {@link MeasureRange}, are {@link ScoreBased} * instances.</li> * </ol> */ public abstract class MeasureId { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger( MeasureId.class); /** Char prefix for a second half id */ protected static final char SH_CHAR = 'X'; /** String prefix for a second half id */ protected static final String SH_STRING = Character.toString(SH_CHAR); //~ Instance fields -------------------------------------------------------- /** Underlying numeric value */ protected final int value; /** Flag for second half */ protected final boolean secondHalf; //~ Constructors ----------------------------------------------------------- //-----------// // MeasureId // //-----------// /** * Creates a new MeasureId object. * * @param value The underlying numeric value * @param secondHalf True if this is a second repeat half */ private MeasureId (int value, boolean secondHalf) { this.value = value; this.secondHalf = secondHalf; } //~ Methods ---------------------------------------------------------------- //------------------// // createScoreBased // //------------------// /** * Creates a ScoreBased measure id object. * * @param strId the score-based id (as visible by the user) */ public static ScoreBased createScoreBased (Score score, String strId) { // Check syntax String str = strId.trim() .toUpperCase(); if ((str == null) || (str.length() == 0)) { throw new IllegalArgumentException("Null or empty Id string"); } char initial = str.charAt(0); boolean secondHalf = initial == SH_CHAR; int value = secondHalf ? Integer.parseInt(str.substring(1)) : Integer.parseInt(str); return new ScoreBased(score, value, secondHalf); } //-----------------// // retrieveMeasure // //-----------------// /** * Report the measure in the provided score, for which the id matches the * provided score-based id * * @param score the related score * @param scoreBasedId the score-based id to search for * @return the measure found, or null if not found */ public static Measure retrieveMeasure (Score score, ScoreBased scoreBasedId) { int scoreValue = scoreBasedId.value; String strId = scoreBasedId.toString(); Page page = retrievePage(score, scoreValue); if (page != null) { int pageIdOffset = score.getMeasureIdOffset(page); int pageValue = scoreValue - pageIdOffset; for (TreeNode sn : page.getSystems()) { ScoreSystem system = (ScoreSystem) sn; SystemPart part = system.getFirstPart(); if (pageValue <= part.getLastMeasure() .getIdValue()) { for (TreeNode mn : part.getMeasures()) { Measure measure = (Measure) mn; if ((pageValue == measure.getIdValue()) && strId.equals(measure.getScoreId())) { return measure; } } } } } return null; } //--------------// // retrievePage // //--------------// /** * Report the page that contains the measure with provided score-based id * value * * @param score the score at hand * @param scoreBasedIdValue the numeric value of score-based measure id * @return the containing page, or null if not found */ public static Page retrievePage (Score score, int scoreBasedIdValue) { for (ListIterator<TreeNode> pageIt = score.getPages() .listIterator( score.getPages().size()); pageIt.hasPrevious();) { Page page = (Page) pageIt.previous(); if (scoreBasedIdValue >= score.getMeasureIdOffset(page)) { return page; } } return null; } //~ Inner Classes ---------------------------------------------------------- //--------------// // MeasureRange // ========================================================= //--------------// /** * Class {@code MeasureRange} handles a range of measures score-based ids, * to ease the playing or the exporting of just a range of measures. */ public static class MeasureRange { //~ Instance fields ---------------------------------------------------- /** Related score */ private final Score score; /** Score-based id of first measure of the range */ private final ScoreBased firstId; /** Score-based id of last measure of the range */ private final ScoreBased lastId; //~ Constructors ------------------------------------------------------- /** * Create a MeasureRange instance from score-based ids * * @param firstScoreId score-based id of the first measure of the range * @param lastScoreId score-based id of the last measure of the range */ public MeasureRange (ScoreBased firstScoreId, ScoreBased lastScoreId) { if (firstScoreId.score != lastScoreId.score) { throw new IllegalArgumentException("Ids from different scores"); } this.score = firstScoreId.score; this.firstId = firstScoreId; this.lastId = lastScoreId; } /** * Create a MeasureRange instance from score-based ids provided as * strings * * @param score the related score * @param firstScoreId score-based id of the first measure of the range * @param lastScoreId score-based id of the last measure of the range */ public MeasureRange (Score score, String firstScoreId, String lastScoreId) { this( createScoreBased(score, firstScoreId), createScoreBased(score, lastScoreId)); } //~ Methods ------------------------------------------------------------ //----------// // contains // //----------// /** * Checks whether the provided page-based measure id is within the * measure range * * @param pageBasedId the page-based measure id to check * @return true if id is within the range, false otherwise */ public boolean contains (PageBased pageBasedId) { ScoreBased scoreBasedId = new ScoreBased(pageBasedId); return (scoreBasedId.compareTo(firstId) >= 0) && (scoreBasedId.compareTo(lastId) <= 0); } //--------// // equals // //--------// @Override public boolean equals (Object obj) { if (!(obj instanceof MeasureRange)) { return false; } else { MeasureRange that = (MeasureRange) obj; return (this.score == that.score) && this.firstId.equals(that.firstId) && this.lastId.equals(that.lastId); } } //------------// // getFirstId // //------------// /** * Report the id of the first measure in range * * @return the score-based id of first measure */ public ScoreBased getFirstId () { return firstId; } //---------------// // getFirstIndex // //---------------// /** * Report the score-based index of the first measure of the range * * @return the index (score-based) of the first measure */ public int getFirstIndex () { Measure measure = retrieveMeasure(score, firstId); return measure.getPageId() .getScoreIndex(); } //-----------// // getLastId // //-----------// /** * Report the id of the last measure in range * * @return the score-based id of last measure */ public ScoreBased getLastId () { return lastId; } //----------// // hashCode // //----------// @Override public int hashCode () { int hash = 3; hash = (89 * hash) + ((score != null) ? score.hashCode() : 0); hash = (89 * hash) + ((firstId != null) ? firstId.hashCode() : 0); hash = (89 * hash) + ((lastId != null) ? lastId.hashCode() : 0); return hash; } //----------// // toString // //----------// @Override public String toString () { StringBuilder sb = new StringBuilder(); sb.append("ids[") .append(firstId) .append("..") .append(lastId) .append("]"); return sb.toString(); } } //-----------// // PageBased // ============================================================ //-----------// /** * A page-based measure id */ public static class PageBased extends MeasureId { //~ Instance fields ---------------------------------------------------- /** The related measure */ protected final Measure measure; //~ Constructors ------------------------------------------------------- public PageBased (Measure measure, int value, boolean secondHalf) { super(value, secondHalf); this.measure = measure; } public PageBased (Measure measure, PageBased other) { this(measure, other.value, other.secondHalf); } //~ Methods ------------------------------------------------------------ //--------// // equals // //--------// @Override public boolean equals (Object obj) { if (!(obj instanceof PageBased)) { return false; } PageBased that = (PageBased) obj; return (this.measure == that.measure) && (this.value == that.value) && (this.secondHalf == that.secondHalf); } //---------------// // getScoreIndex // //---------------// /** * Report the score-based index of this measure * * @return the score-based measure index */ public int getScoreIndex () { Page page = measure.getPage(); Score score = page.getScore(); int offset = score.getMeasureOffset(page); for (TreeNode sn : page.getSystems()) { ScoreSystem system = (ScoreSystem) sn; SystemPart part = system.getFirstPart(); int measureCount = part.getMeasures() .size(); if (value < (offset + measureCount)) { return measure.getChildIndex() + offset; } else { offset += measureCount; } } // This should not happen logger.error( "Cannot retrieve score index of page-based measure id {}", this); return 0; // To keep the compiler happy } //----------// // hashCode // //----------// @Override public int hashCode () { int hash = 5; return hash; } //---------------// // toScoreString // //---------------// /** * Present the score-based display (even though the stored * value is page-based) * * @return [X]absId */ public String toScoreString () { Page page = measure.getPage(); int pageMeasureIdOffset = page.getScore() .getMeasureIdOffset(page); if (secondHalf) { return SH_STRING + (pageMeasureIdOffset + value); } else { return Integer.toString(pageMeasureIdOffset + value); } } //----------// // toString // //----------// @Override public String toString () { StringBuilder sb = new StringBuilder("*"); if (secondHalf) { sb.append(SH_STRING); } sb.append(value); return sb.toString(); } } //------------// // ScoreBased // =========================================================== //------------// /** * A score-based measure id */ public static class ScoreBased extends MeasureId implements Comparable<ScoreBased> { //~ Instance fields ---------------------------------------------------- /** The containing score */ private final Score score; //~ Constructors ------------------------------------------------------- public ScoreBased (Score score, int value, boolean secondHalf) { super(value, secondHalf); this.score = score; } public ScoreBased (PageBased pageBasedId) { this( pageBasedId.measure.getScore(), pageBasedId.value, pageBasedId.secondHalf); } //~ Methods ------------------------------------------------------------ //-----------// // compareTo // //-----------// @Override public int compareTo (ScoreBased that) { if (this.score != that.score) { throw new IllegalArgumentException( "Cannot compare ids from different scores"); } int deltaValue = this.value - that.value; if (deltaValue != 0) { return Integer.signum(deltaValue); } if (secondHalf && !that.secondHalf) { return 1; } if (!secondHalf && that.secondHalf) { return -1; } return 0; } //--------// // equals // //--------// @Override public boolean equals (Object obj) { if (!(obj instanceof ScoreBased)) { return false; } ScoreBased that = (ScoreBased) obj; return (this.value == that.value) && (this.secondHalf == that.secondHalf); } //----------// // hashCode // //----------// @Override public int hashCode () { int hash = 7; return hash; } //----------// // toString // //----------// /** * Present the score-based display * * @return [X]absId */ @Override public String toString () { return (secondHalf ? SH_STRING : "") + value; } } }