package com.xenoage.zong.core.music.volta;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import com.xenoage.utils.kernel.Range;
import com.xenoage.zong.core.header.ColumnHeader;
import com.xenoage.zong.core.music.ColumnElement;
import com.xenoage.zong.core.music.MusicElementType;
import com.xenoage.zong.core.position.MP;
/**
* Class for a volta (also called "ending" in MusicMXL, and informally called "Haus" in German).
*
* A volta is never used within a voice, but only in the {@link ColumnHeader}.
* Voltas span over whole measures, at least one. The number of spanned
* measures is saved in this class.
*
* Each volta has a numbers attribute, which is a range which
* tells in which repetitions it should be entered,
* and optionally an arbitrary caption.
* A downward hook on the right side is optional.
*
* The rules for ordering consecutive voltas (called a volta group) are as follows:
* The voltas should sorted (even this is no technical restriction), e.g.
* the "2nd time" volta should not appear before the "1st time" volta.
* Gaps can be filled with default voltas (= voltas without numbers), e.g. a default
* volta between a "1st time" and "4th time" volta is played two times. If a "4th time"
* volta follows a "1st time" volta directly, the "1st time" volta is played 3 times.
* When the last volta of a group is a default volta, it is always played (the very last time).
*
* @author Andreas Wenger
* @author Uli Teschemacher
*/
@Data @EqualsAndHashCode(exclude="parent") @ToString(exclude="parent")
public final class Volta
implements ColumnElement {
public static final String dash = "–";
/** The number of measures this volta spans, at least one. */
private int length;
/** The repetitions (beginning with 1) where this volta is entered, or null for the default case.
* E.g. [1,1] for the 1st time, ..., null for "else". */
private Range numbers;
/** The caption, or null to use a default caption, generated from the numbers:
* [x,x] results to "x.", [x,y] to "x.–y.", null to "". */
private String caption;
/** True, iff there is a downward hook on the right side. */
private boolean rightHook;
/** The parent measure column, or null if not part of a score. */
private ColumnHeader parent;
public Volta(int length, Range numbers, String caption, boolean rightHook) {
if (length < 1)
throw new IllegalArgumentException("Volta must span at least 1 measure");
this.length = length;
this.numbers = numbers;
this.caption = caption;
this.rightHook = rightHook;
}
/**
* Gets the caption of this volta.
* This is never null, but may be the empty string.
*/
public String getCaption() {
if (caption != null)
return caption;
else if (numbers == null)
return "";
else if (numbers.getCount() == 1)
return numbers.getStart() + ".";
else
return numbers.getStart() + "." + dash + numbers.getStop() + ".";
}
/**
* Gets the caption of this volta, or null if unset.
*/
public String getCaptionOrNull() {
return caption;
}
/**
* Returns true, if this volta is the default case,
* i.e. not for an explicit repeat time like 1st or 2nd time.
*/
public boolean isDefault() {
return numbers == null;
}
@Override public MusicElementType getMusicElementType() {
return MusicElementType.Volta;
}
@Override public MP getMP() {
return MP.getMPFromParent(this);
}
/**
* Gets the index of the measure, where this volta ends (inclusive).
*/
public int getEndMeasureIndex() {
return getMP().measure + length - 1;
}
}