Search in sources :

Example 21 with Fraction

use of com.xenoage.utils.math.Fraction 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));
}
Also used : ElementWidth(com.xenoage.zong.musiclayout.spacing.ElementWidth) ChordNotation(com.xenoage.zong.musiclayout.notation.ChordNotation) RestNotation(com.xenoage.zong.musiclayout.notation.RestNotation) Fraction(com.xenoage.utils.math.Fraction) Notation(com.xenoage.zong.musiclayout.notation.Notation) RestNotation(com.xenoage.zong.musiclayout.notation.RestNotation) ChordNotation(com.xenoage.zong.musiclayout.notation.ChordNotation) ElementSpacing(com.xenoage.zong.musiclayout.spacing.ElementSpacing) ChordSpacing(com.xenoage.zong.musiclayout.spacing.ChordSpacing) BorderSpacing(com.xenoage.zong.musiclayout.spacing.BorderSpacing) VoiceElement(com.xenoage.zong.core.music.VoiceElement) VoiceSpacing(com.xenoage.zong.musiclayout.spacing.VoiceSpacing)

Example 22 with Fraction

use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.

the class ColumnSpacing method getBeatAt.

/**
 * Like {@link #getBeatAt(float)}, but only for a single staff,
 * i.e. unused beats in this staff are ignored.
 *
 * When the staff is {@link MP#unknown}, this method works like
 * {@link #getBeatAt(float)}.
 */
public Fraction getBeatAt(float xMm, int staff) {
    if (staff == Companion.getUnknown())
        return getBeatAt(xMm);
    // find beat and return it
    MeasureSpacing measure = measures.get(staff);
    BeatOffset last = null;
    for (Fraction beat : measure.usedBeats) {
        BeatOffset bo = getBeatOffset(beat);
        if (xMm <= bo.offsetMm)
            return getBeatAt(xMm, last, bo);
        last = bo;
    }
    // return last beat
    return getLast(beatOffsets).beat;
}
Also used : Fraction(com.xenoage.utils.math.Fraction)

Example 23 with Fraction

use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.

the class BeamFragmenter method compute.

/**
 * Computes the fragments for the given line (1: 16th line, 2: 32th line, ...).
 * Use an algorithm based on the rules in Chlapik, page 45, rule 6.
 *
 * Begin with the highest line (e.g. 32th before 16th), and use the result of line n
 * as a parameter to compute line n-1 (for the first computation, use null).
 * This is needed to support Chlapik, page 45, rule 6, example of row 3, column 6.
 * Without that, the 16th line would go from the second note to the fourth one.
 */
Fragments compute(Beam beam, int line, Fragments higherLine) {
    if (line < 1)
        throw new IllegalArgumentException("This method only works for 16th lines or higher");
    // in this algorithm, we go from note to note, looking for "groups".
    // groups are consecutive chords/stems with the same number of flags (or
    // a higher number inbetween) and not divided by a subdivision break.
    // initialize return array with none-waypoints
    Fragments ret = new Fragments(beam.size());
    int lastFlagsCount = -1;
    // start chord of the last group, or -1 if no group is open
    int startChord = -1;
    // stop chord of the last group, or -1 if group is open
    int stopChord = -1;
    for (int iChord : range(beam.size() + 1)) {
        if (iChord < beam.getWaypoints().size()) {
            // another chord within the beam
            Chord chord = beam.getChord(iChord);
            int flagsCount = INSTANCE.getFlagsCount(chord.getDuration());
            // enough flags for the given line? (e.g. a 8th beam has no 16th line)
            if (flagsCount >= line + 1) {
                // yes, we need a line of the given line for this stem
                if (startChord == -1) {
                    if (higherLine == null || higherLine.get(iChord) != HookLeft) {
                        // start new group
                        startChord = iChord;
                        lastFlagsCount = flagsCount;
                    } else {
                        // example mentioned in the method documentation (Chlapik, page 45, row 3, col 6)
                        // we place a hook. this is not explicitly mentioned in the text, but seems to
                        // be right when looking at the example.
                        startChord = iChord;
                        stopChord = iChord;
                    }
                } else if (lastFlagsCount > -1 && (// less flags than previous stem
                flagsCount < flagsCount || beam.isEndOfSubdivision(iChord))) {
                    // forced subdivision break
                    // end the group here
                    stopChord = iChord - 1;
                }
            } else {
                // no, we need no line of the given line for this stem
                // so, close the last group
                stopChord = iChord - 1;
            }
        } else {
            // no more chord in the beam, so we have to close
            stopChord = iChord - 1;
        }
        // if a group was closed, create it
        if (startChord > -1 && stopChord > -1) {
            // type of line is dependent on number of chords in the group
            int chordsCount = stopChord - startChord + 1;
            if (chordsCount > 1) {
                // simple case: more than one chord. create a normal line
                // between those stems
                ret.set(startChord, Start);
                ret.set(stopChord, Stop);
            } else {
                // more difficult case: exactly one chord.
                if (startChord == 0) {
                    // first chord in beam has always hook to the right
                    ret.set(startChord, HookRight);
                } else if (startChord == beam.getWaypoints().size() - 1) {
                    // last chord in beam has always hook to the left
                    ret.set(startChord, HookLeft);
                } else {
                    // middle chords have left hook, if the preceding chord
                    // has a longer or equal duration than the following chord,
                    // otherwise they have a right hook
                    Fraction left = beam.getChord(startChord - 1).getDuration();
                    Fraction right = beam.getChord(startChord + 1).getDuration();
                    if (left.compareTo(right) >= 0)
                        ret.set(startChord, HookLeft);
                    else
                        ret.set(startChord, HookRight);
                }
            }
            // reset group data
            startChord = -1;
            stopChord = -1;
            lastFlagsCount = -1;
        }
    }
    return ret;
}
Also used : Fragments(com.xenoage.zong.musiclayout.notation.beam.Fragments) Fraction(com.xenoage.utils.math.Fraction) Chord(com.xenoage.zong.core.music.chord.Chord)

Example 24 with Fraction

use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.

the class ChordSpacings method getWidth.

/**
 * Computes and returns the width that fits to the given duration.
 */
public float getWidth(@NonNull Fraction duration) {
    checkArgsNotNull(duration);
    // if available, use defined width
    Float width = durationWidths.get(duration);
    if (width != null)
        return width;
    // if available, use cached computed width
    width = durationWidthsCache.get(duration);
    if (width != null)
        return width;
    // not found. find the greatest lesser duration and the lowest
    // greater duration and interpolate linearly. remember the result
    // to avoid this computation for the future.
    Fraction lowerDur = durationWidthsLowestDuration;
    Fraction higherDur = durationWidthsHighestDuration;
    for (Fraction d : durationWidths.keySet()) {
        if (d.compareTo(duration) <= 0 && d.compareTo(lowerDur) > 0) {
            lowerDur = d;
        }
        if (d.compareTo(duration) >= 0 && d.compareTo(higherDur) < 0) {
            higherDur = d;
        }
    }
    float lowerWidth = durationWidths.get(lowerDur);
    float higherWidth = durationWidths.get(higherDur);
    float durationWidth = (lowerWidth + higherWidth) * duration.toFloat() / (lowerDur.toFloat() + higherDur.toFloat());
    durationWidthsCache.put(duration, durationWidth);
    return durationWidth;
}
Also used : Fraction(com.xenoage.utils.math.Fraction)

Example 25 with Fraction

use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.

the class ScoreReader method readToScore.

public void readToScore(Score score, ErrorHandling errorHandling) {
    Context context = new Context(score, new ReaderSettings(errorHandling));
    // create the measures of the parts
    It<MxlPart> mxlParts = it(doc.getParts());
    for (MxlPart mxlPart : mxlParts) {
        // create measures
        execute(new MeasureAddUpTo(score, mxlPart.getMeasures().size()));
        // initialize each measure with a C clef
        Part part = score.getStavesList().getParts().get(mxlParts.getIndex());
        StavesRange stavesRange = score.getStavesList().getPartStaffIndices(part);
        for (int staff : stavesRange.getRange()) {
            execute(new MeasureElementWrite(new Clef(ClefType.Companion.getClefTreble()), score.getMeasure(MP.atMeasure(staff, 0)), Companion.get_0()));
        }
    }
    // write a 4/4 measure and C key signature in the first measure
    execute(new ColumnElementWrite(new TimeSignature(TimeType.Companion.getTime_4_4()), score.getColumnHeader(0), Companion.get_0(), null));
    execute(new ColumnElementWrite(new TraditionalKey(0), score.getColumnHeader(0), Companion.get_0(), null));
    // read the parts
    mxlParts = it(doc.getParts());
    for (MxlPart mxlPart : mxlParts) {
        // clear part-dependent context values
        context.beginNewPart(mxlParts.getIndex());
        // read the measures
        It<MxlMeasure> mxlMeasures = it(mxlPart.getMeasures());
        for (MxlMeasure mxlMeasure : mxlMeasures) {
            try {
                MeasureReader.readToContext(mxlMeasure, mxlMeasures.getIndex(), context);
            } catch (MusicReaderException ex) {
                throw new RuntimeException("Error at " + ex.getContext().toString(), ex);
            } catch (Exception ex) {
                throw new RuntimeException("Error (roughly) around " + context.toString(), ex);
            }
        }
    }
    // remove unclosed elements
    context.removeUnclosedWedges();
    // go through the whole score, and fill empty measures (that means, measures where
    // voice 0 has no single VoiceElement) with rests
    Fraction measureDuration = Companion.fr(1, 4);
    for (int iStaff = 0; iStaff < score.getStavesCount(); iStaff++) {
        Staff staff = score.getStaff(atStaff(iStaff));
        for (int iMeasure : range(staff.getMeasures())) {
            Measure measure = staff.getMeasure(iMeasure);
            TimeSignature newTime = score.getHeader().getColumnHeader(iMeasure).getTime();
            if (newTime != null) {
                // time signature has changed
                measureDuration = newTime.getType().getMeasureBeats();
            }
            if (measureDuration == null) {
                // senza misura
                // use whole rest
                measureDuration = Companion.fr(4, 4);
            }
            Voice voice0 = measure.getVoice(0);
            if (voice0.isEmpty()) {
                // TODO: "whole rests" or split. currently, also 3/4 rests are possible
                MP mp = atElement(iStaff, iMeasure, 0, 0);
                new VoiceElementWrite(score.getVoice(mp), mp, new Rest(measureDuration), null).execute();
            }
        }
    }
}
Also used : VoiceElementWrite(com.xenoage.zong.commands.core.music.VoiceElementWrite) MP.atStaff(com.xenoage.zong.core.position.MP.atStaff) Staff(com.xenoage.zong.core.music.Staff) ColumnElementWrite(com.xenoage.zong.commands.core.music.ColumnElementWrite) MxlPart(com.xenoage.zong.musicxml.types.partwise.MxlPart) Clef(com.xenoage.zong.core.music.clef.Clef) TraditionalKey(com.xenoage.zong.core.music.key.TraditionalKey) MxlMeasure(com.xenoage.zong.musicxml.types.partwise.MxlMeasure) MeasureAddUpTo(com.xenoage.zong.commands.core.music.MeasureAddUpTo) StavesRange(com.xenoage.zong.core.music.group.StavesRange) Rest(com.xenoage.zong.core.music.rest.Rest) MxlMeasure(com.xenoage.zong.musicxml.types.partwise.MxlMeasure) Measure(com.xenoage.zong.core.music.Measure) MP(com.xenoage.zong.core.position.MP) MusicReaderException(com.xenoage.zong.io.musicxml.in.util.MusicReaderException) Fraction(com.xenoage.utils.math.Fraction) MusicReaderException(com.xenoage.zong.io.musicxml.in.util.MusicReaderException) TimeSignature(com.xenoage.zong.core.music.time.TimeSignature) MxlPart(com.xenoage.zong.musicxml.types.partwise.MxlPart) Part(com.xenoage.zong.core.music.Part) MeasureElementWrite(com.xenoage.zong.commands.core.music.MeasureElementWrite) Voice(com.xenoage.zong.core.music.Voice)

Aggregations

Fraction (com.xenoage.utils.math.Fraction)32 BeatOffset (com.xenoage.zong.musiclayout.spacing.BeatOffset)6 Chord (com.xenoage.zong.core.music.chord.Chord)5 ElementSpacing (com.xenoage.zong.musiclayout.spacing.ElementSpacing)5 lombok.val (lombok.val)5 Voice (com.xenoage.zong.core.music.Voice)4 Measure (com.xenoage.zong.core.music.Measure)3 VoiceElement (com.xenoage.zong.core.music.VoiceElement)3 Clef (com.xenoage.zong.core.music.clef.Clef)3 TraditionalKey (com.xenoage.zong.core.music.key.TraditionalKey)3 TimeSignature (com.xenoage.zong.core.music.time.TimeSignature)3 VoiceSpacing (com.xenoage.zong.musiclayout.spacing.VoiceSpacing)3 ColumnElementWrite (com.xenoage.zong.commands.core.music.ColumnElementWrite)2 Score (com.xenoage.zong.core.Score)2 Part (com.xenoage.zong.core.music.Part)2 Note (com.xenoage.zong.core.music.chord.Note)2 Rest (com.xenoage.zong.core.music.rest.Rest)2 ChordNotation (com.xenoage.zong.musiclayout.notation.ChordNotation)2 JsonArray (com.google.gson.JsonArray)1 JsonObject (com.google.gson.JsonObject)1