package com.xenoage.zong.io.midi.out.dynamics; import com.xenoage.utils.annotations.Const; import com.xenoage.zong.core.position.Time; import com.xenoage.zong.io.midi.out.dynamics.type.DynamicsType; import com.xenoage.zong.io.midi.out.score.MeasureBeats; import lombok.Data; import lombok.val; /** * A period of time with a fixed or changing dynamic. * * When a dynamics sign appears, this is usually the begin of a new * {@link DynamicsPeriod}. It also has a duration, since it may not only * end when another sign appears, but also when it belongs to a voice * which ends somewhere. * * @author Andreas Wenger */ @Const @Data public class DynamicsPeriod { /** Start time (inclusive). */ public final Time startTime; /** End time (exclusive). */ public final Time endTime; /** The fixed or gradient dynamics. */ public final DynamicsType dynamics; /** * Returns true, iff the given time is within this period. */ public boolean contains(Time time) { return startTime.compareTo(time) <= 0 && time.compareTo(endTime) < 0; } /** * Gets the exact dynamics at the given time, using the given {@link DynamicsInterpretation}. * To compute the value, also the length of each measure must be given. */ public float getVolumeAt(Time time, DynamicsInterpretation interpretation, MeasureBeats measures) { if (time.compareTo(endTime) > 0) throw new IllegalArgumentException("given time is after period"); else if (time.compareTo(startTime) < 0) throw new IllegalArgumentException("given time is before period"); val beatsToTime = measures.computeBeatsBetween(startTime, time); val beatsToEnd = measures.computeBeatsBetween(startTime, endTime); float progress = beatsToTime.divideBy(beatsToEnd).toFloat(); return dynamics.getVolumeAt(progress, interpretation); } @Override public String toString() { return "{start=" + startTime.toStringCompact() + ", end=" + endTime.toStringCompact() + ", dynamics=" + dynamics + "}"; } }