//----------------------------------------------------------------------------//
// //
// L y r i c s L i n e //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.score.entity;
import omr.constant.ConstantSet;
import omr.math.Population;
import omr.sheet.Scale;
import omr.util.TreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Class {@code LyricsLine} gathers one line of lyrics within a system
* part.
* A lyrics line is composed of instances of LyricsItem, which can be Syllables,
* Hyphens, Extensions or Elisions
*
* @author Hervé Bitteur
*/
public class LyricsLine
extends PartNode
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(LyricsLine.class);
/** For comparing (TreeNode) LyricsLine instances on their ordinate */
public static final Comparator<TreeNode> yComparator = new Comparator<TreeNode>()
{
@Override
public int compare (TreeNode tn1,
TreeNode tn2)
{
LyricsLine l1 = (LyricsLine) tn1;
LyricsLine l2 = (LyricsLine) tn2;
return Integer.signum(l1.getY() - l2.getY());
}
};
//~ Instance fields --------------------------------------------------------
/** The line number */
private int id;
/** The mean ordinate (in units) within the system */
private Integer y;
/** The x-ordered collection of lyrics items */
private final SortedSet<LyricsItem> items = new TreeSet<>();
//~ Constructors -----------------------------------------------------------
/**
* Creates a new LyricsLine object.
*
* @param systemPart the containing system part
*/
public LyricsLine (SystemPart systemPart)
{
super(systemPart);
}
//~ Methods ----------------------------------------------------------------
//----------//
// populate //
//----------//
/**
* Populate a Lyrics line with this lyrics item
*
* @param item the lyrics item to host in a lyrics line
* @param part the containing system part
*/
public static void populate (LyricsItem item,
SystemPart part)
{
logger.debug("LyricsLine. populate with {}", item);
// First look for a suitable lyrics line
for (TreeNode node : part.getLyrics()) {
LyricsLine line = (LyricsLine) node;
if (line.isAlignedWith(item.getReferencePoint())) {
line.addItem(item);
logger.debug("Added {} into {}", item, line);
return;
}
}
// No compatible line, create a brand new one
LyricsLine line = new LyricsLine(part);
line.addItem(item);
logger.debug("Created new {}", line);
}
//------------------//
// getFollowingLine //
//------------------//
/**
* Retrieve the corresponding lyrics line in the next part, if any
*
* @return the next lyrics line, or null
*/
public LyricsLine getFollowingLine ()
{
LyricsLine nextLine = null;
// Check existence of similar line in following system part
SystemPart nextPart = getPart().getFollowing();
if (nextPart != null) {
// Retrieve the same lyrics line in the next (system) part
if (nextPart.getLyrics().size() >= id) {
nextLine = (LyricsLine) nextPart.getLyrics().get(id - 1);
}
}
return nextLine;
}
//-------//
// getId //
//-------//
public int getId ()
{
return id;
}
//------------------//
// getPrecedingLine //
//------------------//
/**
* Retrieve the corresponding lyrics line in the preceding part, if any
*
* @return the preceding lyrics line, or null
*/
public LyricsLine getPrecedingLine ()
{
// Check existence of similar line in preceding system part
SystemPart part = getPart();
if ((part != null) && (part.getLyrics().size() >= id)) {
return (LyricsLine) part.getLyrics().get(id - 1);
}
return null;
}
//------//
// getY //
//------//
/**
* Report the ordinate of this line
*
* @return the mean line ordinate, wrt the containing system
*/
public int getY ()
{
if (y == null) {
Population population = new Population();
for (LyricsItem item : items) {
population.includeValue(item.getReferencePoint().y);
}
if (population.getCardinality() > 0) {
y = (int) Math.rint(population.getMeanValue());
}
}
return y;
}
//----------------------//
// refineLyricSyllables //
//----------------------//
public void refineLyricSyllables ()
{
// Last item of preceding line is any
LyricsItem precedingItem = null;
LyricsLine precedingLine = getPrecedingLine();
if (precedingLine != null) {
precedingItem = precedingLine.items.last();
}
// Now browse sequentially all our line items
LyricsItem[] itemArray = items.toArray(new LyricsItem[items.size()]);
for (int i = 0; i < itemArray.length; i++) {
LyricsItem item = itemArray[i];
// Following item (perhaps to be found in following line if needed)
LyricsItem followingItem = null;
if (i < (itemArray.length - 1)) {
followingItem = itemArray[i + 1];
} else {
LyricsLine followingLine = getFollowingLine();
if (followingLine != null) {
followingItem = followingLine.items.first();
}
}
// We process only syllable items
if (item.getItemKind() == LyricsItem.ItemKind.Syllable) {
item.defineSyllabicType(precedingItem, followingItem);
}
precedingItem = item;
}
}
//-------//
// setId //
//-------//
public void setId (int id)
{
this.id = id;
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder();
sb.append("{LyricLine #").append(getId()).append(" y:").append(y).append(
" items:").append(items).append("}");
return sb.toString();
}
//--------------//
// mapSyllables //
//--------------//
void mapSyllables ()
{
for (LyricsItem item : items) {
item.mapToNote();
}
}
//---------//
// addItem //
//---------//
private void addItem (LyricsItem item)
{
items.add(item);
item.setLyricLine(this);
// Force recomputation of line mean ordinate
y = null;
getY();
}
//---------------//
// isAlignedWith //
//---------------//
/**
* Check whether a system point is roughly aligned with this line instance
*
* @param sysPt the system point to check
* @return true if aligned
*/
private boolean isAlignedWith (Point sysPt)
{
return Math.abs(sysPt.y - getY()) <= getScale().toPixels(
constants.maxItemDy);
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction maxItemDy = new Scale.Fraction(
2,
"Maximum vertical distance between a lyrics line and a lyrics item");
}
}