package com.xenoage.zong.io.midi.out.dynamics; import com.xenoage.utils.annotations.Const; import com.xenoage.utils.annotations.MaybeNull; import com.xenoage.utils.collections.IList; import com.xenoage.utils.kernel.Range; import com.xenoage.zong.core.position.MP; import com.xenoage.zong.core.position.Time; import com.xenoage.zong.io.midi.out.repetitions.Repetition; import lombok.RequiredArgsConstructor; import lombok.val; import static com.xenoage.utils.collections.CollectionUtils.getOrNull; import static com.xenoage.utils.kernel.Range.range; /** * All {@link DynamicsPeriod}s in a score. * * @author Andreas Wenger */ @Const @RequiredArgsConstructor public class DynamicsPeriods { /** * The periods, sorted by staff, voice and repetition. * First index: staff. Second index: voice + 1, or 0 = no voice (staff). Third index: repetition. */ private final IList<IList<IList<IList<DynamicsPeriod>>>> periods; /** * Gets the {@link DynamicsPeriod} for the given position. * The highest priority has a dynamic in the current voice. If there is none, * the last dynamic of the part is searched. When there are multiple candidates * on different staves, the current staff wins. * @param mp the position where to find the dynamic * @param repetition the index of the {@link Repetition} * @param partStaves the staves indices in the part */ @MaybeNull public DynamicsPeriod get(MP mp, int repetition, Range partStaves) { val time = mp.getTime(); //first, find voice dynamics val voicePeriods = getVoicePeriods(mp.staff, mp.voice, repetition); val voicePeriod = getPeriod(voicePeriods, time); if (voicePeriod != null) return voicePeriod; //then, for all staves in the part, look for last period(s) - when there //is more than one at the same time, prefer the one at the own staff Time maxStartTime = time; DynamicsPeriod bestMatch = null; for (int iStaff : partStaves) { val staffPeriods = getStaffPeriods(mp.staff, repetition); val staffPeriod = getPeriod(staffPeriods, time); int compare = staffPeriod.startTime.compareTo(maxStartTime); if (staffPeriod != null && compare > 0 || (compare >= 0 && iStaff == mp.staff)) { //new best match maxStartTime = staffPeriod.startTime; bestMatch = null; } } return bestMatch; } /** * Gets the {@link DynamicsPeriod} from the given list, * which includes the given time, or null if none is found. */ @MaybeNull private DynamicsPeriod getPeriod(@MaybeNull IList<DynamicsPeriod> periods, Time time) { if (periods == null) return null; for (val period : periods) if (period.contains(time)) return period; return null; } @MaybeNull private IList<DynamicsPeriod> getStaffPeriods(int staff, int repetition) { return getOrNull(getOrNull(getOrNull(periods, staff), 0), repetition); } @MaybeNull private IList<DynamicsPeriod> getVoicePeriods(int staff, int voice, int repetition) { return getOrNull(getOrNull(getOrNull(periods, staff), voice + 1), repetition); } @Override public String toString() { String s = ""; for (int iStaff : range(periods)) { val staffList = getOrNull(periods, iStaff); if (staffList != null) { s += "staff " + iStaff + ":\n"; for (int iVoice : range(staffList)) { val voiceList = getOrNull(staffList, iVoice); if (voiceList != null) { s += (iVoice == 0 ? " no voice:" : " voice " + iVoice + ":") + "\n"; for (int iRep : range(voiceList)) { val repList = getOrNull(voiceList, iRep); if (repList != null) { s += " rep " + iRep + ": " + repList.toString() + "\n"; } } } } } } return s; } }