package com.xenoage.zong.io.midi.out.time; import com.xenoage.utils.math.Fraction; import com.xenoage.zong.core.Score; import com.xenoage.zong.io.midi.out.MidiSettings; import com.xenoage.zong.io.midi.out.repetitions.Repetition; import com.xenoage.zong.io.midi.out.repetitions.Repetitions; import lombok.AllArgsConstructor; import lombok.val; import java.util.List; import static com.xenoage.utils.collections.CollectionUtils.alist; import static com.xenoage.utils.kernel.Range.range; import static com.xenoage.zong.core.position.Time.time; /** * Creates a {@link TimeMap} for the whole score. * * @author Andreas Wenger */ @AllArgsConstructor public class TimeMapper { /** The score, for which the map is to be computed. */ Score score; /** The precomputed {@link Repetitions} in this score (repeats barlines, voltas, segnos...). */ Repetitions repetitions; /** See {@link MidiSettings#resolutionFactor}. */ int resolutionFactor; public TimeMap createTimeMap() { //resolution per quarter note int resolution = score.getDivisions() * resolutionFactor; //for each play range compute the used beats in each measure column int tick = 0; val timeMap = new TimeMapBuilder(); for (int iRep : range(repetitions)) { val range = repetitions.get(iRep); for (int iMeasure : range(range.start.measure, range.end.measure)) { val usedBeats = getUsedBeats(iMeasure, range); Fraction lastBeat = null; for (int iBeat : range(usedBeats)) { val beat = usedBeats.get(iBeat); //increase tick by distance between last and this beat if (lastBeat != null) tick += beat.sub(lastBeat).mult(resolution * 4).getNumerator(); //add time timeMap.addTimeNoMs(tick, new RepTime(iRep, time(iMeasure, beat))); lastBeat = beat; } } } return timeMap.build(); } /** * Gets the used beats in the given measure column. * When the given play range starts in this measure, only beats at or after the start beat * of the range are returned. * When the given play range ends in this measure, only beats at or before the end beat * of the given range are returned. */ private List<Fraction> getUsedBeats(int iMeasure, Repetition range) { val usedBeats = score.getMeasureUsedBeats(iMeasure, true); //beats where something begins usedBeats.add(score.getMeasureFilledBeats(iMeasure)); //last beat (where last elements ends) List<Fraction> ret = alist(usedBeats.getSize()); for (val beat : usedBeats) { if (iMeasure == range.start.measure) { if (beat.compareTo(range.start.beat) >= 0) ret.add(beat); } else if (iMeasure == range.end.measure) { if (beat.compareTo(range.end.beat) <= 0) ret.add(beat); } else { ret.add(beat); } } return ret; } }