//----------------------------------------------------------------------------//
// //
// M e a s u r e F i x 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.score;
import omr.glyph.Shape;
import omr.math.Rational;
import omr.score.entity.Barline;
import omr.score.entity.Measure;
import omr.score.entity.Page;
import omr.score.entity.ScoreSystem;
import omr.score.entity.SystemPart;
import omr.score.entity.TimeSignature.InvalidTimeSignature;
import omr.score.entity.Voice;
import omr.score.visitor.AbstractScoreVisitor;
import omr.util.TreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Class {@code MeasureFixer} visits the score hierarchy to fix measures:
* <ul>
* <li>Detect implicit measures (as pickup measures)</li>
* <li>Detect first half repeat measures</li>
* <li>Detect implicit measures (as second half repeats)</li>
* <li>Detect inside barlines (empty measures) </li>
* <li>Assign final page-based Measure ids</li>
* </ul>
*
* @author Hervé Bitteur
*/
public class MeasureFixer
extends AbstractScoreVisitor
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(MeasureFixer.class);
//~ Instance fields --------------------------------------------------------
private int im; // Current measure index in system
private List<Measure> verticals = null; // Current vertical measures
private Rational measureTermination = null; // Current termination
private ScoreSystem system; // Current system
// Information to remember from previous vertical measure
private List<Measure> prevVerticals = null; // Previous vertical measures
private Rational prevMeasureTermination = null; // Previous termination
/** The latest id assigned to a measure (in the previous system) */
private Integer prevSystemLastId = null;
/** The latest id assigned to a measure (in the current system) */
private Integer lastId = null;
//~ Constructors -----------------------------------------------------------
//--------------//
// MeasureFixer //
//--------------//
/**
* Creates a new MeasureFixer object.
*/
public MeasureFixer ()
{
}
//~ Methods ----------------------------------------------------------------
//------------//
// visit Page //
//------------//
@Override
public boolean visit (Page page)
{
logger.debug("{} Visiting {}", getClass().getSimpleName(), page);
page.acceptChildren(this);
// Remember the number of measures in this page
page.computeMeasureCount();
// Remember the delta of measure ids in this page
page.setDeltaMeasureId(
page.getLastSystem().getLastPart().getLastMeasure().getIdValue());
return false;
}
//-------------//
// visit Score //
//-------------//
@Override
public boolean visit (Score score)
{
logger.debug("{} Visiting {}", getClass().getSimpleName(), score);
score.acceptChildren(this);
return false;
}
//--------------//
// visit System //
//--------------//
/**
* Here, we work sequentially on "vertical" measures in this system.
* Such a vertical measure is the collection of measures, across all parts
* of the system, one below the other. They share the same id and the
* same status (such as implicit).
*
* @return false
*/
@Override
public boolean visit (ScoreSystem system)
{
logger.debug("{} Visiting {}", getClass().getSimpleName(), system);
this.system = system;
// First, compute voices terminations
system.acceptChildren(this);
// Measure indices to remove
List<Integer> toRemove = new ArrayList<>();
// Use a loop on "vertical" measures, across all system parts
final int imMax = system.getFirstRealPart().getMeasures().size() - 1;
for (im = 0; im <= imMax; im++) {
logger.debug("im:{}", im);
verticals = verticalsOf(system, im);
// Check if all voices in all parts exhibit the same termination
measureTermination = getMeasureTermination();
logger.debug("measureFinal:{}{}",
measureTermination,
(measureTermination != null)
? ("=" + measureTermination)
: "");
if (isEmpty()) {
logger.debug("empty");
// All this vertical measure is empty (no notes/rests)
// We will merge with the following measure, if any
if (im < imMax) {
setId((lastId != null) ? (lastId + 1)
: ((prevSystemLastId != null)
? (prevSystemLastId + 1) : 1),
false);
}
} else if (isPickup()) {
logger.debug("pickup");
setImplicit();
setId((lastId != null) ? (-lastId)
: ((prevSystemLastId != null)
? (-prevSystemLastId) : 0),
false);
} else if (isSecondRepeatHalf()) {
logger.debug("secondHalf");
// Shorten actual duration for (non-implicit) previous measure
shortenFirstHalf();
setImplicit();
setId((lastId != null) ? lastId : prevSystemLastId, true);
} else if (isRealStart()) {
logger.debug("realStart");
merge(); // Merge with previous vertical measure
toRemove.add(im);
} else {
logger.debug("normal");
// Normal measure
setId((lastId != null) ? (lastId + 1)
: ((prevSystemLastId != null)
? (prevSystemLastId + 1) : 1),
false);
}
// For next measure
prevVerticals = verticals;
prevMeasureTermination = measureTermination;
}
removeMeasures(toRemove, system); // Remove measures if any
// For next system
prevSystemLastId = lastId;
lastId = null;
return false; // Dead end
}
//---------------//
// visit Measure //
//---------------//
@Override
public boolean visit (Measure measure)
{
// Check duration sanity in this measure
// Record forward items in voices when needed
measure.checkDuration();
return false;
}
//-----------------------//
// getMeasureTermination //
//-----------------------//
private Rational getMeasureTermination ()
{
Rational termination = null;
for (Measure measure : verticals) {
if (measure.isDummy()) {
continue;
}
for (Voice voice : measure.getVoices()) {
Rational voiceTermination = voice.getTermination();
if (voiceTermination != null) {
if (termination == null) {
termination = voiceTermination;
} else if (!voiceTermination.equals(termination)) {
logger.debug("Non-consistent voices terminations");
return null;
}
}
}
}
return termination;
}
//---------//
// isEmpty //
//---------//
/**
* Check for an empty measure: perhaps clef and key sig, but no note
* or rest
*
* @return true if so
*/
private boolean isEmpty ()
{
return verticals.get(0).getActualDuration().equals(Rational.ZERO);
}
//----------//
// isPickup //
//----------//
/**
* Check for an implicit pickup measure at the beginning of a system
*
* @return true if so
*/
private boolean isPickup ()
{
return (system.getChildIndex() == 0) && (im == 0)
&& (measureTermination != null)
&& (measureTermination.compareTo(Rational.ZERO) < 0);
}
//-------------//
// isRealStart //
//-------------//
/**
* Check for a measure in second position, while following an empty
* measure
*
* @return true if so
*/
private boolean isRealStart ()
{
return (im == 1)
&& (prevVerticals.get(0).getActualDuration().equals(
Rational.ZERO));
///&& (measureTermination != null); // Too strict!
}
//--------------------//
// isSecondRepeatHalf //
//--------------------//
/**
* Check for an implicit measure as the second half of a repeat
* sequence
*
* @return true if so
*/
private boolean isSecondRepeatHalf ()
{
// Check for partial first half
if ((prevMeasureTermination == null)
|| (prevMeasureTermination.compareTo(Rational.ZERO) >= 0)) {
return false;
}
// Check for partial second half
if ((measureTermination == null)
|| (measureTermination.compareTo(Rational.ZERO) >= 0)) {
return false;
}
// Check for a suitable repeat barline in between
Measure prevMeasure = prevVerticals.get(0);
Barline barline = prevMeasure.getBarline();
if (barline == null) {
return false;
}
Shape shape = barline.getShape();
if ((shape != Shape.RIGHT_REPEAT_SIGN)
&& (shape != Shape.BACK_TO_BACK_REPEAT_SIGN)) {
return false;
}
// Check for an exact duration sum (TODO: is this too strict?)
try {
return prevMeasureTermination.plus(measureTermination).abs().equals(
prevMeasure.getExpectedDuration());
} catch (InvalidTimeSignature its) {
return false;
}
}
//-------------------//
// mergeWithPrevious //
//-------------------//
/**
* We have a real start, following an empty measure.
* We need to merge the vertical measures, right into left
*/
private void merge ()
{
for (int iLine = 0; iLine < verticals.size(); iLine++) {
Measure left = prevVerticals.get(iLine);
Measure right = verticals.get(iLine);
left.mergeWithRight(right);
}
}
//----------------//
// removeMeasures //
//----------------//
/**
* Remove the vertical measures that correspond to the provided
* indices
*
* @param toRemove sequence of indices to remove, perhaps empty
* @param system the containing system
*/
private void removeMeasures (List<Integer> toRemove,
ScoreSystem system)
{
if (toRemove.isEmpty()) {
return;
}
for (TreeNode pn : system.getParts()) {
SystemPart part = (SystemPart) pn;
int index = -1;
for (Iterator<TreeNode> it = part.getMeasures().iterator(); it.
hasNext();) {
index++;
it.next();
if (toRemove.contains(index)) {
it.remove();
}
}
}
}
//-------//
// setId //
//-------//
private void setId (int id,
boolean isSecondHalf)
{
logger.debug("-> id={}{}", id, isSecondHalf ? " SH" : "");
for (Measure measure : verticals) {
measure.setPageId(id, isSecondHalf);
}
// Side effect: remember the numeric value as last id
lastId = id;
}
//-------------//
// setImplicit //
//-------------//
private void setImplicit ()
{
for (Measure measure : verticals) {
measure.setImplicit();
}
}
//------------------//
// shortenFirstHalf //
//------------------//
private void shortenFirstHalf ()
{
for (Measure measure : prevVerticals) {
measure.shorten(prevMeasureTermination);
measure.setFirstHalf(true);
}
}
//-------------//
// verticalsOf //
//-------------//
/**
* Report the sequence of vertical measures for a given index
*
* @param index the index in the parent part
* @return the vertical collection of measure with the same index
*/
private List<Measure> verticalsOf (ScoreSystem system,
int index)
{
List<Measure> measures = new ArrayList<>();
for (TreeNode node : system.getParts()) {
SystemPart part = (SystemPart) node;
measures.add((Measure) part.getMeasures().get(index));
}
return measures;
}
}