//----------------------------------------------------------------------------// // // // T i m e S i g n a t u r e R e t r i e v 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.constant.ConstantSet; import omr.glyph.CompoundBuilder; import omr.glyph.Evaluation; import omr.glyph.Glyphs; import omr.glyph.Grades; import omr.glyph.Shape; import omr.glyph.ShapeSet; import omr.glyph.facets.Glyph; import omr.grid.StaffInfo; import omr.math.GeoUtil; import omr.score.entity.Barline; import omr.score.entity.Chord; import omr.score.entity.Clef; import omr.score.entity.KeySignature; import omr.score.entity.Measure; import omr.score.entity.Note; import omr.score.entity.Page; import omr.score.entity.ScoreSystem; import omr.score.entity.Staff; import omr.score.entity.SystemPart; import omr.score.entity.TimeSignature; import omr.score.visitor.AbstractScoreVisitor; import omr.sheet.Scale; import omr.sheet.SystemInfo; import omr.util.TreeNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Point; import java.awt.Rectangle; import java.util.EnumSet; /** * Class {@code TimeSignatureRetriever} checks carefully the first * measure of each staff for a time signature. * * @author Hervé Bitteur */ public class TimeSignatureRetriever extends AbstractScoreVisitor { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger( TimeSignatureRetriever.class); //~ Instance fields -------------------------------------------------------- // Scale-dependent constants private int timeSigWidth; private int yOffset; //~ Constructors ----------------------------------------------------------- //------------------------// // TimeSignatureRetriever // //------------------------// /** * Creates a new TimeSignatureRetriever object. */ public TimeSignatureRetriever () { } //~ Methods ---------------------------------------------------------------- //------------// // visit Page // //------------// /** * Page hierarchy (sole) entry point. * * @param page the page to check * @return false */ @Override public boolean visit (Page page) { Scale scale = page.getScale(); timeSigWidth = scale.toPixels(constants.timeSigWidth); yOffset = scale.toPixels(constants.yOffset); try { // We simply consider the very first measure of every staff ScoreSystem system = page.getFirstSystem(); Measure firstMeasure = system.getFirstRealPart() .getFirstMeasure(); // If we have some TS, then it's OK if (hasTimeSig(firstMeasure)) { return false; } // No TS found. Let's look where it should be, if there was one Rectangle roi = getRoi(firstMeasure); if (roi.width < timeSigWidth) { logger.debug("No room for time sig: {}", roi.width); return false; } for (Staff.SystemIterator sit = new Staff.SystemIterator( firstMeasure); sit.hasNext();) { Staff staff = sit.next(); if (staff.isDummy()) { continue; } int center = roi.x + (roi.width / 2); Glyph compound = system.getInfo() .buildCompound( null, false, system.getInfo().getGlyphs(), new TimeSigAdapter( system.getInfo(), Grades.timeMinGrade, ShapeSet.FullTimes, staff, center)); if (compound != null) { // Insert time sig in proper measure TimeSignature.populateFullTime( compound, firstMeasure, staff); } } } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + page, ex); } return false; // No navigation } //--------// // getRoi // //--------// /** * Retrieve the free space where a time signature could be within * the first measure width. * * @param firstMeasure the containing measure (whatever the part) * @return a degenerated rectangle, just to provide left and right bounds */ private Rectangle getRoi (Measure firstMeasure) { ScoreSystem system = firstMeasure.getSystem(); int left = 0; // Min int right = system.getTopLeft().x + system.getDimension().width; // Max for (Staff.SystemIterator sit = new Staff.SystemIterator(firstMeasure); sit.hasNext();) { Staff staff = sit.next(); if (staff.isDummy()) { continue; } int staffId = staff.getId(); Measure measure = sit.getMeasure(); // Before: clef? + key signature? KeySignature keySig = measure.getFirstMeasureKey(staffId); if (keySig != null) { Rectangle kBox = keySig.getBox(); left = Math.max(left, kBox.x + kBox.width); } else { Clef clef = measure.getFirstMeasureClef(staffId); if (clef != null) { Rectangle cBox = clef.getBox(); left = Math.max(left, cBox.x + cBox.width); } } // After: alteration? + chord? Chord chord = measure.getClosestChord(new Point(0, 0)); if (chord != null) { for (TreeNode tn : chord.getNotes()) { Note note = (Note) tn; Glyph accid = note.getAccidental(); if (accid != null) { right = Math.min(right, accid.getBounds().x); } else { right = Math.min(right, note.getBox().x); } } } // Limit right to the abscissa of the measure ending barline if (measure.getRightX() != null) { right = Math.min(right, measure.getRightX()); } logger.debug("Staff:{} left:{} right:{}", staffId, left, right); } return new Rectangle(left, 0, right - left, 0); } //------------// // hasTimeSig // //------------// /** * Check whether the provided measure contains at least one explicit time * signature * * @param measure the provided measure (in fact we care only about the * measure id, regardless of the part) * @return true if a time sig exists in some staff of the measure */ private boolean hasTimeSig (Measure measure) { for (Staff.SystemIterator sit = new Staff.SystemIterator(measure); sit.hasNext();) { Staff staff = sit.next(); if (sit.getMeasure() .getTimeSignature(staff) != null) { return true; } } return false; } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- Scale.Fraction timeSigWidth = new Scale.Fraction( 2d, "Width of a time signature"); Scale.Fraction yOffset = new Scale.Fraction( 0.5d, "Time signature vertical offset since staff line"); } //----------------// // TimeSigAdapter // //----------------// /** * Compound adapter to search for a time sig shape */ private class TimeSigAdapter extends CompoundBuilder.TopShapeAdapter { //~ Instance fields ---------------------------------------------------- final Staff staff; final int center; //~ Constructors ------------------------------------------------------- public TimeSigAdapter (SystemInfo system, double minGrade, EnumSet<Shape> desiredShapes, Staff staff, int center) { super(system, minGrade, desiredShapes); this.staff = staff; this.center = center; } //~ Methods ------------------------------------------------------------ @Override public Rectangle computeReferenceBox () { StaffInfo staffInfo = staff.getInfo(); Rectangle newBox = new Rectangle( center, staffInfo.getFirstLine().yAt(center) + (staffInfo.getHeight() / 2), 0, 0); newBox.grow( (timeSigWidth / 2), (staffInfo.getHeight() / 2) - yOffset); // Draw the box, for visual debug SystemPart part = system.getScoreSystem() .getPartAt(GeoUtil.centerOf(newBox)); Barline barline = part.getStartingBarline(); Glyph line = null; if (barline != null) { line = Glyphs.firstOf( barline.getGlyphs(), Barline.linePredicate); if (line != null) { line.addAttachment("ti" + staff.getId(), newBox); } } return newBox; } @Override public Evaluation getChosenEvaluation () { return new Evaluation(chosenEvaluation.shape, Evaluation.ALGORITHM); } @Override public boolean isCandidateSuitable (Glyph glyph) { return !glyph.isManualShape(); } } }