package com.xenoage.zong.core.music.slur;
import com.xenoage.utils.math.VSide;
import com.xenoage.zong.core.music.MusicElement;
import com.xenoage.zong.core.music.MusicElementType;
import com.xenoage.zong.core.music.WaypointPosition;
import com.xenoage.zong.core.music.chord.Chord;
import lombok.Data;
import java.util.ArrayList;
import static com.xenoage.utils.CheckUtils.checkArgsNotNull;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.utils.kernel.Range.range;
/**
* A slur or tie connecting two notes,
* represented by two {@link SlurWaypoint}s.
*
* Formerly, this class was called "curved line", inspired by Wikipedia,
* which states that "a slur is denoted with a curved line",
* see http://en.wikipedia.org/wiki/Slur_(music) .
* But "slur" seems to be a more intuitive naming, even if this
* class represents both slurs and ties.
*
* @author Andreas Wenger
*/
@Data public final class Slur
implements MusicElement {
/** Slur or tie. */
private SlurType type;
/** The waypoints of the slur (at least two). */
private ArrayList<SlurWaypoint> waypoints;
/** The vertical side of the slur, or null for default. */
private VSide side;
/**
* Creates a new {@link Slur}.
* @param start the left end point of the curved line
* @param stop the right end point of the curved line
*/
public Slur(SlurType type, SlurWaypoint start, SlurWaypoint stop, VSide side) {
checkArgsNotNull(type, start, stop);
this.type = type;
this.waypoints = alist(start, stop);
this.side = side;
}
public Slur(SlurType type, ArrayList<SlurWaypoint> waypoints, VSide side) {
if (waypoints.size() < 2)
throw new IllegalArgumentException("At least two waypoints are needed to create a slur!");
this.type = type;
this.waypoints = waypoints;
this.side = side;
}
public SlurWaypoint getStart() {
return waypoints.get(0);
}
public SlurWaypoint getStop() {
return waypoints.get(waypoints.size() - 1);
}
public SlurWaypoint getWaypoint(Chord chord) {
for (SlurWaypoint wp : waypoints) {
if (chord == wp.getChord()) {
return wp;
}
}
throw new IllegalArgumentException("Given chord is not part of this slur.");
}
public WaypointPosition getWaypointPosition(Chord chord) {
if (chord == getStart().getChord()) {
return WaypointPosition.Start;
}
else if (chord == getStop().getChord()) {
return WaypointPosition.Stop;
}
else {
for (int i : range(1, waypoints.size() - 2)) {
if (chord == waypoints.get(i).getChord()) {
return WaypointPosition.Continue;
}
}
throw new IllegalArgumentException("Given chord is not part of this slur.");
}
}
/**
* Replaces the given chord.
*/
public void replaceChord(Chord oldChord, Chord newChord) {
for (int i : range(waypoints)) {
SlurWaypoint wp = waypoints.get(i);
if (wp.getChord() == oldChord) {
//try to stay on the same note index, if possible
wp.setNoteIndex(Math.min(wp.getNoteIndex(), newChord.getNotes().size()));
wp.setChord(newChord);
return;
}
}
throw new IllegalArgumentException("Given chord is not part of this beam");
}
@Override public MusicElementType getMusicElementType() {
return MusicElementType.Slur;
}
}