package com.xenoage.zong.io.midi.out.dynamics; import com.xenoage.utils.collections.CList; import com.xenoage.utils.collections.IList; import static com.xenoage.utils.collections.CList.clist; import static com.xenoage.utils.collections.CollectionUtils.getOrNull; import static com.xenoage.utils.collections.CollectionUtils.setExtend; /** * Builder for {@link DynamicsPeriods}. * * {@link DynamicsPeriod}s can be added to play ranges and a staff * and optionally a voice, but they may not overlap within their * tracks. * * @author Andreas Wenger */ public class DynamicsPeriodsBuilder { private final CList<IList<IList<IList<DynamicsPeriod>>>> periods = clist(); /** * Adds a {@link DynamicsPeriod} in the given staff and repetition. * This is used for dynamics which are defined in a measure, not in a specific voice. */ public DynamicsPeriodsBuilder addPeriodToStaff( DynamicsPeriod period, int staff, int repetition) { addPeriod(period, (CList<DynamicsPeriod>) getStaffPeriods(staff, repetition)); return this; } /** * Adds a {@link DynamicsPeriod} in the given staff, voice and repetition. * This is used for dynamics which are defined in a specific voice, not in its parent measure. */ public DynamicsPeriodsBuilder addPeriodToVoice( DynamicsPeriod period, int staff, int voice, int repetition) { addPeriod(period, (CList<DynamicsPeriod>) getVoicePeriods(staff, voice, repetition)); return this; } public DynamicsPeriods build() { return new DynamicsPeriods(periods.closeDeep()); } /** * Gets the periods track for the given staff and repetition. */ private IList<DynamicsPeriod> getStaffPeriods(int staff, int repetition) { return getVoicePeriods(staff, -1, repetition); } /** * Gets the periods track for the given staff, voice and repetition. */ private IList<DynamicsPeriod> getVoicePeriods(int staff, int voice, int repetition) { //get or create staff IList<IList<IList<DynamicsPeriod>>> staffList = getOrNull(periods, staff); if (staffList == null) { staffList = clist(); setExtend(periods, staff, staffList, null); } //get or create voice (0 = staff; 1..n = voice 0..(n-1)) IList<IList<DynamicsPeriod>> voiceList = getOrNull(staffList, voice + 1); if (voiceList == null) { voiceList = clist(); setExtend(staffList, voice + 1, voiceList, null); } //get or create repetition IList<DynamicsPeriod> repetitionList = getOrNull(voiceList, repetition); if (repetitionList == null) { repetitionList = clist(); setExtend(voiceList, repetition, repetitionList, null); } return repetitionList; } /** * Adds the given period into the given list. When there are collisions, * an {@link IllegalArgumentException} is thrown. */ private void addPeriod(DynamicsPeriod period, CList<DynamicsPeriod> target) { //add at correct position int pos = 0; while (pos < target.size() && target.get(pos).startTime.compareTo(period.startTime) < 0) pos++; //add at this position, but only if the following (if any) period does not overlap if (pos + 1 < target.size() && target.get(pos + 1).startTime.compareTo(period.endTime) < 0) throw new IllegalArgumentException("periods overlap"); target.add(pos, period); } }