package com.xenoage.zong.io.midi.out.repetitions;
import com.xenoage.utils.annotations.MaybeNull;
import com.xenoage.utils.collections.IList;
import com.xenoage.utils.kernel.Range;
import com.xenoage.zong.core.music.volta.Volta;
import lombok.AllArgsConstructor;
import lombok.val;
import static com.xenoage.utils.kernel.Range.range;
import static java.lang.Math.max;
/**
* A group of consecutive {@link Volta}s with their start measure indices.
* Use the {@link VoltaGroupFinder} to get volta groups from a score.
*
* See {@link Volta} for more details and ordering rules.
*
* @author Andreas Wenger
*/
public class VoltaGroup {
@AllArgsConstructor
public static class VoltaStartMeasure {
Volta volta;
int startMeasure;
}
//the voltas and their start measure indices.
final IList<VoltaStartMeasure> voltasStartMeasures;
//the index of the first measure
final int startMeasure;
//the index of the last measure (inclusive)
final int endMeasure;
VoltaGroup(IList<VoltaStartMeasure> voltasStartMeasures) {
this.voltasStartMeasures = voltasStartMeasures;
startMeasure = voltasStartMeasures.getFirst().startMeasure;
endMeasure = startMeasure + getMeasuresCount() - 1;
}
/**
* Returns the measure numbers of the measures that
* have to be played in the volta, when the playback reaches the
* volta block the given time (1-based), or null if no appropriate volta
* can be found.
* @deprecated unneeded
*/
@MaybeNull private Range getRange(int repeatTime) {
val v = findVolta(repeatTime);
if (v != null)
return range(v.startMeasure, v.startMeasure + v.volta.getLength() - 1);
else
return null;
}
/**
* Returns whether this time is the last time to repeat.
* @deprecated unneeded
*/
private boolean isLastTime(int repeatTime) {
return (repeatTime == getRepeatCount());
}
/**
* Gets the number of repetitions.
* See {@link Volta} for the playback rules.
* Notice, that the actual playback could be different,
* e.g. when a volta contains a jump (e.g. coda) to another
* measure.
*/
public int getRepeatCount() {
//find maximum repeat number (+1, if the last volta is a default volta)
int maxRepeatTime = 0;
for (val v : voltasStartMeasures) {
if (false == v.volta.isDefault())
maxRepeatTime = max(maxRepeatTime, v.volta.getNumbers().getStop());
else
maxRepeatTime = max(maxRepeatTime, 1); //enter default volta at least one time
}
if (voltasStartMeasures.size() > 1 && //when there is at least one other volta before a final default volta
voltasStartMeasures.getLast().volta.isDefault()) {
maxRepeatTime += 1;
}
return maxRepeatTime;
}
/**
* Gets the volta and start measure where to jump into when
* the volta group is reached the given time.
* When there is no repeat for the given time, null is returned.
*/
@MaybeNull public VoltaStartMeasure findVolta(int repeatTime) {
if (repeatTime < 1 || repeatTime > getRepeatCount())
return null;
//find explicit number in voltas first
for (val v : voltasStartMeasures) {
if (false == v.volta.isDefault() && v.volta.getNumbers().isInRange(repeatTime))
return v;
}
//otherwise use first default volta after the last matching one
VoltaStartMeasure last = null;
for (val v : voltasStartMeasures) {
if (v.volta.isDefault())
last = v;
else if (v.volta.getNumbers().getStart() >= repeatTime)
break;
}
if (last != null)
return last;
//otherwise use the last volta before the searched one
for (val v : voltasStartMeasures) {
if (v.volta.getNumbers().getStart() >= repeatTime)
break;
last = v;
}
if (last != null)
return last;
//none found
return null;
}
/**
* Returns the number of measures in the group.
*/
public int getMeasuresCount() {
int length = 0;
for (val v : voltasStartMeasures)
length += v.volta.getLength();
return length;
}
private int getStartMeasure(Volta volta) {
for (val v : voltasStartMeasures)
if (v.volta == volta)
return v.startMeasure;
throw new IllegalStateException("unknown volta");
}
}