package com.xenoage.zong.io.midi.out.time;
import com.xenoage.zong.commands.core.music.MeasureAdd;
import com.xenoage.zong.commands.core.music.MeasureElementWrite;
import com.xenoage.zong.commands.core.music.PartAdd;
import com.xenoage.zong.core.Score;
import com.xenoage.zong.core.music.Part;
import com.xenoage.zong.core.music.VoiceElement;
import com.xenoage.zong.core.music.direction.DaCapo;
import com.xenoage.zong.core.music.direction.Dynamic;
import com.xenoage.zong.core.music.direction.DynamicValue;
import com.xenoage.zong.core.music.rest.Rest;
import com.xenoage.zong.core.music.time.TimeSignature;
import com.xenoage.zong.core.music.time.TimeType;
import com.xenoage.zong.core.music.volta.Volta;
import com.xenoage.zong.io.selection.Cursor;
import lombok.val;
import org.junit.Test;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.utils.math.Fraction._1;
import static com.xenoage.utils.math.Fraction._1$2;
import static com.xenoage.utils.math.Fraction.fr;
import static com.xenoage.zong.core.music.barline.Barline.barlineBackwardRepeat;
import static com.xenoage.zong.core.music.barline.Barline.barlineForwardRepeat;
import static com.xenoage.zong.core.music.barline.BarlineStyle.Regular;
import static com.xenoage.zong.core.position.MP.*;
import static com.xenoage.zong.core.position.Time.time;
import static com.xenoage.zong.io.midi.out.repetitions.RepetitionsFinder.findRepetitions;
import static junit.framework.TestCase.assertEquals;
/**
* Tests for {@link TimeMapper}.
*
* @author Andreas Wenger
*/
public class TimeMapperTest {
//ticks per quarter note
private static final int resolution = 8;
@Test public void createTimeMapTest() {
val score = getScore();
val expectedTimeMap = getTimeMap();
assertEquals(expectedTimeMap, new TimeMapper(score, findRepetitions(score),8).createTimeMap());
}
/**
* Creates the following score (each space is a quarter note)
*
* measures:
* repetitions: voltas/signs: 1───┐2───┐ d.c. senza rep
* barlines: | |: | :| |1 ||
* part 0: staff 0: voice 0: |1 |1 |2 2 |1 |1 ||
* voice 1: | |442 |42 4|1 d |1 || //d = direction
* staff 1: voice 0: |2 44|1 | |1 |1 ||
* part 1: staff 2: voice 0: |1 |1 |2 2 | |2 2 ||
*
* play ranges: 0---------------
* 1---- 2---------
* 3--------- 4---------
*/
private Score getScore() {
val score = new Score();
new PartAdd(score, new Part("p0", null, 2, null),
0, null).execute();
new PartAdd(score, new Part("p1", null, 1, null),
1, null).execute();
new MeasureAdd(score, 5).execute();
//time, barlines, voltas and signs
score.getColumnHeader(0).setTime(new TimeSignature(TimeType.time_4_4));
score.getColumnHeader(1).setStartBarline(barlineForwardRepeat(Regular));
score.getColumnHeader(2).setVolta(new Volta(1, range(1, 1), "1", true));
score.getColumnHeader(2).setEndBarline(barlineBackwardRepeat(Regular, 1));
score.getColumnHeader(3).setVolta(new Volta(1, range(2, 2), "2", true));
score.getColumnHeader(4).setNavigationOrigin(new DaCapo(false));
//staff 0, voice 0
val cursor = new Cursor(score, mp0, true);
cursor.write(e(1));
cursor.write(e(1));
cursor.write(e(2));
cursor.write(e(2));
cursor.write(e(1));
cursor.write(e(1));
//staff 0, voice 1
cursor.setMp(atElement(0, 1, 1, 0));
cursor.write(e(4));
cursor.write(e(4));
cursor.write(e(2));
cursor.write(e(4));
cursor.write(e(2));
cursor.write(e(4));
cursor.write(e(1));
cursor.write(e(1));
new MeasureElementWrite(new Dynamic(DynamicValue.f),
score.getMeasure(atMeasure(0, 3)), _1$2).execute();
//staff 1, voice 0
cursor.setMp(atElement(1, 0, 0, 0));
cursor.write(e(2));
cursor.write(e(4));
cursor.write(e(4));
cursor.write(e(1));
cursor.setMp(atElement(1, 2, 0, 0));
cursor.write(e(1));
cursor.write(e(1));
//staff 2, voice 0
cursor.setMp(atElement(2, 0, 0, 0));
cursor.write(e(1));
cursor.write(e(1));
cursor.write(e(2));
cursor.write(e(2));
cursor.setMp(atElement(2, 4, 0, 0));
cursor.write(e(2));
cursor.write(e(2));
return score;
}
/**
* See {@link #getScore()}.
* We use 8 ticks per quarter note.
*/
private TimeMap getTimeMap() {
val timeMap = new TimeMapBuilder();
//range 0
addTick(timeMap, 0, 0, 0);
addTick(timeMap, 0, 0, 2);
addTick(timeMap, 0, 0, 3);
addTick(timeMap, 0, 1, 0, true);
addTick(timeMap, 0, 1, 1);
addTick(timeMap, 0, 1, 2);
addTick(timeMap, 0, 2, 0, true);
addTick(timeMap, 0, 2, 1);
addTick(timeMap, 0, 2, 2);
addTick(timeMap, 0, 2, 3);
addTick(timeMap, 0, 3, 0, true);
//range 1
addTick(timeMap, 1, 1, 0);
addTick(timeMap, 1, 1, 1);
addTick(timeMap, 1, 1, 2);
addTick(timeMap, 1, 2, 0, true);
//range 2
addTick(timeMap, 2, 3, 0);
addTick(timeMap, 2, 3, 2);
addTick(timeMap, 2, 4, 0, true);
addTick(timeMap, 2, 4, 2);
addTick(timeMap, 2, 5, 0, true);
//range 3
addTick(timeMap, 3, 0, 0);
addTick(timeMap, 3, 0, 2);
addTick(timeMap, 3, 0, 3);
addTick(timeMap, 3, 1, 0, true);
addTick(timeMap, 3, 1, 1);
addTick(timeMap, 3, 1, 2);
addTick(timeMap, 3, 2, 0, true);
//range 4
addTick(timeMap, 4, 3, 0);
addTick(timeMap, 4, 3, 2);
addTick(timeMap, 4, 4, 0, true);
addTick(timeMap, 4, 4, 2);
addTick(timeMap, 4, 5, 0, true);
return timeMap.build();
}
private void addTick(TimeMapBuilder timeMap, int repetition, int measure, int quarterBeat) {
addTick(timeMap, repetition, measure, quarterBeat, false);
}
private void addTick(TimeMapBuilder timeMap, int repetition, int measure, int quarterBeat,
boolean isAlsoLastBeatInPreviosMeasure) {
int playRangeMeasureOffset = new int[]{0, 3, 4, 6, 8}[repetition];
int playRangeStartMeasure = new int[]{0, 1, 3, 0, 3}[repetition];
int internalMeasure = measure - playRangeStartMeasure;
if (isAlsoLastBeatInPreviosMeasure)
timeMap.addTimeNoMs(resolution * ((playRangeMeasureOffset + internalMeasure - 1) * 4 + 4),
new RepTime(repetition, time(measure - 1, _1)));
timeMap.addTimeNoMs(resolution * ((playRangeMeasureOffset + internalMeasure) * 4 + quarterBeat),
new RepTime(repetition, time(measure, fr(quarterBeat, 4))));
}
/**
* Creates a note. 4 = quarter, 2 = half, 1 = whole
*/
private VoiceElement e(int note) {
return new Rest(fr(1, note));
}
}