package com.xenoage.zong.io.midi.out.repetitions;
import com.xenoage.utils.kernel.Range;
import com.xenoage.utils.math.Fraction;
import com.xenoage.zong.commands.core.music.MeasureAdd;
import com.xenoage.zong.core.Score;
import com.xenoage.zong.core.ScoreFactory;
import com.xenoage.zong.core.music.barline.Barline;
import com.xenoage.zong.core.music.barline.BarlineStyle;
import com.xenoage.zong.core.music.direction.*;
import com.xenoage.zong.core.music.volta.Volta;
import com.xenoage.zong.core.position.Time;
import lombok.AllArgsConstructor;
import lombok.val;
import org.junit.Test;
import static com.xenoage.utils.collections.CList.ilist;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.utils.math.Fraction.*;
import static com.xenoage.zong.core.music.util.BeatE.beatE;
import static com.xenoage.zong.io.midi.out.repetitions.RepetitionsTest.repetition;
import static org.junit.Assert.*;
/**
* Tests for {@link RepetitionsFinder}.
*
* @author Andreas Wenger
*/
public class RepetitionsFinderTest {
@AllArgsConstructor
class TestCase {
Score score;
Repetitions expectedRepetitions;
}
/**
* Test case with no repeats at all:
*
* measures: | | | ||
* numbers: |0 |1 |2 ||
*/
@Test public void testSimple() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 2).execute();
val expectedRepetitions = new Repetitions(ilist(
repetition(0, 3)));
runTest(new TestCase(score, expectedRepetitions));
}
/**
* Test case with barline repeats, with the following repetitions:
*
* repeats: 2x
* measures: | :|: :| :|
* numbers: |0 |1 |2 |
*/
@Test public void testBarlines() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 2).execute();
writeBackwardRepeat(0, 1);
writeForwardRepeat(1);
writeBackwardRepeat(1, 2);
writeBackwardRepeat(2, 1);
val expectedRepetitions = new Repetitions(ilist(
repetition(0, 1),
repetition(0, 2),
repetition(1, 2),
repetition(1, 3),
repetition(1, 2),
repetition(1, 2),
repetition(1, 3)));
runTest(new TestCase(score, expectedRepetitions));
}
/**
* Test case with barline repeats, also within measures, with the following repetitions:
*
* repeats: 2x
* measures: | | /: :| :\ | /: :\ |
* numbers: |0 |1 |2 |3 |
*/
@Test public void testMiddleBarlines() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 3).execute();
writeMiddleForwardRepeat(1, _1$2);
writeBackwardRepeat(1, 1);
writeMiddleBackwardRepeat(2, _1$2, 2);
writeMiddleForwardRepeat(3, _1$4);
writeMiddleBackwardRepeat(3, _3$4, 1);
val expectedRepetitions = new Repetitions(ilist(
new Repetition(bp(0, _0), bp(2, _0)),
new Repetition(bp(1, _1$2), bp(2, _1$2)),
new Repetition(bp(1, _1$2), bp(2, _0)),
new Repetition(bp(1, _1$2), bp(2, _1$2)),
new Repetition(bp(1, _1$2), bp(2, _0)),
new Repetition(bp(1, _1$2), bp(3, _3$4)),
new Repetition(bp(3, _1$4), bp(4, _0))));
runTest(new TestCase(score, expectedRepetitions));
}
/**
* Test case with coda/segna/dacapo, with the following repetitions:
*
* senzarep conrep senzarep
* (tocoda) (dacapo) (coda) (segno) (dalsegno) (segno2) (dalsegno2)
* measures: | :| | | | | |: :| | | |: :| ||
* numbers: |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 ||
*/
@Test public void testNavigationSigns() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 11).execute();
writeBackwardRepeat(0, 1);
writeNavigationOrigin(0, new Coda());
writeNavigationOrigin(2, new DaCapo(false));
writeNavigationTarget(4, new Coda());
writeForwardRepeat(6);
writeNavigationTarget(6, new Segno());
writeBackwardRepeat(6, 1);
writeNavigationOrigin(7, new Segno());
writeForwardRepeat(10);
writeNavigationTarget(9, new Segno());
writeBackwardRepeat(10, 1);
writeNavigationOrigin(10, segno(false));
val expectedRepetitions = new Repetitions(ilist(
repetition(0, 1),
repetition(0, 3),
repetition(0, 1),
repetition(4, 7),
repetition(6, 8),
repetition(6, 7),
repetition(6, 11),
repetition(10, 11),
repetition(9, 12)));
runTest(new TestCase(score, expectedRepetitions));
}
/**
* Test case with voltas, with the following repetitions:
* ___ ___ ___ ____ ___ _____ _____
* voltas: 1 2 1 2 1 2+3 def
* measures: | | :| | | | :| |: | :| :| ||
* numbers: |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 ||
*/
@Test public void testVoltas() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 10).execute();
writeVolta(1, 1, range(1, 1));
writeBackwardRepeat(1, 1);
writeVolta(2, 1, range(2, 2));
writeVolta(5, 1, range(1, 1));
writeBackwardRepeat(5, 1);
writeVolta(6, 1, range(2, 2));
writeForwardRepeat(7);
writeVolta(8, 1, range(1, 1));
writeBackwardRepeat(8, 1);
writeVolta(9, 1, range(2, 3));
writeBackwardRepeat(9, 1); //should be 2, but 1 is also fine since within a volta we use this repeat sign implicitly
writeVolta(10, 1, null);
val expectedRepetitions = new Repetitions(ilist(
repetition(0, 2),
repetition(0, 1),
repetition(2, 6), //-> back to the very beginning (because of missing forward repeat)
repetition(0, 2),
repetition(0, 1),
repetition(2, 5),
repetition(6, 9),
repetition(7, 8),
repetition(9, 10),
repetition(7, 8),
repetition(9, 10),
repetition(7, 8),
repetition(10, 11)));
runTest(new TestCase(score, expectedRepetitions));
}
/**
* Test case with voltas and repeats, but with a senza repetizione da capo:
* ___ ___ ___ ____
* voltas: 1 2 1 2 d.c. senza rep
* measures: | | :| |: | :|: | :| ||
* numbers: |0 |1 |2 |3 |4 |5 |6 |7 ||
*/
@Test public void testSenzaRep() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 7).execute();
writeVolta(1, 1, range(1, 1));
writeBackwardRepeat(1, 1);
writeVolta(2, 1, range(2, 2));
writeForwardRepeat(3);
writeBackwardRepeat(4, 1);
writeForwardRepeat(5);
writeVolta(6, 1, range(1, 1));
writeBackwardRepeat(6, 1);
writeVolta(7, 1, range(2, 2));
writeNavigationOrigin(7, new DaCapo(false));
val expectedRepetitions = new Repetitions(ilist(
repetition(0, 2),
repetition(0, 1),
repetition(2, 5),
repetition(3, 7),
repetition(5, 6),
repetition(7, 8),
repetition(0, 1),
repetition(2, 6),
repetition(7, 8)));
runTest(new TestCase(score, expectedRepetitions));
}
/**
* Test case with repeats within voltas:
* _______________ ___
* voltas: 1 2
* measures: | | |: | :| :| ||
* numbers: |0 |1 |2 |3 |4 |5 ||
*/
@Test public void testRepeatsWithinVoltas() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 5).execute();
writeVolta(1, 4, range(1, 1));
writeForwardRepeat(2);
writeBackwardRepeat(3, 1);
writeBackwardRepeat(4, 1);
writeVolta(5, 1, range(2, 2));
val expectedRepetitions = new Repetitions(ilist(
repetition(0, 4),
repetition(2, 5),
repetition(0, 1),
repetition(5, 6)));
runTest(new TestCase(score, expectedRepetitions));
}
/**
* A more advanced test case with the following repetitions:
* ____ ________ ________________ ___ ___
* voltas/repeats: 2x 1+2 3 4 senzarep 1 2
* measures: | |(segno) |: | | :| | |(tocoda) |: | :| | :| | (dalsegno)|(coda) |: | :| ||
* numbers: |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |15 |16 |17 ||
*/
@Test public void testAdvanced() {
score = ScoreFactory.create1Staff();
new MeasureAdd(score, 17).execute();
writeNavigationTarget(1, new Segno());
writeForwardRepeat(2);
writeBackwardRepeat(4, 2);
writeNavigationOrigin(6, new Coda());
writeForwardRepeat(8);
writeVolta(9, 1, range(1, 2));
writeBackwardRepeat(9, 2);
writeVolta(10, 2, range(3, 3));
writeBackwardRepeat(11, 1);
writeVolta(12, 2, range(4, 4));
writeNavigationOrigin(13, segno(false));
writeNavigationTarget(14, new Coda());
writeForwardRepeat(15);
writeVolta(16, 1, range(1, 1));
writeBackwardRepeat(16, 1);
writeVolta(17, 1, range(2, 2));
val expectedRepetitions = new Repetitions(ilist(
repetition(0, 5),
repetition(2, 5),
repetition(2, 10),
repetition(8, 10),
repetition(8, 9),
repetition(10, 12),
repetition(8, 9),
repetition(12, 14), //-> dal segno senza rep
repetition(1, 7),
repetition(14, 17),
repetition(15, 16),
repetition(17, 18)));
runTest(new TestCase(score, expectedRepetitions));
}
private void runTest(TestCase testCase) {
val reps = RepetitionsFinder.findRepetitions(testCase.score);
assertEquals(testCase.expectedRepetitions, reps);
}
Score score;
private void writeForwardRepeat(int measure) {
score.getColumnHeader(measure).setStartBarline(Barline.barlineForwardRepeat(BarlineStyle.HeavyLight));
}
private void writeMiddleForwardRepeat(int measure, Fraction beat) {
score.getColumnHeader(measure).getMiddleBarlines().add(beatE(
Barline.barlineForwardRepeat(BarlineStyle.Dashed), beat));
}
private void writeBackwardRepeat(int measure, int repeatTimes) {
score.getColumnHeader(measure).setEndBarline(Barline.barlineBackwardRepeat(BarlineStyle.LightHeavy, repeatTimes));
}
private void writeMiddleBackwardRepeat(int measure, Fraction beat, int repeatTimes) {
score.getColumnHeader(measure).getMiddleBarlines().add(beatE(
Barline.barlineBackwardRepeat(BarlineStyle.Dashed, repeatTimes), beat));
}
private void writeNavigationTarget(int measure, NavigationSign sign) {
score.getColumnHeader(measure).setNavigationTarget(sign);
}
private void writeNavigationOrigin(int measure, NavigationSign sign) {
score.getColumnHeader(measure).setNavigationOrigin(sign);
}
private void writeVolta(int measure, int length, Range range) {
score.getColumnHeader(measure).setVolta(new Volta(length, range, ""+range, false));
}
private Segno segno(boolean conRep) {
val ret = new Segno();
ret.setWithRepeats(conRep);
return ret;
}
private Time bp(int measure, Fraction beat) {
return Time.time(measure, beat);
}
}