use of com.xenoage.zong.musiclayout.spacing.BeatOffset in project Zong by Xenoage.
the class VoicesBeatOffsetter method compute.
/**
* Computes the offsets of all the used beats, including
* at least beat 0 and the beat at the end of the measure.
* The beats containing the notes with the lowest valuation
* (or that needs accidentals) dictate the spacing.
* See "Ross: The Art of Music Engraving", page 79.
*/
public List<BeatOffset> compute(List<VoiceSpacing> voiceSpacings, Fraction measureBeats, float minimalBeatsOffsetIs) {
// the list of all used beats of the measure
// TODO: handle upbeat measures correctly!
SortedList<Fraction> beats = computeVoicesBeats(voiceSpacings);
// add final used beat
beats.add(computeLastBeat(voiceSpacings));
// add final beat in terms of time signature (only correct for non-upbeat measures)
beats.add(measureBeats);
// the resulting offsets for each used beat
ArrayList<BeatOffset> ret = alist();
// compute the offset of beat 0
float offsetMm = getOffsetBeat0InMm(voiceSpacings);
Fraction lastBeat = Companion.get_0();
ret.add(new BeatOffset(lastBeat, offsetMm));
// Otherwise we must find the dominant parts within the voices
if (voiceSpacings.size() == 1) {
// only one voice
float interlineSpace = voiceSpacings.get(0).interlineSpace;
for (ElementSpacing se : voiceSpacings.get(0).elements) {
// if last beat offset has same beat, overwrite it
if (ret.get(ret.size() - 1).getBeat().equals(se.beat))
ret.remove(ret.size() - 1);
ret.add(new BeatOffset(se.beat, se.xIs * interlineSpace));
}
} else {
// more than one voice
// use the following algorithm:
// for each beat, compute the offset, by asking each voice how much space
// it requires between the last computed beat offset and the current one.
// each time, take the greatest distance required.
Iterator<Fraction> beatsIterator = beats.iterator();
// ignore beat 0, we have handled it before
beatsIterator.next();
while (beatsIterator.hasNext()) {
Fraction beat = beatsIterator.next();
// find dominating voice and its minimal required distance
float minimalDistance = 0;
for (VoiceSpacing voiceSpacing : voiceSpacings) {
float interlineSpace = voiceSpacing.interlineSpace;
float voiceMinimalDistance = computeMinimalDistance(lastBeat, beat, beat.equals(measureBeats), voiceSpacing.voice, voiceSpacing.elements, ret, interlineSpace);
minimalDistance = Math.max(minimalDistance, voiceMinimalDistance);
// but we do not want to have different beats at the same offset, so add a small distance.
if (minimalDistance < minimalBeatsOffsetIs * interlineSpace) {
minimalDistance = minimalBeatsOffsetIs * interlineSpace;
}
}
// add beat
offsetMm += minimalDistance;
ret.add(new BeatOffset(beat, offsetMm));
lastBeat = beat;
}
}
ret.trimToSize();
return ret;
}
use of com.xenoage.zong.musiclayout.spacing.BeatOffset in project Zong by Xenoage.
the class VoicesBeatOffsetter method computeMinimalDistance.
/**
* Computes and returns the minimal distance in mm
* within the given spacing elements of the given voice
* between the given starting and ending beat (ending beat
* without its width).
*
* Beats may be multiused. The last element with the given start beat
* and also the last element of the given end beat are used
* (because the important offset of a beat is the position of the main note
* or rest, not the position of a grace note or a clef or key signature).
*
* If both the starting and ending beat are used,
* computing their minimal distance is simple.
*
* If the ending beat is unused, 0 is returned, since the given
* voice does not need any space because it has no element to place there.
*
* If the starting beat is unused, we have to compute
* the distance in the following way:
*
* The following example shows 2 voices:
*
* # # # ? { #: there the offsets are already known and given
* 1/4 1/4 1/4 |
* | |
* 1/4 3/8 * 1/8 { this voice is given. *: startBeat is not used
* | |
* startBeat_| |_endBeat
*
* Because startBeat is not used, we compute the distance
* from the last used beat to the end beat, which is known
* from the given spacing elements:
*
* 1/4 3/8 * 1/8
* |_____________|
* distanceToEndBeat
*
* And we subtract distance between the already computed offset of
* the last used beat and the also already computed offset of
* the starting beat (both given in the list of beat offsets):
*
* 1/4 3/8 * 1/8
* |_________|
* distanceToLastUsedBeat
*
* The result is the distance between the starting beat
* and the ending beat:
*
* 1/4 3/8 * 1/8
* |___|
* return
*
* This value is the minimal distance the given voice needs to
* place the elements up to the given ending beat.
*/
float computeMinimalDistance(Fraction startBeat, Fraction endBeat, boolean endBeatIsMeasureEnd, Voice voice, List<ElementSpacing> spacings, List<BeatOffset> alreadyComputedBeatOffsets, float interlineSpace) {
// end beat used? (measure end beat is always used)
if (endBeatIsMeasureEnd || voice.isBeatUsed(endBeat)) {
// when measure is incomplete: use last available beat
if (endBeatIsMeasureEnd) {
endBeat = voice.getLastUsedBeat(endBeat);
}
float endOffset = getLastOffset(spacings, endBeat) * interlineSpace;
// start beat used?
if (voice.isBeatUsed(startBeat)) {
// yes
float startOffset = getLastOffset(spacings, startBeat) * interlineSpace;
// return the distance between this two beats
return endOffset - startOffset;
} else {
// no, start beat is not used. use the algorithm described above
Fraction lastUsedBeat = voice.getLastUsedBeat(startBeat);
// get offset of the last used beat in the voice spacing
float lastUsedBeatVoiceSpacingOffset = 0;
for (ElementSpacing spacing : spacings) {
if (spacing.beat.equals(lastUsedBeat)) {
lastUsedBeatVoiceSpacingOffset = spacing.xIs * interlineSpace;
break;
}
}
// compute minimal distance from last used beat to end beat
float distanceToEndBeat = endOffset - lastUsedBeatVoiceSpacingOffset;
// get offset of the last computed beat from the list of already computed beat offsets
float lastComputedBeatOffset = alreadyComputedBeatOffsets.get(alreadyComputedBeatOffsets.size() - 1).getOffsetMm();
// get offset of the last used beat from the list of already computed beat offsets
float lastUsedBeatBeatOffsetsOffset = 0;
for (BeatOffset beatOffset : alreadyComputedBeatOffsets) {
if (beatOffset.getBeat().equals(lastUsedBeat)) {
lastUsedBeatBeatOffsetsOffset = beatOffset.getOffsetMm();
break;
}
}
// compute distance between these two offsets
float distanceToLastUsedBeat = lastComputedBeatOffset - lastUsedBeatBeatOffsetsOffset;
// return the distance between the last computed beat offset and the end beat
return distanceToEndBeat - distanceToLastUsedBeat;
}
} else {
// since there is no element, we need no space
return 0;
}
}
use of com.xenoage.zong.musiclayout.spacing.BeatOffset 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.BeatOffset 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.BeatOffset in project Zong by Xenoage.
the class AlignedVoicesSpacer method compute.
/**
* Modifies the given {@link VoiceSpacing}, based on the given beat offsets.
*/
public void compute(VoiceSpacing voiceSpacing, List<BeatOffset> beatOffsets) {
List<ElementSpacing> spacingElements = voiceSpacing.elements;
if (spacingElements.size() == 0 || beatOffsets.size() == 0)
return;
// find the given beats, that are also used here
List<BeatOffset> sharedBeats = computeSharedBeats(spacingElements, beatOffsets);
// interpolate positions between the shared beats
float lastGivenBeatPosition = 0;
float lastEndElementPosition = 0;
int firstElement = 0;
int lastElement = -1;
float interlineSpace = voiceSpacing.interlineSpace;
for (int iGivenBeat : range(sharedBeats)) {
// for each given beat: find elements before or at that beat
for (int iElement : range(lastElement + 1, spacingElements.size() - 1)) {
if (spacingElements.get(iElement).beat.compareTo(sharedBeats.get(iGivenBeat).getBeat()) > 0)
break;
lastElement++;
}
if (lastElement == -1)
break;
// compute horizontal positions and distances of the
// given beat offsets and the current voice spacing, from the
// last shared beat up to the current shared beat.
// we calculate in interline spaces here
float currentGivenBeatPosition = sharedBeats.get(iGivenBeat).getOffsetMm() / interlineSpace;
float givenBeatsDistance = currentGivenBeatPosition - lastGivenBeatPosition;
float currentEndElementPosition = spacingElements.get(lastElement).xIs;
float elementsDistance = currentEndElementPosition - lastEndElementPosition;
// interpolate the offsets of the current voice spacing
// between the last shared beat and the current shared beat.
// do this in reverse order, because the position of grace notes is dependent
// on the position of the (following) main note
float lastOriginalOffsetIs = getLast(beatOffsets).offsetMm / interlineSpace;
for (int iElement : rangeReverse(lastElement, firstElement)) {
ElementSpacing e = spacingElements.get(iElement);
if (false == e.isGrace()) {
// normal element: interpolate position
float currentElementOffset = e.xIs - lastEndElementPosition;
float newElementOffset;
if (elementsDistance != 0) {
// scale offset according to the given beats distance
newElementOffset = currentElementOffset / elementsDistance * givenBeatsDistance;
} else {
newElementOffset = currentGivenBeatPosition;
}
lastOriginalOffsetIs = e.xIs;
e.xIs = newElementOffset + lastGivenBeatPosition;
} else {
// grace element: same distance to the main note as before
float distanceBefore = lastOriginalOffsetIs - e.xIs;
lastOriginalOffsetIs = e.xIs;
e.xIs = spacingElements.get(iElement + 1).xIs - distanceBefore;
}
}
// next range up to next shared beat
firstElement = lastElement + 1;
if (firstElement >= spacingElements.size())
break;
lastGivenBeatPosition = currentGivenBeatPosition;
lastEndElementPosition = currentEndElementPosition;
}
}
Aggregations