package com.xenoage.zong.musiclayout.layouter.scoreframelayout;
import static com.xenoage.zong.core.music.format.SP.sp;
import static com.xenoage.zong.core.text.FormattedText.fText;
import static com.xenoage.zong.core.text.FormattedTextParagraph.fPara;
import com.xenoage.zong.core.music.chord.Chord;
import com.xenoage.zong.core.music.tuplet.Tuplet;
import com.xenoage.zong.core.text.FormattedText;
import com.xenoage.zong.core.text.FormattedTextParagraph;
import com.xenoage.zong.core.text.FormattedTextStyle;
import com.xenoage.zong.core.text.FormattedTextSymbol;
import com.xenoage.zong.musiclayout.layouter.cache.OpenTupletsCache;
import com.xenoage.zong.musiclayout.layouter.scoreframelayout.util.ChordStampings;
import com.xenoage.zong.musiclayout.stampings.StaffStamping;
import com.xenoage.zong.musiclayout.stampings.TupletStamping;
import com.xenoage.zong.symbols.SymbolPool;
import com.xenoage.zong.symbols.common.CommonSymbol;
/**
* Creates the {@link TupletStamping}s for tuplets.
*
* The rules are inspired by Chlapik, page 66 and 67,
* but not all rules are implemented yet.
*
* Currently, only single-measure tuplets are supported.
*
* @author Andreas Wenger
*/
public class TupletStamper {
public static final TupletStamper tupletStamper = new TupletStamper();
/**
* Computes the {@link TupletStamping} for the given {@link ChordStampings}
* and returns it.
*/
public TupletStamping createTupletStamping(Tuplet tuplet, OpenTupletsCache cache,
SymbolPool symbolPool) {
StaffStamping ss = cache.getChord(tuplet.getChords().get(0), tuplet).staff;
//horizontal position of the bracket
float xDistance = ss.is / 4;
float x1Mm = cache.getChord(tuplet.getFirstChord(), tuplet).xMm - xDistance;
ChordStampings cs2 = cache.getChord(tuplet.getLastChord(), tuplet);
float cs2Width = ss.is * 1.2f; //TODO: notehead width!
float x2Mm = cs2.xMm + cs2Width + xDistance;
//vertical position of the bracket (above or below) depends on the directions
//of the majority of the stems
int stemDir = 0;
for (Chord chord : tuplet.getChords()) {
ChordStampings cs = cache.getChord(chord, tuplet);
if (cs.stem != null) {
stemDir += cs.stem.direction.getSign();
}
}
int placement = (stemDir < 0 ? 1 : -1); //1: above, -1: below
//compute position of start and end point
//by default, the bracket is 1.5 IS away from the end of the stems
//when there is no stem, the innermost notehead is used
//TODO: if stems of inner chords are longer, correct!
float distanceLp = 1.5f * 2;
float y1Lp = computeBracketLP(cache.getChord(tuplet.getFirstChord(), tuplet), placement,
distanceLp);
float y2Lp = computeBracketLP(cache.getChord(tuplet.getLastChord(), tuplet), placement,
distanceLp);
//bracket always outside of staff lines
//at least 2 IS over top barline / under bottom barline
if (placement == 1) //above staff
{
y1Lp = Math.max(y1Lp, (ss.linesCount - 1) * 2 + 4);
y2Lp = Math.max(y1Lp, (ss.linesCount - 1) * 2 + 4);
}
else //below staff
{
y1Lp = Math.min(y1Lp, 0 - 4);
y2Lp = Math.min(y1Lp, 0 - 4);
}
//text
float fontSize = 10 * ss.is / 1.6f;
FormattedText text = createText(tuplet.getActualNotes(), fontSize, symbolPool);
//return result
return new TupletStamping(sp(x1Mm, y1Lp), sp(x2Mm, y2Lp), true, text, ss);
}
private float computeBracketLP(ChordStampings cs, int placement, float distanceLp) {
if (cs.stem != null) {
return cs.stem.endLp + placement * distanceLp;
}
else {
if (placement == 1) //distance to top notehead
{
return cs.getLastNotehead().position.lp + placement * distanceLp;
}
else //distance to bottom notehead
{
return cs.getFirstNotehead().position.lp + placement * distanceLp;
}
}
}
private FormattedText createText(int digit, float fontSize, SymbolPool symbolPool) {
if (digit < 0 || digit > 9)
return null; //TODO
CommonSymbol s = CommonSymbol.getDigit(digit);
FormattedTextParagraph p = fPara(new FormattedTextSymbol(symbolPool.getSymbol(s), fontSize,
FormattedTextStyle.defaultColor));
return fText(p);
}
}