package com.xenoage.zong.musiclayout.spacer.system.fill;
import static com.xenoage.utils.collections.CollectionUtils.getLast;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.utils.kernel.Range.rangeReverse;
import com.xenoage.zong.musiclayout.spacing.BeatOffset;
import com.xenoage.zong.musiclayout.spacing.ColumnSpacing;
import com.xenoage.zong.musiclayout.spacing.ElementSpacing;
import com.xenoage.zong.musiclayout.spacing.MeasureSpacing;
import com.xenoage.zong.musiclayout.spacing.SystemSpacing;
import com.xenoage.zong.musiclayout.spacing.VoiceSpacing;
/**
* Stretches all measures within the given system,
* so that they use the whole useable width of the system.
*
* @author Andreas Wenger
*/
public class StretchMeasures
implements SystemFiller {
public static final StretchMeasures stretchMeasures = new StretchMeasures();
@Override public void compute(SystemSpacing system, float usableWidthMm) {
//compute width of all voice spacings
//(leading spacings are not stretched)
float voicesWidthMm = 0;
float leadingsWidthMm = 0;
for (ColumnSpacing column : system.columns) {
voicesWidthMm += column.getVoicesWidthMm();
leadingsWidthMm += column.getLeadingWidthMm();
}
//compute the stretching factor for the voice spacings
if (voicesWidthMm == 0)
return;
float stretch = (usableWidthMm - leadingsWidthMm) / voicesWidthMm;
//stretch the voice spacings
//measure columns
for (ColumnSpacing column : system.columns) {
//beat offsets
for (int i : range(column.beatOffsets)) {
BeatOffset bo = column.beatOffsets.get(i);
BeatOffset stretched = bo.withOffsetMm(bo.offsetMm * stretch);
column.beatOffsets.set(i, stretched);
}
for (int i : range(column.barlineOffsets)) {
BeatOffset bo = column.barlineOffsets.get(i);
BeatOffset stretched = bo.withOffsetMm(bo.offsetMm * stretch);
column.barlineOffsets.set(i, stretched);
}
//measures
for (MeasureSpacing measure : column.measures) {
//measure elements
for (ElementSpacing element : measure.elements) {
//stretch the offset
element.xIs *= stretch;
}
//voices
for (VoiceSpacing voice : measure.voices) {
//traverse elements in reverse order, so we can align grace elements correctly
//grace elements are not stretched, but the distance to their following full element
//stays the same
float lastElementOriginalOffsetIs = getLast(column.beatOffsets).offsetMm / voice.interlineSpace;
for (int i : rangeReverse(voice.elements)) {
ElementSpacing element = voice.elements.get(i);
if (element.isGrace()) {
//grace element: keep distance to following element
float oldDistance = lastElementOriginalOffsetIs - element.xIs;
lastElementOriginalOffsetIs = element.xIs;
element.xIs = voice.elements.get(i + 1).xIs - oldDistance;
}
else {
//normal element: stretch the offset
lastElementOriginalOffsetIs = element.xIs;
element.xIs *= stretch;
}
}
}
}
}
//full system width
system.widthMm = usableWidthMm;
//columns have been changed
system.onColumnsWidthChange();
}
}