package com.xenoage.zong.musiclayout.notator.beam;
import com.xenoage.utils.math.Fraction;
import com.xenoage.zong.core.music.beam.Beam;
import com.xenoage.zong.core.music.chord.Chord;
import com.xenoage.zong.musiclayout.notation.beam.Fragments;
import java.util.List;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.zong.core.music.util.DurationInfo.getFlagsCount;
import static com.xenoage.zong.musiclayout.notation.beam.Fragment.*;
/**
* Computes the {@link Fragments} of the lines of a {@link Beam}.
*
* @author Andreas Wenger
*/
public class BeamFragmenter {
public static final BeamFragmenter beamFragmenter = new BeamFragmenter();
/**
* Computes the line fragments for all lines of the given beam,
* starting with the 16th, then 32nd, ... line.
*/
public List<Fragments> compute(Beam beam) {
int linesCount = beam.getMaxLinesCount();
List<Fragments> ret = alist(linesCount - 1);
Fragments lastFragments = null;
for (int line : range(1, linesCount - 1))
ret.add(lastFragments = compute(beam, line, lastFragments));
return ret;
}
/**
* 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;
int startChord = -1; //start chord of the last group, or -1 if no group is open
int stopChord = -1; //stop chord of the last group, or -1 if group is open
for (int iChord : range(beam.size() + 1)) {
if (iChord < beam.getWaypoints().size()) {
//another chord within the beam
Chord chord = beam.getChord(iChord);
int flagsCount = 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 && (flagsCount < flagsCount || //less flags than previous stem
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;
}
}