//----------------------------------------------------------------------------//
// //
// B e a 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.Main;
import omr.constant.ConstantSet;
import omr.glyph.facets.Glyph;
import omr.math.BasicLine;
import omr.math.Line;
import omr.score.visitor.ScoreVisitor;
import omr.sheet.Scale;
import omr.util.HorizontalSide;
import static 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
/**
* Class {@code Beam} represents a beam "line", that may be composed of
* several BeamItem "segments", aligned horizontally one after the
* other, along the same line.
* It can degenerate to just a single beam hook.
*
* <div style="float: right;">
* <img src="doc-files/Beam.jpg" alt="diagram">
* </div>
*
* @author Hervé Bitteur
*/
public class Beam
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(Beam.class);
//~ Instance fields --------------------------------------------------------
//
/** (Debug) flag this object as VIP. */
private boolean vip;
/** Id for debug. */
private final int id;
/** The containing beam group. */
private BeamGroup group;
/** Chords that are linked by this beam. */
private List<Chord> chords = new ArrayList<>();
/** Line equation for the beam. */
private Line line;
//~ Constructors -----------------------------------------------------------
//
//------//
// Beam //
//------//
/** Creates a new instance of Beam.
*
* @param measure the enclosing measure
*/
private Beam (Measure measure)
{
super(measure);
id = 1 + getChildIndex();
logger.debug("{} Created {}", measure.getContextString(), this);
}
//~ Methods ----------------------------------------------------------------
//
//----------//
// populate //
//----------//
/**
* Retrieve (or create a brand new) beam to host the item known by
* its left and right points.
* Remark: We cannot create the BeamItem instance before its hosting
* Beam instance exists.
*
* @param left left point of the candidate
* @param right right point of the candidate
* @param measure the containing measure
* @return the (perhaps new) containing Beam instance
*/
public static Beam populate (Point left,
Point right,
Measure measure)
{
///logger.info("Populating " + glyph);
Beam beam = null;
// Browse existing beams, to check if this item can be appended
for (TreeNode node : measure.getBeams()) {
Beam b = (Beam) node;
if (b.isCompatibleWith(left, right)) {
beam = b;
break;
}
}
// If not, create a brand new beam entity
if (beam == null) {
beam = new Beam(measure);
}
////////////////// beam.addItem(item);
// TODO: preserve order in items !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
logger.debug("{} {}", beam.getContextString(), beam);
return beam;
}
//--------//
// accept //
//--------//
@Override
public boolean accept (ScoreVisitor visitor)
{
return visitor.visit(this);
}
//----------//
// addChord //
//----------//
/**
* Insert a chord linked by this beam.
*
* @param chord the linked chord
*/
public void addChord (Chord chord)
{
if (!chords.contains(chord)) {
chords.add(chord);
}
}
//------------------//
// closeConnections //
//------------------//
/**
* Make sure all connections between this beam and the linked
* chords/stems are actually recorded.
*/
public void closeConnections ()
{
if (chords.isEmpty()) {
addError("No chords connected to " + this);
} else {
Collections.sort(chords, Chord.byAbscissa);
Chord first = chords.get(0);
Chord last = chords.get(chords.size() - 1);
boolean started = false;
// Add interleaved chords if any, plus relevant chords of the group
SortedSet<Chord> adds = Chord.lookupInterleavedChords(first, last);
adds.add(first);
adds.add(last);
for (Chord chord : adds) {
if (chord == first) {
started = true;
}
if (started) {
addChord(chord);
chord.addBeam(this);
}
if (chord == last) {
break;
}
}
}
}
//----------------//
// determineGroup //
//----------------//
/**
* Determine which BeamGroup this beam is part of.
* The BeamGroup is either reused (if one of its beams has a linked chord
* in common with this beam) or created from scratch otherwise
*/
public void determineGroup ()
{
// Check if this beam should belong to an existing group
for (BeamGroup group : getMeasure().getBeamGroups()) {
for (Beam beam : group.getBeams()) {
for (Chord chord : beam.getChords()) {
if (this.chords.contains(chord)) {
// We have a chord in common with this beam, so we are
// part of the same group
switchToGroup(group);
logger.debug("{} Reused {} for {}",
getContextString(), group, this);
return;
}
}
}
}
// No compatible group found, let's build a new one
switchToGroup(new BeamGroup(getMeasure()));
logger.debug("{} Created new {} for {}",
getContextString(), getGroup(), this);
}
//------//
// dump //
//------//
/**
* Utility method for easy dumping of the beam entity.
*/
public void dump ()
{
getLine();
Main.dumping.dump(this);
}
//-----------//
// getChords //
//-----------//
/**
* Report the chords that are linked by this beam.
*
* @return the linked chords
*/
public List<Chord> getChords ()
{
return Collections.unmodifiableList(chords);
}
//----------//
// getGroup //
//----------//
/**
* Report the containing group.
*
* @return the containing group, if already set, or null
*/
public BeamGroup getGroup ()
{
return group;
}
//-------//
// getId //
//-------//
/**
* Report the unique id of the beam within its containing measure.
*
* @return the beam id, starting from 1
*/
public int getId ()
{
return id;
}
//----------//
// getItems //
//----------//
/**
* Report the ordered sequence of items (one or several BeamItem
* instances of BEAM shape, or one glyph of BEAM_HOOK shape) that
* compose this beam.
*
* @return the ordered set of beam items
*/
public List<TreeNode> getItems ()
{
return children;
}
//--------------//
// getFirstItem //
//--------------//
/**
* Report the first of beam items
*
* @return the first item (on left)
*/
public BeamItem getFirstItem ()
{
return (BeamItem) getItems().get(0);
}
//-------------//
// getLastItem //
//-------------//
/**
* Report the last of beam items
*
* @return the last item (on right)
*/
public BeamItem getLastItem ()
{
return (BeamItem) getItems().get(getItems().size() - 1);
}
//---------//
// getLine //
//---------//
/**
* Report the line equation defined by the beam.
*
* @return the line equation
*/
public Line getLine ()
{
if ((line == null) && !getItems().isEmpty()) {
line = new BasicLine();
// Take left side of first item, and right side of last item
Point left = getPoint(LEFT);
line.includePoint(left.x, left.y);
Point right = getPoint(RIGHT);
line.includePoint(right.x, right.y);
}
return line;
}
//----------//
// getPoint //
//----------//
/**
* Report the point that define the desired edge of the beam.
*
* @return the Point coordinates of the point on desired side
*/
public Point getPoint (HorizontalSide side)
{
if (side == LEFT) {
return getFirstItem().getPoint(LEFT);
} else {
return getLastItem().getPoint(RIGHT);
}
}
//----------//
// setPoint //
//----------//
/**
* Assign the point that define the desired edge of the beam.
*
* @param side the desired side
* @param point the Point coordinates of the point on desired side
*/
public void setPoint (HorizontalSide side,
Point point)
{
if (side == LEFT) {
getFirstItem().setPoint(LEFT, point);
} else {
getLastItem().setPoint(RIGHT, point);
}
reset();
}
//--------//
// isHook //
//--------//
public boolean isHook ()
{
return getFirstItem().isHook();
}
//-------//
// isVip //
//-------//
@Override
public boolean isVip ()
{
return vip;
}
//------------//
// linkChords //
//------------//
/**
* Assign the both-way link between this beam and the chords
* connected by the beam.
*/
public void linkChords ()
{
for (TreeNode node : getItems()) {
BeamItem item = (BeamItem) node;
linkChordsOnStems(item);
}
}
//-------------//
// removeChord //
//-------------//
/**
* Remove a chord from this beam.
*
* @param chord the chord to remove
*/
public void removeChord (Chord chord)
{
chords.remove(chord);
}
//--------//
// setVip //
//--------//
@Override
public void setVip ()
{
vip = true;
}
//---------------//
// switchToGroup //
//---------------//
/**
* Move this beam to a BeamGroup, by setting the link both ways
* between this beam and the containing group.
*
* @param group the (new) containing beam group
*/
public void switchToGroup (BeamGroup group)
{
logger.debug("Switching {} from {} to {}",
this, this.group, group);
// Trivial noop case
if (this.group == group) {
return;
}
// Remove from current group if any
if (this.group != null) {
this.group.removeBeam(this);
}
// Assign to new group
if (group != null) {
group.addBeam(this);
}
// Remember assignment
this.group = group;
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder();
sb.append("{Beam");
try {
sb.append("#").append(id);
if (isHook()) {
sb.append(" hook");
}
sb.append(" items[");
for (TreeNode node : getItems()) {
BeamItem item = (BeamItem) node;
sb.append("#").append(item.getGlyph().getId());
}
sb.append("]");
} catch (NullPointerException e) {
sb.append(" INVALID");
}
sb.append("}");
return sb.toString();
}
//---------------//
// computeCenter //
//---------------//
/**
* Compute the center of this beam.
*/
@Override
protected void computeCenter ()
{
Point left = getPoint(LEFT);
Point right = getPoint(RIGHT);
setCenter(
new Point((left.x + right.x) / 2, (left.y + right.y) / 2));
}
//-------//
// reset //
//-------//
/**
* Invalidate cached data, to force its recomputation when needed.
*/
@Override
protected void reset ()
{
super.reset();
line = null;
}
//----------//
// addChild //
//----------//
@Override
public void addChild (TreeNode node)
{
super.addChild(node);
reset();
}
//------------------//
// isCompatibleWith //
//------------------//
/**
* Check compatibility of a candidate item with this Beam instance.
* We use alignment and distance criterias.
*
* @param left left point of item candidate
* @param right right point of item candidate
* @return true if compatible
*/
private boolean isCompatibleWith (Point left,
Point right)
{
boolean logging = isVip() || logger.isDebugEnabled();
// Check alignment, using distance to line
int centerX = (left.x + right.x) / 2;
int centerY = (left.y + right.y) / 2;
double dy = getScale().pixelsToFrac(
getLine().distanceOf(centerX, centerY));
if (logging) {
logger.info("dy={} vs {}", (float) Math.abs(dy),
constants.maxDistance.getValue());
}
if (Math.abs(dy) > constants.maxDistance.getValue()) {
return false;
}
// Check distance along the same alignment
for (HorizontalSide side : HorizontalSide.values()) {
Point itemPoint = (side == LEFT) ? left : right;
Point beamPoint = getPoint((side == LEFT) ? RIGHT : LEFT);
double dx = getScale().pixelsToFrac(itemPoint.distance(beamPoint));
if (logging) {
logger.info("dx={} vs {}", (float) dx,
constants.maxGap.getValue());
}
if (dx <= constants.maxGap.getValue()) {
return true;
}
}
return false;
}
//------------------//
// linkChordsOnStems //
//------------------//
private void linkChordsOnStems (BeamItem item)
{
for (HorizontalSide side : HorizontalSide.values()) {
Glyph stem = item.getStem(side);
if (stem != null) {
List<Chord> sideChords = Chord.getStemChords(
getMeasure(),
stem);
if (!sideChords.isEmpty()) {
for (Chord chord : sideChords) {
addChord(chord);
chord.addBeam(this);
}
} else {
addError("Beam with no chord on " + side + " stem");
}
}
}
}
//-----------//
// getGlyphs //
//-----------//
@Override
public Collection<Glyph> getGlyphs ()
{
List<Glyph> glyphs = new ArrayList<>();
for (TreeNode node : getItems()) {
BeamItem item = (BeamItem) node;
glyphs.add(item.getGlyph());
}
return glyphs;
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction maxDistance = new Scale.Fraction(
0.5,
"Maximum euclidian distance between glyph center and beam line");
Scale.Fraction maxGap = new Scale.Fraction(
0.5,
"Maximum gap along alignment with beam left or right extremum");
}
}