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));
}
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;
}
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;
}
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;
}
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();
}
}
}
}
Aggregations