//----------------------------------------------------------------------------//
// //
// S y s t e m 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.Main;
import omr.constant.ConstantSet;
import omr.glyph.Glyphs;
import omr.glyph.GlyphsModel;
import omr.glyph.facets.Glyph;
import omr.grid.LineInfo;
import omr.grid.StaffInfo;
import omr.math.BasicLine;
import omr.math.Line;
import omr.step.StepException;
import omr.step.Steps;
import omr.util.BrokenLine;
import omr.util.HorizontalSide;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
/**
* Class {@code SystemsBuilder} is in charge of retrieving the systems
* (SystemInfo instances) and parts (PartInfo instances) in the
* provided sheet and to allocate the corresponding instances on the
* Score side (the Score instance, and the various instances of
* ScoreSystem, SystemPart and Staff).
*
* <p>Is does so automatically by using barlines glyphs that embrace staves,
* parts and systems. It also allows the user to interactively modify the
* retrieved information.</p>
*
* <p>Systems define their own area, which may be more complex than a simple
* ordinate range, in order to precisely define which glyph belongs to which
* system. The user has the ability to interactively modify the broken line
* that defines the limit between two adjacent systems.</p>
*
* <p>This class has close relationships with {@link MeasuresBuilder} in charge
* of building and checking the measures, because barlines are used both to
* define systems and parts, and to define measures.</p>
*
* <p>From the related view, the user has the ability to assign or to deassign
* a barline glyph, with subsequent impact on the related measures.</p>
*
* <p>TODO: Implement a way for the user to tell whether a bar glyph is or not
* a BAR_PART_DEFINING (i.e. if it is anchored on top and bottom).</p>
*
* @author Hervé Bitteur
*/
public class SystemsBuilder
extends GlyphsModel
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(SystemsBuilder.class);
//~ Instance fields --------------------------------------------------------
//
/** Sheet retrieved systems */
private final List<SystemInfo> systems;
//~ Constructors -----------------------------------------------------------
//----------------//
// SystemsBuilder //
//----------------//
/**
* Creates a new SystemsBuilder object.
*
* @param sheet the related sheet
*/
public SystemsBuilder (Sheet sheet)
{
super(sheet, sheet.getNest(), Steps.valueOf(Steps.SYSTEMS));
systems = sheet.getSystems();
}
//~ Methods ----------------------------------------------------------------
//--------------//
// buildSystems //
//--------------//
/**
* Process the sheet information produced by the GRID step and allocate the
* related score information
*/
public void buildSystems ()
throws StepException
{
// Systems have been created by GRID step on sheet side
// Build parts on sheet side
buildParts();
// Create score counterparts
// Build systems, parts & measures on score side
allocateScoreStructure();
// Report number of systems retrieved
reportResults();
// Define precisely the systems boundaries
computeBoundaries();
// Split sections & glyphs per system
splitSystemEntities();
}
//---------------------//
// splitSystemEntities //
//---------------------//
/**
* Split horizontals, vertical sections, glyphs per system
*/
public void splitSystemEntities ()
{
// Split everything, including horizontals, per system
///sheet.splitHorizontals();
sheet.splitHorizontalSections();
sheet.splitVerticalSections();
sheet.splitGlyphs();
}
//------------------------//
// allocateScoreStructure //
//------------------------//
/**
* For each SystemInfo, build the corresponding System entity with all its
* depending Parts and Staves
*/
private void allocateScoreStructure ()
throws StepException
{
// Clear Score -> Systems
sheet.getPage().resetSystems();
for (SystemInfo system : systems) {
system.allocateScoreStructure(); // ScoreSystem, Parts & Staves
}
}
//------------//
// buildParts //
//------------//
/**
* Knowing the starting staff indice of each part, allocate the related
* PartInfo instances in proper SystemInfo instances
*/
private void buildParts ()
{
final Integer[] partTops = sheet.getStaffManager().getPartTops();
for (SystemInfo system : sheet.getSystems()) {
system.getParts().clear(); // Start from scratch
int partTop = -1;
PartInfo part = null;
for (StaffInfo staff : system.getStaves()) {
int topId = partTops[staff.getId() - 1];
if (topId != partTop) {
part = new PartInfo();
system.addPart(part);
partTop = topId;
}
part.addStaff(staff);
}
}
// TODO // Specific degraded case, just one staff, no bar stick
// if (systems.isEmpty() && (staffNb == 1)) {
// StaffInfo singleStaff = staffManager.getFirstStaff();
// system = new SystemInfo(++id, sheet);
// systems.add(system);
// system.addStaff(singleStaff);
// part = new PartInfo();
// system.addPart(part);
// part.addStaff(singleStaff);
// logger.warn("Created one system, one part, one staff");
// }
if (logger.isDebugEnabled()) {
for (SystemInfo systemInfo : systems) {
Main.dumping.dump(systemInfo);
int i = 0;
for (PartInfo partInfo : systemInfo.getParts()) {
Main.dumping.dump(partInfo, "Part #" + ++i, 1);
}
}
}
}
//-------------------//
// computeBoundaries //
//-------------------//
/**
* Compute the boundary of the related area of each system.
*/
private void computeBoundaries ()
{
// Very first system top border
SystemInfo prevSystem = null;
BrokenLine prevBorder = new BrokenLine(
new Point(0, 0),
new Point(sheet.getWidth(), 0));
BrokenLine border; // Top border of current system
for (SystemInfo system : sheet.getSystems()) {
if (prevSystem != null) {
// Try the simplistic approach, defining top border as the
// middle between last line of last staff of previous system
// and first line of first staff of current system
Line line = new BasicLine();
LineInfo topLine = prevSystem.getLastStaff().getLastLine();
LineInfo botLine = system.getFirstStaff().getFirstLine();
for (HorizontalSide side : HorizontalSide.values()) {
Point2D top = topLine.getEndPoint(side);
Point2D bot = botLine.getEndPoint(side);
line.includePoint(
(top.getX() + bot.getX()) / 2,
(top.getY() + bot.getY()) / 2);
}
border = new BrokenLine(
new Point(0, line.yAtX(0)),
new Point(sheet.getWidth(), line.yAtX(sheet.getWidth())));
// Check if the border is acceptable and replace it if needed
BrokenLine newBorder = refineBorder(border, prevSystem, system);
if (newBorder != null) {
border = newBorder;
} else {
// Use basic border as a temporary virtual border only
}
prevSystem.setBoundary(
new SystemBoundary(prevSystem, prevBorder, border));
prevBorder = border;
}
prevSystem = system;
}
// Very last system
if (prevSystem != null) {
border = new BrokenLine(
new Point(0, sheet.getHeight()),
new Point(sheet.getWidth(), sheet.getHeight()));
prevSystem.setBoundary(
new SystemBoundary(prevSystem, prevBorder, border));
}
sheet.setSystemBoundaries();
}
//--------------//
// refineBorder //
//--------------//
private BrokenLine refineBorder (BrokenLine border,
SystemInfo prevSystem,
SystemInfo system)
{
// Define the inter-system yellow zone
int yellowDy = sheet.getScale().toPixels(constants.yellowZoneHalfHeight);
Polygon polygon = new Polygon();
Point left = border.getPoint(0);
Point right = border.getPoint(1);
polygon.addPoint(left.x, left.y - yellowDy);
polygon.addPoint(right.x, right.y - yellowDy);
polygon.addPoint(right.x, right.y + yellowDy);
polygon.addPoint(left.x, left.y + yellowDy);
// Look for glyphs intersected by this yellow zone
List<Glyph> intersected = new ArrayList<>();
for (Glyph glyph : nest.getActiveGlyphs()) {
if (polygon.intersects(glyph.getBounds())) {
intersected.add(glyph);
}
}
logger.debug("S#{}-{} : {}{}", prevSystem.getId(),
system.getId(), polygon.getBounds(),
Glyphs.toString(" inter:", intersected));
// If the yellow zone is empty, keep the border
// Otherwise, use the more complex approach
if (intersected.isEmpty()) {
return border;
} else {
return new BorderBuilder(sheet, prevSystem, system).buildBorder();
}
}
//---------------//
// reportResults //
//---------------//
private void reportResults ()
{
StringBuilder sb = new StringBuilder();
int partNb = 0;
for (SystemInfo system : sheet.getSystems()) {
partNb = Math.max(partNb, system.getParts().size());
}
int sysNb = systems.size();
if (partNb > 0) {
sb.append(partNb).append(" part");
if (partNb > 1) {
sb.append("s");
}
} else {
sb.append("no part found");
}
sheet.getBench().recordPartCount(partNb);
if (sysNb > 0) {
sb.append(", ").append(sysNb).append(" system");
if (sysNb > 1) {
sb.append("s");
}
} else {
sb.append(", no system found");
}
sheet.getBench().recordSystemCount(sysNb);
logger.info("{}{}", sheet.getLogPrefix(), sb.toString());
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction yellowZoneHalfHeight = new Scale.Fraction(
0.1,
"Half height of inter-system yellow zone");
}
}