package com.xenoage.zong.musiclayout.stamper; import com.xenoage.utils.collections.CList; import com.xenoage.zong.core.music.MusicElementType; import com.xenoage.zong.core.music.chord.Chord; import com.xenoage.zong.core.music.direction.*; import com.xenoage.zong.core.music.format.Placement; import com.xenoage.zong.core.music.format.Position; import com.xenoage.zong.core.music.format.Positioning; import com.xenoage.zong.core.music.format.SP; import com.xenoage.zong.core.music.util.BeatE; import com.xenoage.zong.core.position.MP; import com.xenoage.zong.core.text.*; import com.xenoage.zong.musiclayout.layouter.scoreframelayout.util.ChordStampings; import com.xenoage.zong.musiclayout.stampings.StaffStamping; import com.xenoage.zong.musiclayout.stampings.StaffSymbolStamping; import com.xenoage.zong.musiclayout.stampings.StaffTextStamping; import com.xenoage.zong.musiclayout.stampings.Stamping; import com.xenoage.zong.symbols.Symbol; import com.xenoage.zong.symbols.SymbolPool; import com.xenoage.zong.symbols.common.CommonSymbol; import lombok.val; import java.util.List; import static com.xenoage.utils.NullUtils.notNull; import static com.xenoage.utils.collections.CList.clist; import static com.xenoage.utils.collections.CollectionUtils.alist; 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.FormattedTextUtils.styleText; import static com.xenoage.zong.musiclayout.text.DefaultTexts.getTempoTextNotNull; import static java.util.Collections.emptyList; /** * Computes the {@link Stamping}s of {@link Direction}s. * * @author Andreas Wenger */ public class DirectionStamper { public static final DirectionStamper directionStamper = new DirectionStamper(); private static final float FONT_SIZE_IN_IS = 3.5f * 2.67f; //TODO /** * Creates all direction stampings for the given measure. */ public List<Stamping> stamp(StamperContext context) { List<Stamping> ret = alist(); val directionsWithBeats = context.getCurrentMeasure().getDirections().clone(); //over first staff, also add tempo directions for the whole column if (context.staffIndex == 0) { directionsWithBeats.addAll(context.getCurrentColumnHeader().getTempos()); } for (BeatE<Direction> elementWithBeat : directionsWithBeats) { Direction element = elementWithBeat.element; Stamping stamping = null; if (MusicElementType.Tempo.is(element)) stamping = directionStamper.createTempo((Tempo) element, context); else if (MusicElementType.Dynamic.is(element)) stamping = directionStamper.createDynamics((Dynamic) element, context); else if (MusicElementType.Pedal.is(element)) stamping = directionStamper.createPedal((Pedal) element, context); else if (MusicElementType.Words.is(element)) stamping = directionStamper.createWords((Words) element, context); if (stamping != null) ret.add(stamping); } return ret; } /** * Creates the {@link StaffTextStamping}s for the {@link Direction}s of * the given {@link ChordStampings}. */ public List<StaffTextStamping> stampForChord(ChordStampings chordStampings, SymbolPool symbolPool) { Chord chord = chordStampings.chord; int directionsCount = chord.getDirections().size(); if (directionsCount == 0) return emptyList(); List<StaffTextStamping> ret = alist(); for (Direction direction : chord.getDirections()) { if (direction instanceof Dynamic) { ret.add(createDynamics((Dynamic) direction, chord, chordStampings, symbolPool)); } //TODO: support more directions } return ret; } /** * Creates a {@link StaffTextStamping} for the given {@link Dynamic}s * below the given {@link Chord} and its {@link ChordStampings}. */ public StaffTextStamping createDynamics(Dynamic dynamics, Chord chord, ChordStampings chordStampings, SymbolPool symbolPool) { StaffStamping staff = chordStampings.staff; //positioning //below (default): 3 IS below the base line, or 2 IS below the lowest note //above: 2 IS above the top line, or 1 IS above the highest note float defaultLPBelow = -3f * 2; float defaultLPAbove = (staff.linesCount - 1) * 2 + 2 * 2; if (chordStampings.noteheads.length > 0) { defaultLPBelow = Math.min(defaultLPBelow, chordStampings.getFirstNotehead().position.lp - 2 * 2); defaultLPAbove = Math.max(defaultLPAbove, chordStampings.getLastNotehead().position.lp + 1 * 2); } SP sp = computePosition(dynamics, staff, defaultLPBelow, defaultLPAbove, defaultLPBelow); //create text CList<FormattedTextElement> elements = clist(); for (CommonSymbol s : CommonSymbol.getDynamics(dynamics.getValue())) { Symbol symbol = symbolPool.getSymbol(s); elements.add(new FormattedTextSymbol(symbol, staff.is * FONT_SIZE_IN_IS, FormattedTextStyle.defaultColor)); } elements.close(); FormattedTextParagraph paragraph = new FormattedTextParagraph(elements, Alignment.Center); FormattedText text = fText(paragraph); //create stamping return new StaffTextStamping(text, sp, staff, dynamics); } /** * Creates a {@link StaffTextStamping} for the given {@link Dynamic}s. */ public StaffTextStamping createDynamics(Dynamic dynamics, StamperContext context) { val staff = context.getCurrentStaffStamping(); //positioning //below (default): 3 IS below the base line, or 2 IS below the lowest note //above: 2 IS above the top line, or 1 IS above the highest note float defaultLPBelow = -3f * 2; float defaultLPAbove = (context.getCurrentStaffStamping().linesCount - 1) * 2 + 2 * 2; SP sp = computePosition(dynamics, staff, defaultLPBelow, defaultLPAbove, defaultLPBelow); //create text CList<FormattedTextElement> elements = clist(); for (CommonSymbol s : CommonSymbol.getDynamics(dynamics.getValue())) { Symbol symbol = context.getSymbol(s); elements.add(new FormattedTextSymbol(symbol, staff.is * FONT_SIZE_IN_IS, FormattedTextStyle.defaultColor)); } elements.close(); FormattedTextParagraph paragraph = new FormattedTextParagraph(elements, Alignment.Center); FormattedText text = fText(paragraph); //create stamping return new StaffTextStamping(text, sp, staff, dynamics); } /** * Creates a {@link StaffTextStamping} for the given {@link Tempo}. */ public StaffTextStamping createTempo(Tempo tempo, StamperContext context) { val staff = context.getCurrentStaffStamping(); //positioning //below: 3 IS below the base line //above (default): 2 IS above the top line float defaultLPBelow = -3f * 2; float defaultLPAbove = (staff.linesCount - 1) * 2 + 2 * 2; SP p = computePosition(tempo, staff, defaultLPAbove, defaultLPAbove, defaultLPBelow); //create text FormattedText text = getTempoTextNotNull(tempo, context.layouter.symbols); //create stamping return new StaffTextStamping(text, p, staff, tempo); } /** * Creates a {@link StaffTextStamping} for the given {@link Words}. */ public StaffTextStamping createWords(Words words, StamperContext context) { val staff = context.getCurrentStaffStamping(); if (words.getText().getLength() == 0) return null; //positioning //below: 5 IS below the base line //above (default): 4 IS above the top line float defaultLPBelow = -5f * 2; float defaultLPAbove = (staff.linesCount - 1) * 2 + 4 * 2; SP p = computePosition(words, staff, defaultLPAbove, defaultLPAbove, defaultLPBelow); //create text FormattedTextStyle style = FormattedTextStyle.defaultStyle; //TODO: FormattedTextStyle(words.getFontInfo()); FormattedText text = styleText(words.getText(), style); //create stamping return new StaffTextStamping(text, p, staff, words); } /** * Creates a {@link StaffSymbolStamping} for the given {@link Pedal}. */ public StaffSymbolStamping createPedal(Pedal pedal, StamperContext context) { val staff = context.getCurrentStaffStamping(); //positioning //below (default): 4 IS below the base line //above: 3 IS above the top line float defaultLPBelow = -4f * 2; float defaultLPAbove = (staff.linesCount - 1) * 2 + 3 * 2; SP sp = computePosition(pedal, staff, defaultLPBelow, defaultLPAbove, defaultLPBelow); //create stamping Symbol symbol = context.getSymbol(CommonSymbol.getPedal(pedal.getType())); return new StaffSymbolStamping(null, staff, symbol, null, sp, 1, false); } /** * Computes the position for the given {@link Direction} * within the given {@link StaffStamping}. */ private SP computePosition(Direction direction, StaffStamping staffStamping, float defaultLP, float defaultLPAbove, float defaultLPBelow) { Positioning customPos = direction.getPositioning(); MP mp = direction.getMP(); if (mp == null) return SP.sp(0, 0); //TODO. Happens. But just with offical MusicXML sample Telemann.xml float x, lp; //default positioning x = notNull(staffStamping.system.getXMmAt(mp.getTime()), 0f) + staffStamping.positionMm.x; lp = defaultLP; //custom positioning if (customPos instanceof Placement) { Placement placement = (Placement) customPos; if (placement == Placement.Above) lp = defaultLPAbove; else lp = defaultLPBelow; } else if (customPos instanceof Position) { //coordinates Position pos = (Position) customPos; if (pos.x != null) x = pos.x; x += Position.getRelativeX(pos); //vertical position if (pos.y != null) lp = pos.y; lp += Position.getRelativeY(pos); } return sp(x, lp); } }