//----------------------------------------------------------------------------// // // // T i m e S i g n a t 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.score.entity.Measure; import omr.score.entity.Page; import omr.score.entity.ScoreSystem; import omr.score.entity.Staff; import omr.score.entity.SystemPart; import omr.score.entity.TimeRational; import omr.score.entity.TimeSignature; import omr.score.entity.Voice; import omr.score.visitor.AbstractScoreVisitor; import omr.util.TreeNode; import omr.util.WrappedBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; /** * Class {@code TimeSignatureFixer} can visit the score hierarchy to * check whether each of the time signatures is consistent with most of * measures intrinsic time signature. * * @author Hervé Bitteur */ public class TimeSignatureFixer extends AbstractScoreVisitor { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger( TimeSignatureFixer.class); /** Used to sort integers by decreasing value */ protected static final Comparator<Integer> reverseIntComparator = new Comparator<Integer>() { @Override public int compare (Integer e1, Integer e2) { return e2 - e1; } }; //~ Constructors ----------------------------------------------------------- //--------------------// // TimeSignatureFixer // //--------------------// /** * Creates a new TimeSignatureFixer object. */ public TimeSignatureFixer () { } //~ Methods ---------------------------------------------------------------- //------------// // visit Page // //------------// /** * Page hierarchy entry point * * @param page the page to export * @return false */ @Override public boolean visit (Page page) { try { // We cannot rely on standard browsing part by part, since we need to // address all vertical measures (of same Id), regardless of their // containing part ScoreSystem system = page.getFirstSystem(); SystemPart part = system.getFirstPart(); Measure measure = part.getFirstMeasure(); // Measure that starts a range of measures with an explicit time sig Measure startMeasure = null; // Is this starting time sig a manual one? boolean startManual = false; // Measure that ends the range // Right before another time sig, or last measure of the score Measure stopMeasure = null; // Remember if current signature is manual // And thus should not be updated WrappedBoolean isManual = new WrappedBoolean(false); while (measure != null) { if (hasTimeSig(measure, isManual)) { if ((startMeasure != null) && !startManual) { // Complete the ongoing time sig analysis checkTimeSigs(startMeasure, stopMeasure); } // Start a new analysis startMeasure = measure; startManual = isManual.isSet(); } stopMeasure = measure; measure = measure.getFollowing(); } if (startMeasure != null) { if (!startManual) { // Complete the ongoing time sig analysis checkTimeSigs(startMeasure, stopMeasure); } } else { // Whole page without explicit time signature checkTimeSigs(part.getFirstMeasure(), page.getLastSystem().getFirstPart().getLastMeasure()); } } catch (Exception ex) { logger.warn("TimeSignatureFixer. Error visiting " + page, ex); } // Don't go the standard way (part per part) return false; } //---------------// // visit Measure // //---------------// @Override public boolean visit (Measure measure) { measure.setExpectedDuration(null); return false; } //---------------// // checkTimeSigs // //---------------// /** * Perform the analysis on the provided range of measures, * retrieving the most significant intrinsic time sig as determined * by measures chords. * Based on this "intrinsic" time information, modify the explicit time * signatures accordingly. * * @param startMeasure beginning of the measure range * @param stopMeasure end of the measure range */ private void checkTimeSigs (Measure startMeasure, Measure stopMeasure) { logger.debug("checkTimeSigs on measure range {}..{}", startMeasure.getPageId(), stopMeasure.getPageId()); // Retrieve the best possible time signature(s) SortedMap<Integer, TimeRational> bestSigs = retrieveBestSigs( startMeasure, stopMeasure); logger.debug("{}Best inferred time sigs in [M#{},M#{}]: {}", startMeasure.getPage().getSheet().getLogPrefix(), startMeasure.getIdValue(), stopMeasure.getIdValue(), bestSigs); if (!bestSigs.isEmpty()) { TimeRational bestRational = bestSigs.get(bestSigs.firstKey()); if (!TimeSignature.isAcceptable(bestRational)) { logger.debug("Time sig too uncommon: {}", bestRational); return; } logger.debug("Best sig: {}", bestRational); // Loop on every staff in the vertical startMeasure for (Staff.SystemIterator sit = new Staff.SystemIterator( startMeasure); sit.hasNext();) { Staff staff = sit.next(); Measure measure = sit.getMeasure(); TimeSignature sig = measure.getTimeSignature(staff); if (sig != null) { try { TimeRational timeRational = sig.getTimeRational(); if (timeRational == null || !timeRational.equals(bestRational)) { logger.info("{}Measure#{} {}T{} {}->{}", measure.getPage().getSheet().getLogPrefix(), measure.getPageId(), staff.getContextString(), staff.getId(), timeRational, bestRational); sig.modify(null, bestRational); } } catch (Exception ex) { sig.addError(sig.getGlyphs().iterator().next(), "Could not check time signature " + ex); } } } } else { logger.debug("No best sig!"); } } //------------// // 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, WrappedBoolean isManual) { isManual.set(false); boolean found = false; for (Staff.SystemIterator sit = new Staff.SystemIterator(measure); sit.hasNext();) { Staff staff = sit.next(); TimeSignature sig = sit.getMeasure().getTimeSignature(staff); if (sig != null) { logger.debug("Measure#{} {}T{} {}", measure.getPageId(), staff.getContextString(), staff.getId(), sig); if (sig.isManual()) { isManual.set(true); } found = true; } } return found; } //------------------// // retrieveBestSigs // //------------------// /** * By inspecting each voice in the provided measure range, * determine the best intrinsic time signatures. * * @param startMeasure beginning of the measure range * @param stopMeasure end of the measure range * @return a map, sorted by decreasing count, of possible time signatures */ private SortedMap<Integer, TimeRational> retrieveBestSigs ( Measure startMeasure, Measure stopMeasure) { // Retrieve the significant measure informations Map<TimeRational, Integer> sigs = new LinkedHashMap<>(); Measure m = startMeasure; int mIndex = m.getParent().getChildren().indexOf(m); // Loop on measure range while (true) { // Retrieve info logger.debug("Checking measure#{} idx:{}", m.getPageId(), m.getChildIndex()); ScoreSystem system = m.getSystem(); for (TreeNode pNode : system.getParts()) { SystemPart part = (SystemPart) pNode; Measure measure = (Measure) part.getMeasures().get(mIndex); if (logger.isDebugEnabled()) { measure.printVoices(null); } for (Voice voice : measure.getVoices()) { TimeRational timeRational = voice.getInferredTimeSignature(); logger.debug("Voice#{} time inferred: {}", voice.getId(), timeRational); if (timeRational != null) { // Update histogram Integer sum = sigs.get(timeRational); if (sum == null) { sum = 1; } else { sum += 1; } sigs.put(timeRational, sum); } } } // Are we through? if (m == stopMeasure) { break; } else { // Move to next measure m = m.getFollowing(); mIndex = m.getParent().getChildren().indexOf(m); } } // Sort info by decreasing counts SortedMap<Integer, TimeRational> bestSigs = new TreeMap<>( reverseIntComparator); for (Map.Entry<TimeRational, Integer> entry : sigs.entrySet()) { bestSigs.put(entry.getValue(), entry.getKey()); } return bestSigs; } }