//----------------------------------------------------------------------------//
// //
// B e a m I t e m //
// //
//----------------------------------------------------------------------------//
// <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.Constant;
import omr.constant.ConstantSet;
import omr.glyph.Shape;
import omr.glyph.facets.Glyph;
import omr.math.BasicLine;
import omr.math.Line;
import omr.util.HorizontalSide;
import omr.util.TreeNode;
import omr.util.Vip;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.Comparator;
/**
* Class {@code BeamItem} represents either a single beam hook
* (stuck to 1 stem) or a beam (stuck to 2 stems).
* By extension, a BeamItem can be a logical part of thick beam "pack" due to
* glyphs stuck vertically.
*
* @author Hervé Bitteur
*/
public class BeamItem
extends MeasureNode
implements Vip
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
BeamItem.class);
/** Compare two BeamItem instances by abscissa within a Beam. */
public static final Comparator<TreeNode> byNodeAbscissa = new Comparator<TreeNode>()
{
@Override
public int compare (TreeNode tn1,
TreeNode tn2)
{
BeamItem item1 = (BeamItem) tn1;
BeamItem item2 = (BeamItem) tn2;
// Delegate to underlying glyph
return Glyph.byAbscissa.compare(item1.getGlyph(), item2.getGlyph());
}
};
//~ Instance fields --------------------------------------------------------
//
/** (Debug) flag this object as VIP. */
private boolean vip;
/**
* Cardinality of the containing beam pack (nb of stuck items).
* Card = 1 for an isolated beam
*/
private final int packCard;
/** Index within the beam pack. Index = 0 for an isolated beam */
private final int packIndex;
/** Line equation for the beam item. */
private Line line;
/** Left point of beam item. */
private Point left;
/** Right point of beam item. */
private Point right;
//~ Constructors -----------------------------------------------------------
//
//----------//
// BeamItem //
//----------//
/** Create a new instance of beam item, as part of a beam item pack.
*
* @param Beam the containing beam instance
* @param glyph the underlying glyph
* @param left the left defining point
* @param right the right defining point
* @param packCard the number of items in the pack
* @param packIndex the zero-based index of this item in the pack
*/
private BeamItem (Beam beam,
Glyph glyph,
Point left,
Point right,
int packCard,
int packIndex)
{
super(beam);
addGlyph(glyph);
this.packCard = packCard;
this.packIndex = packIndex;
this.left = new Point(left);
this.right = new Point(right);
// Keep the items sorted by abscissa in Beam container
Collections.sort(beam.getItems(), BeamItem.byNodeAbscissa);
if (glyph.isVip()) {
setVip();
beam.setVip();
}
if (isVip() || logger.isDebugEnabled()) {
logger.info("{} Created {}", beam.getContextString(), this);
}
}
//~ Methods ----------------------------------------------------------------
//----------//
// populate //
//----------//
/**
* Populate a BeamItem with this glyph, or a series of BeamItem's
* if the glyph is a beam pack.
*
* @param glyph glyph of the beam, or beam pack
* @param measure the containing measure
*/
public static void populate (Glyph glyph,
Measure measure)
{
if (glyph.isVip()) {
logger.info("BeamItem. populate {}", glyph.idString());
}
createPack(measure, glyph);
}
//----------//
// getGlyph //
//----------//
/**
* Report the underlying glyph.
*
* @return the underlying glyph
*/
public Glyph getGlyph ()
{
return glyphs.first();
}
//---------//
// getLine //
//---------//
/**
* Report the (horizontal) line equation defined by the beam item.
*
* @return the line equation
*/
public Line getLine ()
{
if (line == null) {
line = new BasicLine();
// Take left and right points of this beam item
line.includePoint(left.x, left.y);
line.includePoint(right.x, right.y);
}
return line;
}
//----------//
// getPoint //
//----------//
/**
* Report the point that defines the desired edge of the beam item.
*
* @return (a copy) of the point on desired side
*/
public Point getPoint (HorizontalSide side)
{
if (side == HorizontalSide.LEFT) {
return new Point(left);
} else {
return new Point(right);
}
}
//---------//
// getStem //
//---------//
/**
* Report the stem (if any) of this beam item on the desired side.
*
* @return the found stem or null
*/
public Glyph getStem (HorizontalSide side)
{
return getGlyph()
.getStem(side);
}
//--------//
// isHook //
//--------//
/**
* Check whether the item is a beam hook.
*
* @return true if beam hook, false otherwise
*/
public boolean isHook ()
{
return getGlyph()
.getShape() == Shape.BEAM_HOOK;
}
//-------//
// isVip //
//-------//
@Override
public boolean isVip ()
{
return vip;
}
//----------//
// setPoint //
//----------//
/**
* Set the point that defines the desired edge of the beam item.
*
* @param side the desired side
* @param point the new point on desired side
*/
public void setPoint (HorizontalSide side,
Point point)
{
if (side == HorizontalSide.LEFT) {
this.left = point;
} else {
this.right = point;
}
// Invalidate
line = null;
center = null;
}
//--------//
// setVip //
//--------//
@Override
public void setVip ()
{
vip = true;
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder();
sb.append("{BeamItem");
try {
sb.append(" ")
.append(getGlyph().idString());
if (packCard != 1) {
sb.append(" [")
.append(packIndex)
.append("/")
.append(packCard)
.append("]");
}
sb.append(" left=[")
.append(left.x)
.append(",")
.append(left.y)
.append("]");
sb.append(" center=[")
.append(getCenter().x)
.append(",")
.append(getCenter().y)
.append("]");
sb.append(" right=[")
.append(right.x)
.append(",")
.append(right.y)
.append("]");
sb.append(" slope=")
.append((float) getLine().getSlope());
} catch (NullPointerException e) {
sb.append(" INVALID");
}
sb.append("}");
return sb.toString();
}
//---------------//
// computeCenter //
//---------------//
/**
* Compute the center of this beam item (which is different from the
* glyph center in case of a multi-beam pack glyph).
*/
@Override
protected void computeCenter ()
{
setCenter(new Point((left.x + right.x) / 2, (left.y + right.y) / 2));
}
//------------//
// createPack //
//------------//
/**
* Create a bunch of BeamItem instances for one pack, and create
* or augment the containing Beam instance.
*
* @param measure the containing measure
* @param glyph the underlying glyph of the beam pack
*/
private static void createPack (Measure measure,
Glyph glyph)
{
int card = packCardOf(glyph.getShape());
glyph.clearTranslations();
try {
for (int i = 0; i < card; i++) {
// Compute item defining points
Point left;
Point right;
// For hooks, the stick line is not reliable
Rectangle box = glyph.getBounds();
if (glyph.getShape() == Shape.BEAM_HOOK) {
// Make a simple horizontal beam item
left = new Point(box.x, box.y + (box.height / 2));
right = new Point(
(box.x + box.width) - 1,
box.y + (box.height / 2));
} else {
// Check line slope
Line glyphLine = glyph.getLine();
if (Math.abs(glyphLine.getSlope()) > constants.maxBeamSlope.getValue()) {
// Slope is not realistic, use horizontal lines
double halfHeight = box.height / (card * 2);
int y = box.y
+ (int) Math.rint(halfHeight * ((2 * i) + 1));
left = new Point(box.x, y);
right = new Point((box.x + box.width) - 1, y);
} else {
double yMidLeft = glyphLine.yAtX((double) box.x);
double yMidRight = glyphLine.yAtX(
(double) ((box.x + box.width) - 1));
double deltaMid1 = Math.min(yMidLeft, yMidRight)
- box.y;
double deltaMid2 = (box.y + box.height)
- Math.max(yMidLeft, yMidRight);
double deltaMid = (deltaMid1 + deltaMid2) / 2.0;
double deltaY = (((4 * i) + 1) * deltaMid) / ((2 * card)
- 1);
int highY = (int) Math.rint(box.y + deltaY);
int lowY = (int) Math.rint(
(box.y + box.height) - (2 * deltaMid) + deltaY);
if (yMidLeft > yMidRight) {
// This is an ascending beam
left = new Point(box.x, lowY);
right = new Point((box.x + box.width) - 1, highY);
} else {
// This is a descending beam
left = new Point(box.x, highY);
right = new Point((box.x + box.width) - 1, lowY);
}
}
}
// Retrieve/create the hosting Beam instance
Beam beam = Beam.populate(left, right, measure);
// Finally, allocate the BeamItem instance
BeamItem item = new BeamItem(beam, glyph, left, right, card, i);
glyph.addTranslation(item);
}
} catch (Exception ex) {
logger.warn(
measure.getContextString() + " Error creating BeamItem from "
+ glyph.idString(),
ex);
}
}
//------------//
// packCardOf //
//------------//
/**
* Report the cardinality inferred from the glyph shape.
*
* @param shape the shape of the underlying glyph
* @return the number of beam items for this shape
*/
private static int packCardOf (Shape shape)
{
switch (shape) {
case BEAM_3:
return 3;
case BEAM_2:
return 2;
case BEAM:
case BEAM_HOOK:
return 1;
default:
logger.error("Use of BeamItem.packCardOf with shape {}", shape);
return 0;
}
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Constant.Double maxBeamSlope = new Constant.Double(
"slope",
1.0,
"Maximum slope for a beam item line");
}
}