package com.xenoage.zong.core.position; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.Wither; import com.xenoage.utils.math.Fraction; import com.xenoage.zong.core.Score; import com.xenoage.zong.core.music.MusicElement; import com.xenoage.zong.core.music.Voice; import static com.xenoage.utils.math.Fraction._0; import static com.xenoage.zong.core.position.Time.time; /** * Musical Position within a score. * * It can contain a staff, measure, voice index, beat and element index. * * All values may be {@link #unknown} (or {@link #unknownBeat} for the beat). * The element index is only used for elements within a voice, otherwise its value is {@link #unknown}. * * @author Andreas Wenger */ @Data @AllArgsConstructor public class MP implements Comparable<MP> { /** The staff index, or {@link #unknown}. */ @Wither public final int staff; /** The measure index, or {@link #unknown}. */ @Wither public final int measure; /** The voice index, or {@link #unknown}. */ @Wither public final int voice; /** The beat, or {@link #unknownBeat}. */ @Wither public final Fraction beat; /** The element index within the voice, or {@link #unknown}. */ @Wither public final int element; /** Special value for an unknown beat. */ public static final Fraction unknownBeat = null; /** Special value for an unknown index. */ public static final int unknown = -1; /** Unknown MP. */ public static final MP unknownMp = new MP(unknown, unknown, unknown, unknownBeat, unknown); /** Musical position with all values set to 0, including beat and element index. */ public static final MP mp0 = new MP(0, 0, 0, _0, 0); /** Musical position with all values set to 0, including beat ("b" in name), but element index set to {@link #unknown}. */ public static final MP mpb0 = new MP(0, 0, 0, _0, unknown); /** Musical position with all values set to 0, including element ("e" in name), but beat set to {@link #unknownBeat}. */ public static final MP mpe0 = new MP(0, 0, 0, unknownBeat, 0); /** * Creates a new musical position at the given staff, measure, voice, element index and beat. */ public static MP mp(int staff, int measure, int voice, Fraction beat, int element) { return new MP(staff, measure, voice, beat, element); } /** * Creates a new musical position with the given measure and the * other values set to unknown. */ public static MP atMeasure(int measure) { return new MP(unknown, measure, unknown, unknownBeat, unknown); } /** * Creates a new musical position with the given staff and the * other values set to unknown. */ public static MP atStaff(int staff) { return new MP(staff, unknown, unknown, unknownBeat, unknown); } /** * Creates a new musical position with the given staff and measure and the * voice set to unknown. */ public static MP atMeasure(int staff, int measure) { return new MP(staff, measure, unknown, unknownBeat, unknown); } /** * Creates a new musical position with the given voice and the * other values set to unknown. */ public static MP atVoice(int voice) { return new MP(unknown, unknown, voice, unknownBeat, unknown); } /** * Creates a new musical position at the given staff, measure and voice. */ public static MP atVoice(int staff, int measure, int voice) { return new MP(staff, measure, voice, unknownBeat, unknown); } /** * Creates a new musical position at the given beat. */ public static MP atBeat(Fraction beat) { return new MP(unknown, unknown, unknown, beat, unknown); } /** * Creates a new musical position at the given staff, measure, voice and beat. */ public static MP atBeat(int staff, int measure, int voice, Fraction beat) { return new MP(staff, measure, voice, beat, unknown); } /** * Creates a new musical position at the given element index. */ public static MP atElement(int element) { return new MP(unknown, unknown, unknown, unknownBeat, element); } /** * Creates a new musical position at the given staff, measure, voice and element index. */ public static MP atElement(int staff, int measure, int voice, int element) { return new MP(staff, measure, voice, unknownBeat, element); } /** * Creates a new musical position with the given measure and beat * and the other values set to unknown. */ public static MP atColumnBeat(int measure, Fraction beat) { return new MP(unknown, measure, unknown, beat, unknown); } public boolean isMeasureUnknown() { return measure == unknown; } public boolean isStaffOrMeasureUnknown() { return staff == unknown || measure == unknown; } public boolean isStaffOrMeasureOrVoiceUnknown() { return staff == unknown || measure == unknown || voice == unknown; } public boolean isStaffOrMeasureOrVoiceOrBeatUnknown() { return staff == unknown || measure == unknown || voice == unknown || beat == unknownBeat; } public boolean isStaffOrMeasureOrVoiceOrElementUnknown() { return staff == unknown || measure == unknown || voice == unknown || element == unknown; } public void requireStaffAndMeasureAndVoiceAndElement() { if (isStaffOrMeasureOrVoiceOrElementUnknown()) throw new IllegalStateException("Missing MP data. Required: staff, measure, voice, element. In: " + toString()); } /** * Gets the {@link MP} of the given element, or null if it is unknown. */ public static MP getMP(MPElement element) { if (element == null) return null; return element.getMP(); } /** * Gets the {@link MP} of the given element by querying its parent, or null if it is unknown. * TODO: When Java 8 can be used for the whole project, use this method as the default * implementation for MPElement.getMP(). */ public static MP getMPFromParent(MPElement element) { if (element == null || element.getParent() == null) return null; return element.getParent().getChildMP(element); } /** * Gets the {@link MP} of the given element, or null if it is no MPElement, or * if its parent is null or if the parent is not part of a score. */ public static MP getMP(MusicElement element) { if (element instanceof MPElement) return getMP((MPElement) element); else return null; } /** * Returns the measure and beat of this {@link MP} as a {@link Time}. */ public Time getTime() { return time(measure, beat); } @Override public String toString() { return "[" + (staff != unknown ? "Staff = " + staff + ", " : "") + (measure != unknown ? "Measure = " + measure + ", " : "") + (voice != unknown ? "Voice = " + voice + ", ": "") + (beat != unknownBeat ? "Beat = " + beat.getNumerator() + "/" + beat.getDenominator() + ", " : "") + (element != unknown ? "Element = " + element : "") + "]"; } /** * Compares this {@link MP} with the given one. * It is compared by staff, measure, voice, beat or element index. * If the beats are known, only those are compared, and if they are unknown, * only the element indices are compared. * None of the other values should be unknown, otherwise the result is undefined. */ @Override public int compareTo(MP mp) { //staff if (staff < mp.staff) return -1; else if (staff > mp.staff) return 1; else { //measure if (measure < mp.measure) return -1; else if (measure > mp.measure) return 1; else { //voice if (voice < mp.voice) return -1; else if (voice > mp.voice) return 1; else if (beat != null && mp.beat != null) { //beat return beat.compareTo(mp.beat); } else { //element index if (element < mp.element) return -1; else if (element > mp.element) return 1; else return 0; } } } } /** * Compares the time of this {@link MP} with the given one. * It is compared by measure, and then by beat or element index. * If the beats are known, only those are compared, and if they are unknown, * only the element indices are compared. */ public int compareTimeTo(MP mp) { //measure if (measure < mp.measure) return -1; else if (measure > mp.measure) return 1; else { if (beat != null && mp.beat != null) { //beat return beat.compareTo(mp.beat); } else { //element index if (element < mp.element) return -1; else if (element > mp.element) return 1; else return 0; } } } /** * Returns this {@link MP} but also with the {@link #beat} field. * For this, the {@link #element} index must be known and the score must be given. */ public MP getWithBeat(Score score) { Voice voice = score.getVoice(this); Fraction beat = voice.getBeat(element); return withBeat(beat); } }