use of com.xenoage.zong.musiclayout.spacing.VoiceSpacing in project Zong by Xenoage.
the class ColumnSpacer method compute.
/**
* Computes a {@link ColumnSpacing} from a measure column.
* @param context the current context, with the current {@link MP} and precomputed
* element {@link Notation}s
* @param createLeading true, if a leading spacing has to be created, otherwise false
* @param notations the precomputed notations of the measure and voice elements
*/
public ColumnSpacing compute(Context context, boolean createLeading, Notations notations) {
context.saveMp();
int measureIndex = context.mp.measure;
Column column = context.score.getColumn(measureIndex);
ColumnHeader columnHeader = context.score.getHeader().getColumnHeader(measureIndex);
// compute the optimal spacings for each voice separately
List<List<VoiceSpacing>> voiceSpacingsByStaff = alist();
for (int iStaff : range(column)) {
List<VoiceSpacing> vss = alist();
Measure measure = column.get(iStaff);
for (Voice voice : measure.getVoices()) {
context.mp = MP.atVoice(iStaff, measureIndex, measure.getVoices().indexOf(voice));
VoiceSpacing vs = singleVoiceSpacer.compute(context, notations);
vss.add(vs);
}
voiceSpacingsByStaff.add(vss);
}
// compute the measure elements (like inner clefs) and accordingly updated voice spacings
ArrayList<List<ElementSpacing>> optimalMeasureElementsSpacingsByStaff = alist();
for (int iStaff : range(column)) {
context.mp = MP.atMeasure(iStaff, measureIndex);
List<ElementSpacing> measureSpacing = measureElementsSpacer.compute(context, createLeading, voiceSpacingsByStaff.get(iStaff), notations);
optimalMeasureElementsSpacingsByStaff.add(measureSpacing);
}
// compute the common beat offsets of this measure column
Fraction measureBeats = context.score.getMeasureBeats(measureIndex);
VoiceSpacingsByStaff voiceSpacings = new VoiceSpacingsByStaff(voiceSpacingsByStaff);
List<BeatOffset> beatOffsets = voicesBeatOffsetter.compute(voiceSpacings.getAll(), measureBeats, context.settings.offsetBeatsMinimal);
// recompute beat offsets with respect to barlines
BarlinesBeatOffsetter.Result offsets = barlinesBeatOffsetter.compute(beatOffsets, columnHeader, context.score.getMaxIS());
beatOffsets = offsets.voiceElementOffsets;
List<BeatOffset> barlineOffsets = offsets.barlineOffsets;
// compute the spacings for the whole column, so that equal beats are aligned
ArrayList<List<ElementSpacing>> alignedMeasureElementsSpacingsByStaff = alist();
for (int iStaff : range(column)) {
Measure measure = column.get(iStaff);
// voice spacings
for (int iVoice : range(measure.getVoices())) alignedVoicesSpacer.compute(voiceSpacings.get(iStaff, iVoice), beatOffsets);
// measure elements, based on the aligned voice spacings
context.mp = atMeasure(iStaff, measureIndex);
alignedMeasureElementsSpacingsByStaff.add(measureElementsSpacer.compute(context, createLeading, voiceSpacings.getStaff(iStaff), notations));
}
// compute spacings for each staff
List<MeasureSpacing> measureSpacings = alist(column.size());
for (int iStaff : range(column)) {
// create leading spacing, if needed
LeadingSpacing leadingSpacing = null;
if (createLeading) {
context.mp = atBeat(iStaff, measureIndex, 0, Fraction.Companion.get_0());
leadingSpacing = leadingSpacer.compute(context, notations);
}
// create measure spacing
float interlineSpace = context.score.getInterlineSpace(iStaff);
measureSpacings.add(new MeasureSpacing(atMeasure(iStaff, measureIndex), interlineSpace, voiceSpacings.getStaff(iStaff), alignedMeasureElementsSpacingsByStaff.get(iStaff), leadingSpacing));
}
context.restoreMp();
return new ColumnSpacing(measureIndex, measureSpacings, beatOffsets, barlineOffsets);
}
use of com.xenoage.zong.musiclayout.spacing.VoiceSpacing in project Zong by Xenoage.
the class MeasureElementsSpacer method getFirstElementSpacing.
/**
* Gets the leftmost {@link ElementSpacing} in the given list of {@link VoiceSpacing}s,
* or null there is none.
*/
private ElementSpacing getFirstElementSpacing(List<VoiceSpacing> vss) {
ElementSpacing ret = null;
float retLeftX = Float.MAX_VALUE;
for (VoiceSpacing vs : vss) {
for (ElementSpacing se : vs.elements) {
float leftX = getLeftX(se);
if (leftX < retLeftX) {
retLeftX = leftX;
ret = se;
// no other element after this one in the same voice will be more left
break;
}
}
}
return ret;
}
use of com.xenoage.zong.musiclayout.spacing.VoiceSpacing in project Zong by Xenoage.
the class StretchMeasures method compute.
@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;
// 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();
}
use of com.xenoage.zong.musiclayout.spacing.VoiceSpacing in project Zong by Xenoage.
the class MeasureElementsSpacer method getNearestSpacingElements.
/**
* Gets the nearest two {@link ElementSpacing}s at the
* given beat (left [0] and right [1]).
*/
private ElementSpacing[] getNearestSpacingElements(Fraction beat, List<VoiceSpacing> vss) {
ElementSpacing[] ret = { null, null };
float retLeftX = Float.MIN_VALUE;
float retRightX = Float.MAX_VALUE;
for (VoiceSpacing vs : vss) {
for (ElementSpacing se : vs.elements) {
int compare = se.beat.compareTo(beat);
if (compare < 0) {
float leftX = getLeftX(se);
if (leftX > retLeftX) {
// found nearer left element
retLeftX = leftX;
ret[0] = se;
}
} else if (compare >= 0) {
float rightX = getRightX(se);
if (rightX < retRightX) {
// found nearer right element
retRightX = rightX;
ret[1] = se;
}
// first candidate always matches here
break;
}
}
}
return ret;
}
use of com.xenoage.zong.musiclayout.spacing.VoiceSpacing in project Zong by Xenoage.
the class SingleVoiceSpacer method compute.
VoiceSpacing compute(Voice voice, float interlineSpace, Fraction measureBeats, int staffLinesCount, Notations notations, LayoutSettings layoutSettings) {
LinkedList<ElementSpacing> ret = llist();
// special case: no elements in the measure.
if (voice.getElements().size() == 0) {
return new VoiceSpacing(voice, interlineSpace, alist((ElementSpacing) new BorderSpacing(Fraction.Companion.get_0(), 0), new BorderSpacing(measureBeats, layoutSettings.spacings.widthMeasureEmpty)));
}
// we compute the spacings in reverse order. this is easier, since grace chords
// use shared space when possible, but are aligned to the right. so we begin
// at position 0, and create the spacings in reverse direction (thus we find negative positions).
// at the end, we shift the voice spacing to the right to be aligned at the left measure border,
// which is position 0.
// last symbol offset:
// real offset where the last element's symbol started
// since we do not know the right border yet, we start at 0
float lastSymbolOffset = 0;
// front gap offset:
// offset where the last element (including front gap) started.
float lastFrontGapOffset = lastSymbolOffset;
// last full element offset:
// lastSymbolOffset of the last full (non-grace) element
float lastFullSymbolOffset = lastSymbolOffset;
// at last beat
Fraction curBeat = voice.getFilledBeats();
ret.addFirst(new BorderSpacing(curBeat, lastFrontGapOffset));
// iterate through the elements in reverse order
for (VoiceElement element : Companion.reverseIt(voice.getElements())) {
// get the notation
Notation notation = notations.get(element);
if (notation == null)
throw new IllegalStateException("No notation for element " + element);
// get the width of the element (front gap, symbol's width, rear gap, lyric's width)
ElementWidth elementWidth = notation.getWidth();
// add spacing for voice element
float symbolOffset;
boolean grace = !element.getDuration().isGreater0();
if (!grace) {
// full element
// share this rear gap and the front gap of the following
// element + the space of following grace elements, when possible
// (but use at least minimal distance)
symbolOffset = Math.min(lastFrontGapOffset - layoutSettings.spacings.widthDistanceMin, lastFullSymbolOffset - elementWidth.rearGap) - elementWidth.symbolWidth;
lastFullSymbolOffset = symbolOffset;
// update beat cursor
curBeat = curBeat.sub(element.getDuration());
} else {
// grace element
// share this rear gap and the front gap of the following element, when possible
symbolOffset = Math.min(lastFrontGapOffset, lastSymbolOffset - elementWidth.rearGap) - elementWidth.symbolWidth;
}
ElementSpacing elementSpacing = null;
if (notation instanceof RestNotation) {
// rest spacing
elementSpacing = restSpacer.compute((RestNotation) notation, curBeat, symbolOffset, staffLinesCount);
} else {
// chord spacing
elementSpacing = new ChordSpacing((ChordNotation) notation, curBeat, symbolOffset);
}
ret.addFirst(elementSpacing);
lastFrontGapOffset = symbolOffset - elementWidth.frontGap;
lastSymbolOffset = symbolOffset;
}
// shift spacings to the right
float shift = (-lastFrontGapOffset) + layoutSettings.offsetMeasureStart;
for (ElementSpacing e : ret) e.xIs += shift;
return new VoiceSpacing(voice, interlineSpace, ilist(ret));
}
Aggregations