//----------------------------------------------------------------------------//
// //
// M e a s u r e s B u i l d e r //
// //
//----------------------------------------------------------------------------//
// <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.sheet;
import omr.check.FailureResult;
import omr.constant.ConstantSet;
import omr.glyph.facets.Glyph;
import omr.grid.BarAlignment;
import omr.grid.StaffInfo;
import omr.grid.StickIntersection;
import static omr.run.Orientation.*;
import omr.score.entity.Barline;
import omr.score.entity.Measure;
import omr.score.entity.ScoreSystem;
import omr.score.entity.Staff;
import omr.score.entity.SystemPart;
import omr.util.Navigable;
import omr.util.TreeNode;
import net.jcip.annotations.NotThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.List;
/**
* Class {@code MeasuresBuilder} is in charge, at system info level, of
* building measures from the bar sticks found.
* At this moment, the only glyphs in the system collection are the barline
* candidates.
*
* <p>Each instance of this class is meant to be called by a single thread,
* dedicated to the processing of one system. So this class does not need to be
* thread-safe.</p>
*
* @author Hervé Bitteur
*/
@NotThreadSafe
public class MeasuresBuilder
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
MeasuresBuilder.class);
// Failures
private static final FailureResult NOT_WITHIN_SYSTEM = new FailureResult(
"Bar-NotWithinSystem");
private static final FailureResult NOT_STAFF_ALIGNED = new FailureResult(
"Bar-NotStaffAligned");
private static final FailureResult NOT_SYSTEM_ALIGNED = new FailureResult(
"Bar-NotSystemAligned");
//~ Instance fields --------------------------------------------------------
/** The dedicated system */
@Navigable(false)
private final SystemInfo system;
/** Its counterpart within Score hierarchy */
@Navigable(false)
private ScoreSystem scoreSystem;
/** The related sheet */
@Navigable(false)
private final Sheet sheet;
/** Sheet scale */
@Navigable(false)
private final Scale scale;
//~ Constructors -----------------------------------------------------------
//-----------------//
// MeasuresBuilder //
//-----------------//
/**
* Creates a new MeasuresBuilder object.
*
* @param system the dedicated system
*/
public MeasuresBuilder (SystemInfo system)
{
this.system = system;
sheet = system.getSheet();
scale = sheet.getScale();
}
//~ Methods ----------------------------------------------------------------
//---------------//
// buildMeasures //
//---------------//
/**
* Based on barlines found, build, check and cleanup score measures.
*/
public void buildMeasures ()
{
scoreSystem = system.getScoreSystem();
allocateMeasures();
checkMeasures();
}
//------------------//
// allocateMeasures //
//------------------//
/**
* Bar lines are first sorted according to their abscissa, then we
* run additional checks on each bar line, since we now know its
* enclosing system.
* If OK, then we add the corresponding measures in their parts.
*/
private void allocateMeasures ()
{
// Clear the collection of Measure instances
for (TreeNode node : scoreSystem.getParts()) {
SystemPart part = (SystemPart) node;
part.getMeasures()
.clear();
}
// Create measures out of BarAlignment instances
List<BarAlignment> alignments = system.getBarAlignments();
int firstId = system.getFirstStaff()
.getId();
for (BarAlignment align : alignments) {
if (logger.isDebugEnabled()) {
logger.debug(align.toString());
}
StickIntersection[] inters = align.getIntersections();
int ip = 0;
for (TreeNode node : scoreSystem.getParts()) {
SystemPart part = (SystemPart) node;
PartInfo partInfo = system.getParts()
.get(ip++);
Measure measure = new Measure(part);
Barline barline = new Barline(measure);
for (StaffInfo staffInfo : partInfo.getStaves()) {
int is = staffInfo.getId() - firstId;
StickIntersection inter = inters[is];
if (inter != null) {
Glyph stick = inter.getStickAncestor();
barline.addGlyph(stick);
} else {
// TODO
logger.warn(
"No intersection at index {} in {}",
is,
align);
}
}
logger.debug(
"S#{} {}" + " - Created measure " + " with {}",
scoreSystem.getId(),
part,
barline);
}
}
// Degraded case w/ no bar stick at all!
for (TreeNode node : scoreSystem.getParts()) {
SystemPart part = (SystemPart) node;
if (part.getMeasures()
.isEmpty()) {
logger.debug("{} - Creating artificial measure", part);
Measure measure = new Measure(part);
}
}
}
//----------------//
// checkEndingBar //
//----------------//
/**
* Use ending bar line if any, to adjust the right abscissa of the
* system and its staves.
*/
private void checkEndingBar ()
{
try {
SystemPart part = scoreSystem.getFirstRealPart();
Measure measure = part.getLastMeasure();
Barline barline = measure.getBarline();
if (barline == null) {
return;
}
int lastX = barline.getRightX();
int minWidth = scale.toPixels(constants.minMeasureWidth);
if (((scoreSystem.getTopLeft().x + part.getFirstStaff()
.getWidth()) - lastX) < minWidth) {
logger.debug("Adjusting EndingBar {}", system);
// Adjust end of system & staff(s) to this one
scoreSystem.setWidth(lastX - scoreSystem.getTopLeft().x);
for (TreeNode pnode : scoreSystem.getParts()) {
SystemPart prt = (SystemPart) pnode;
for (Iterator<TreeNode> sit = prt.getStaves()
.iterator();
sit.hasNext();) {
Staff stv = (Staff) sit.next();
stv.setWidth(scoreSystem.getDimension().width);
}
}
}
} catch (Exception ex) {
logger.warn(
scoreSystem.getContextString()
+ " Error in checking ending bar",
ex);
}
}
//---------------//
// checkMeasures //
//---------------//
/**
* Check measure reality, using a set of additional tests.
*/
private void checkMeasures ()
{
// Detect very narrow measures which in fact indicate double bar
// lines.
mergeBarlines();
// First barline may be just the beginning of the staff, so do not
// count the very first bar line, which in general defines the
// beginning of the staff rather than the end of a measure, but use
// it to precisely define the left abscissa of the system and all
// its contained staves.
removeStartingMeasure();
// Similarly, use the very last bar line, which generally ends the
// system, to define the right abscissa of the system and its
// staves.
checkEndingBar();
}
//---------------//
// mergeBarlines //
//---------------//
/**
* Check whether two close bar lines are not in fact double lines
* (with variants).
*/
private void mergeBarlines ()
{
int maxDoubleDx = scale.toPixels(constants.maxDoubleBarDx);
for (TreeNode node : scoreSystem.getParts()) {
SystemPart part = (SystemPart) node;
Measure prevMeasure = null;
for (Iterator<TreeNode> mit = part.getMeasures()
.iterator(); mit.hasNext();) {
Measure measure = (Measure) mit.next();
if (prevMeasure != null) {
final int measureWidth = measure.getBarline()
.getCenter().x
- prevMeasure.getBarline()
.getCenter().x;
if (measureWidth <= maxDoubleDx) {
// Lines are side by side or one above the other?
Glyph stick = (Glyph) measure.getBarline()
.getGlyphs()
.toArray()[0];
Glyph prevStick = (Glyph) prevMeasure.getBarline()
.getGlyphs()
.toArray()[0];
if (yOverlap(stick, prevStick)) {
// Overlap => side by side
// Merge the two bar lines into the first one
prevMeasure.getBarline()
.mergeWith(measure.getBarline());
logger.debug(
"Merged two close barlines into {}",
prevMeasure.getBarline());
} else {
// No overlap => one above the other
logger.debug(
"Two barlines segments one above the other in {}",
measure.getBarline());
}
mit.remove();
} else {
prevMeasure = measure;
}
} else {
prevMeasure = measure;
}
}
}
}
//-----------------------//
// removeStartingMeasure //
//-----------------------//
/**
* We associate measures only with their ending bar line(s),
* so the starting bar of a staff does not end a measure,
* we thus have to remove the measure that we first had associated
* with it.
*/
private void removeStartingMeasure ()
{
int minWidth = scale.toPixels(constants.minMeasureWidth);
Barline firstBarline = scoreSystem.getFirstRealPart()
.getFirstMeasure()
.getBarline();
if (firstBarline == null) {
return;
}
int dx = firstBarline.getLeftX() - scoreSystem.getTopLeft().x;
// Check is based on the width of this first measure
if (dx < minWidth) {
// Adjust system parameters if needed : topLeft and dimension
if (dx != 0) {
logger.debug("Adjusting firstX={} {}", dx, system);
scoreSystem.getTopLeft()
.translate(dx, 0);
scoreSystem.getDimension().width -= dx;
}
// Adjust beginning of all staves to this one
// Remove this false "measure" in all parts of the system
for (TreeNode node : scoreSystem.getParts()) {
SystemPart part = (SystemPart) node;
if (!part.isDummy()) {
// Set the bar as starting bar for the staff
Measure measure = part.getFirstMeasure();
part.setStartingBarline(measure.getBarline());
// Remove this first measure
List<TreeNode> measures = part.getMeasures();
measures.remove(0);
// Update abscissa of top-left corner of every staff
for (TreeNode sNode : part.getStaves()) {
Staff staff = (Staff) sNode;
staff.getTopLeft()
.translate(dx, 0);
}
// Update other bar lines abscissae accordingly
for (TreeNode mNode : part.getMeasures()) {
Measure meas = (Measure) mNode;
meas.resetAbscissae();
}
}
}
}
}
//----------//
// yOverlap //
//----------//
private boolean yOverlap (Glyph one,
Glyph two)
{
double start = Math.max(
one.getStartPoint(VERTICAL).getY(),
two.getStartPoint(VERTICAL).getY());
double stop = Math.min(
one.getStopPoint(VERTICAL).getY(),
two.getStopPoint(VERTICAL).getY());
return start < stop;
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction maxAlignShiftDx = new Scale.Fraction(
0.5,
"Maximum horizontal shift in bars between staves in a system");
//
Scale.Fraction maxDoubleBarDx = new Scale.Fraction(
2.0,
"Maximum horizontal distance between the two bars of a double bar");
//
Scale.Fraction minMeasureWidth = new Scale.Fraction(
2.0,
"Minimum width for a measure");
//
Scale.Fraction maxBarOffset = new Scale.Fraction(
1.0,
"Vertical offset used to detect that a bar extends past a staff");
}
}