Search in sources :

Example 6 with BeatOffset

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;
}
Also used : ElementSpacing(com.xenoage.zong.musiclayout.spacing.ElementSpacing) BeatOffset(com.xenoage.zong.musiclayout.spacing.BeatOffset) Fraction(com.xenoage.utils.math.Fraction) VoiceSpacing(com.xenoage.zong.musiclayout.spacing.VoiceSpacing)

Example 7 with BeatOffset

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;
    }
}
Also used : ElementSpacing(com.xenoage.zong.musiclayout.spacing.ElementSpacing) BeatOffset(com.xenoage.zong.musiclayout.spacing.BeatOffset) Fraction(com.xenoage.utils.math.Fraction)

Example 8 with BeatOffset

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);
}
Also used : LeadingSpacing(com.xenoage.zong.musiclayout.spacing.LeadingSpacing) ColumnSpacing(com.xenoage.zong.musiclayout.spacing.ColumnSpacing) Fraction(com.xenoage.utils.math.Fraction) ElementSpacing(com.xenoage.zong.musiclayout.spacing.ElementSpacing) ColumnHeader(com.xenoage.zong.core.header.ColumnHeader) Column(com.xenoage.zong.core.music.util.Column) BarlinesBeatOffsetter(com.xenoage.zong.musiclayout.spacer.beat.BarlinesBeatOffsetter) MP.atMeasure(com.xenoage.zong.core.position.MP.atMeasure) Measure(com.xenoage.zong.core.music.Measure) MeasureSpacing(com.xenoage.zong.musiclayout.spacing.MeasureSpacing) VoiceSpacingsByStaff(com.xenoage.zong.musiclayout.layouter.columnspacing.VoiceSpacingsByStaff) BeatOffset(com.xenoage.zong.musiclayout.spacing.BeatOffset) ArrayList(java.util.ArrayList) List(java.util.List) VoiceSpacing(com.xenoage.zong.musiclayout.spacing.VoiceSpacing) Voice(com.xenoage.zong.core.music.Voice)

Example 9 with BeatOffset

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();
}
Also used : ElementSpacing(com.xenoage.zong.musiclayout.spacing.ElementSpacing) ColumnSpacing(com.xenoage.zong.musiclayout.spacing.ColumnSpacing) MeasureSpacing(com.xenoage.zong.musiclayout.spacing.MeasureSpacing) BeatOffset(com.xenoage.zong.musiclayout.spacing.BeatOffset) VoiceSpacing(com.xenoage.zong.musiclayout.spacing.VoiceSpacing)

Example 10 with BeatOffset

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;
    }
}
Also used : ElementSpacing(com.xenoage.zong.musiclayout.spacing.ElementSpacing) BeatOffset(com.xenoage.zong.musiclayout.spacing.BeatOffset)

Aggregations

BeatOffset (com.xenoage.zong.musiclayout.spacing.BeatOffset)16 ElementSpacing (com.xenoage.zong.musiclayout.spacing.ElementSpacing)10 VoiceSpacing (com.xenoage.zong.musiclayout.spacing.VoiceSpacing)9 Fraction (com.xenoage.utils.math.Fraction)6 Test (org.junit.Test)6 ColumnSpacing (com.xenoage.zong.musiclayout.spacing.ColumnSpacing)4 MeasureSpacing (com.xenoage.zong.musiclayout.spacing.MeasureSpacing)4 Voice (com.xenoage.zong.core.music.Voice)3 SystemSpacing (com.xenoage.zong.musiclayout.spacing.SystemSpacing)3 ColumnHeader (com.xenoage.zong.core.header.ColumnHeader)2 Chord (com.xenoage.zong.core.music.chord.Chord)2 ChordFactory.graceChord (com.xenoage.zong.core.music.chord.ChordFactory.graceChord)2 ChordNotation (com.xenoage.zong.musiclayout.notation.ChordNotation)2 ChordSpacing (com.xenoage.zong.musiclayout.spacing.ChordSpacing)2 ArrayList (java.util.ArrayList)2 JsonArray (com.google.gson.JsonArray)1 JsonObject (com.google.gson.JsonObject)1 Point2f (com.xenoage.utils.math.geom.Point2f)1 Size2f (com.xenoage.utils.math.geom.Size2f)1 Score (com.xenoage.zong.core.Score)1