//----------------------------------------------------------------------------// // // // S y s t e m P a r t // // // //----------------------------------------------------------------------------// // <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.entity; import omr.glyph.facets.Glyph; import omr.grid.StaffInfo; import omr.score.visitor.ScoreVisitor; import omr.sheet.PartInfo; import omr.util.Predicate; import omr.util.TreeNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Class {@code SystemPart} handles each of the various parts found in * one system, since the layout of parts may vary from system to system. * * @author Hervé Bitteur */ public class SystemPart extends PartNode { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(SystemPart.class); /** For comparing (TreeNode) SystemPart instances according to their id */ public static final Comparator<TreeNode> idComparator = new Comparator<TreeNode>() { @Override public int compare (TreeNode o1, TreeNode o2) { if (o1 instanceof SystemPart && o2 instanceof SystemPart) { SystemPart p1 = (SystemPart) o1; SystemPart p2 = (SystemPart) o2; return Integer.signum(p1.getId() - p2.getId()); } else { throw new RuntimeException( "Comparing illegal SystemPart instances"); } } }; //~ Instance fields -------------------------------------------------------- /** Id of this part within the system, starting at 1 */ private int id; /** Name, if any, that faces this system part */ private String name; /** The related information */ private final PartInfo info; /** The corresponding ScorePart */ private ScorePart scorePart; /** A brace (or bracket) attached if any */ private Glyph brace; /** Specific child : sequence of staves that belong to this system part */ private final Container staves; /** Specific child : sequence of measures that compose this system part */ private final Container measures; /** Specific child : list of slurs */ private final Container slurs; /** Specific child : list of lyrics lines */ private final Container lyrics; /** Specific child : list of text items */ private final Container texts; /** Lonesome child : Starting barline (the others are linked to measures) */ private Barline startingBarline; /** Flag to indicate this system part is just a placeholder */ private boolean dummy; //~ Constructors ----------------------------------------------------------- //------------// // SystemPart // //------------// /** * Create a new instance of SystemPart. * * @param system the containing system * @param info the counterpart in sheet */ public SystemPart (ScoreSystem system, PartInfo info) { super(system); this.info = info; // Allocate specific children staves = new Container(this, "Staves"); measures = new Container(this, "Measures"); slurs = new Container(this, "Slurs"); lyrics = new Container(this, "Lyrics"); texts = new Container(this, "Texts"); } //~ Methods ---------------------------------------------------------------- //--------// // accept // //--------// @Override public boolean accept (ScoreVisitor visitor) { return visitor.visit(this); } //----------// // addChild // //----------// /** * Overrides normal behavior, to deal with the separation of * specific children. * * @param node the node to insert */ @Override public void addChild (TreeNode node) { // Specific children lists if (node instanceof Staff) { staves.addChild(node); reset(); } else if (node instanceof Measure) { measures.addChild(node); } else if (node instanceof Slur) { slurs.addChild(node); } else if (node instanceof LyricsLine) { lyrics.addChild(node); } else if (node instanceof Text) { texts.addChild(node); } else { super.addChild(node); } } //---------------// // barlineExists // //---------------// public boolean barlineExists (int x, int maxShiftDx) { for (TreeNode node : getMeasures()) { Measure measure = (Measure) node; if (Math.abs(measure.getBarline().getCenter().x - x) <= maxShiftDx) { return true; } } return false; // Not found } //-------------// // cleanupNode // //-------------// public void cleanupNode () { getSlurs().clear(); getLyrics().clear(); getTexts().clear(); } //------------------// // connectSlursWith // //------------------// /** * Try to connect the orphan slurs at the beginning of this part * with the orphan slurs at the end of the provided preceding part. * * @param precedingPart the part to connect to, either in the preceding * system, or in the last system of the preceding page */ public void connectSlursWith (SystemPart precedingPart) { if (precedingPart != null) { // Orphans slurs at the beginning of the current system part List<Slur> orphans = getSlurs(Slur.isBeginningOrphan); Collections.sort(orphans, Slur.verticalComparator); for (Slur slur : orphans) { // Nullify a potential link to zombie slurs slur.resetLeftExtension(); } List<Slur> precedingOrphans = precedingPart.getSlurs( Slur.isEndingOrphan); Collections.sort(precedingOrphans, Slur.verticalComparator); for (Slur slur : precedingOrphans) { // Nullify a potential link to zombie slurs slur.resetRightExtension(); } // Connect the orphans as much as possible SlurLoop: for (Slur slur : orphans) { for (Slur prevSlur : precedingOrphans) { if (slur.canExtend(prevSlur)) { slur.connectTo(prevSlur); continue SlurLoop; } } // No connection for this orphan slur.addError(" Could not left-connect slur #" + slur.getId()); } // Check previous orphans for (Slur prevSlur : precedingOrphans) { if (prevSlur.getRightExtension() == null) { prevSlur.addError( " Could not right-connect slur #" + prevSlur.getId()); } } } } //-----------------// // createDummyPart // //-----------------// /** * Create an dummy system part, parallel to this part, just to fill * needed measures for another part. * <ul> * <li>Clef is taken from first real measure of part to be extended * (this part, called the refPart, has the provided id and is found in * a following system, or in a preceding system)</li> * <li>Key sig is taken from this part</li> * <li>Time sig is taken from this part</li> * <li>Measures are defined as parallel to this part, and filled with just * one whole rest</li> * </ul> * * @param id the id for the desired dummy part * @return the created dummy part, ready to be exported */ public SystemPart createDummyPart (int id) { logger.debug("{} createDummyPart for id={}", getContextString(), id); // Find some concrete system part for the provided id SystemPart refPart = findRefPart(id); SystemPart dummyPart = new SystemPart(getSystem(), null); dummyPart.setId(id); dummyPart.setDummy(true); dummyPart.setScorePart(refPart.getScorePart()); Measure nextMeasure = refPart.getFirstMeasure(); // Loop on measures boolean isFirstMeasure = true; for (TreeNode mn : getMeasures()) { Measure measure = (Measure) mn; Measure dummyMeasure = new Measure(dummyPart); dummyMeasure.setDummy(true); dummyMeasure.setPageId(measure.getPageId()); // Loop on staves int staffIndex = -1; for (TreeNode sn : refPart.getStaves()) { Staff nextStaff = (Staff) sn; staffIndex++; Staff dummyStaff; if (isFirstMeasure) { // Create dummy Staff dummyStaff = new Staff( null, // No staff info counterpart dummyPart, null, getFirstStaff().getWidth(), nextStaff.getHeight()); dummyStaff.setDummy(true); // Create dummy Clef Clef nextClef = nextMeasure.getFirstMeasureClef( nextStaff.getId()); if (nextClef != null) { Clef dummyClef = new Clef( dummyMeasure, dummyStaff, nextClef); } } else { dummyStaff = (Staff) dummyPart.getStaves().get(staffIndex); } // Replicate Key if any if (!measure.getKeySignatures().isEmpty()) { KeySignature dummyKey = new KeySignature( dummyMeasure, dummyStaff, (KeySignature) measure.getKeySignatures().get(0)); } // Replicate Time if any TimeSignature ts = measure.getTimeSignature(); if (ts != null) { new TimeSignature(dummyMeasure, dummyStaff, ts); } // Create dummy Whole rest (w/ no precise location) dummyMeasure.addWholeRest(dummyStaff, null); } // Compute a not-too-silly ordinate int yOffset = refPart.getBox().y - refPart.getSystem().getBox().y; dummyMeasure.setBox( new Rectangle( measure.getBox().x, getSystem().getBox().y + yOffset, measure.getBox().width, refPart.getBox().height)); isFirstMeasure = false; } if (logger.isDebugEnabled()) { if (dummyPart.dumpNode()) { dummyPart.dumpChildren(1); } } return dummyPart; } //-------------// // findRefPart // //-------------// /** * Look in following systems, then in previous systems, for a real * part with the provided ID. * * @param id the desired part ID * @return the first real part with this ID, either in following systems * or in preceding systems. */ private SystemPart findRefPart (int id) { // First look in the following systems in the same page ScoreSystem nextSystem = getSystem(); while (true) { nextSystem = (ScoreSystem) nextSystem.getNextSibling(); if (nextSystem != null) { SystemPart part = nextSystem.getPart(id); if (part != null && !part.isDummy()) { return part; } } else { break; } } // Then look in the preceding systems in the same page ScoreSystem prevSystem = getSystem(); while (true) { prevSystem = (ScoreSystem) prevSystem.getPreviousSibling(); if (prevSystem != null) { SystemPart part = prevSystem.getPart(id); if (part != null && !part.isDummy()) { return part; } } else { break; } } logger.warn("{} Cannot find real system part with id {}", getContextString(), id); return null; } //----------// // getBrace // //----------// /** * @return the brace */ public Glyph getBrace () { return brace; } //-----------------// // getFirstMeasure // //-----------------// /** * Report the first measure in this system part. * * @return the first measure entity */ public Measure getFirstMeasure () { return (Measure) getMeasures().get(0); } //---------------// // getFirstStaff // //---------------// /** * Report the first staff in this system part. * * @return the first staff entity */ public Staff getFirstStaff () { return (Staff) getStaves().get(0); } //--------------// // getFollowing // //--------------// public SystemPart getFollowing () { ScoreSystem nextSystem = (ScoreSystem) getSystem().getNextSibling(); if (nextSystem != null) { return nextSystem.getPart(id); } else { return null; } } //-------// // getId // //-------// /** * Report the part id within the containing system, starting at 1. * * @return the part id */ public int getId () { return id; } //----------------// // getLastMeasure // //----------------// /** * Report the last measure in the system part. * * @return the last measure entity */ public Measure getLastMeasure () { List<TreeNode> meas = getMeasures(); if (!meas.isEmpty()) { return (Measure) meas.get(meas.size() - 1); } else { return null; } } //--------------// // getLastStaff // //--------------// /** * Report the last staff in this system part. * * @return the last staff entity */ public Staff getLastStaff () { return (Staff) getStaves().get(getStaves().size() - 1); } //-----------// // getLyrics // //-----------// /** * Report the collection of lyrics. * * @return the lyrics list, which may be empty but not null */ public List<TreeNode> getLyrics () { return lyrics.getChildren(); } //--------------// // getMeasureAt // //--------------// /** * Report the measure that contains a given point (assumed to be in * the containing system part). * * @param point page-based coordinates of the given point * @return the containing measure */ public Measure getMeasureAt (Point point) { Measure measure = null; for (TreeNode node : getMeasures()) { measure = (Measure) node; Barline barline = measure.getBarline(); if ((barline == null) || (point.x <= barline.getRightX())) { return measure; } } return measure; } //-------------// // getMeasures // //-------------// /** * Report the collection of measures. * * @return the measure list, which may be empty but not null */ public List<TreeNode> getMeasures () { return measures.getChildren(); } //---------// // getName // //---------// /** * @return the name */ public String getName () { return name; } //--------------------// // getPrecedingInPage // //--------------------// /** * Report the corresponding part (if any) in the previous system. * Even if there is a previous system, there may be no part that corresponds * to this one. * * @return the corresponding part, or null */ public SystemPart getPrecedingInPage () { ScoreSystem prevSystem = (ScoreSystem) getSystem().getPreviousSibling(); if (prevSystem != null) { return prevSystem.getPart(id); } else { return null; } } //--------------// // getScorePart // //--------------// public ScorePart getScorePart () { return scorePart; } //----------// // getSlurs // //----------// /** * Report the collection of slurs. * * @return the slur list, which may be empty but not null */ public List<TreeNode> getSlurs () { return slurs.getChildren(); } //----------// // getSlurs // //----------// /** * Report the collection of slurs for which the provided predicate * is true. * * @param predicate the check to run * @return the collection of selected slurs, which may be empty */ public List<Slur> getSlurs (Predicate<Slur> predicate) { List<Slur> selectedSlurs = new ArrayList<>(); for (TreeNode sNode : getSlurs()) { Slur slur = (Slur) sNode; if (predicate.check(slur)) { selectedSlurs.add(slur); } } return selectedSlurs; } //------------// // getStaffAt // //------------// /** * Report the staff nearest (in ordinate) to a provided page point * within the part staves. * * @param point the provided page point * @return the nearest staff, within the part staves */ public Staff getStaffAt (Point point) { // This may fail StaffInfo staffInfo = getSystem().getInfo().getStaffAt(point); if (staffInfo == null) { return null; } Staff staff = staffInfo.getScoreStaff(); if (staves.getChildren().contains(staff)) { return staff; } if (staff.getInfo().getId() < getFirstStaff().getInfo().getId()) { return getFirstStaff(); } else { return getLastStaff(); } } //-------------------// // getStaffJustAbove // //-------------------// /** * Report the staff which is at or above the provided point * * @param point the provided point * @return the staff just above */ public Staff getStaffJustAbove (Point point) { Staff pointStaff = getStaffAt(point); double pitch = pointStaff.pitchPositionOf(point); if (pitch < 0 && pointStaff != getFirstStaff()) { return (Staff) pointStaff.getPreviousSibling(); } else { return pointStaff; } } //-------------------// // getStaffJustBelow // //-------------------// /** * Report the staff which is at or below the provided point * * @param point the provided point * @return the staff just below */ public Staff getStaffJustBelow (Point point) { Staff pointStaff = getStaffAt(point); double pitch = pointStaff.pitchPositionOf(point); if (pitch > 0 && pointStaff != getLastStaff()) { return (Staff) pointStaff.getNextSibling(); } else { return pointStaff; } } //------------------// // getStaffPosition // //------------------// /** * Report the vertical position of the provided point with respect * to the part staves. * * @param point the point whose ordinate is to be checked * @return the StaffPosition value */ public StaffPosition getStaffPosition (Point point) { Staff firstStaff = getFirstStaff(); if (point.y < firstStaff.getTopLeft().y) { return StaffPosition.ABOVE_STAVES; } Staff lastStaff = getLastStaff(); if (point.y > (lastStaff.getTopLeft().y + lastStaff.getHeight())) { return StaffPosition.BELOW_STAVES; } else { return StaffPosition.WITHIN_STAVES; } } //--------------------// // getStartingBarline // //--------------------// /** * Get the barline that starts the part. * * @return barline the starting bar line (which may be null) */ public Barline getStartingBarline () { return startingBarline; } //-----------// // getStaves // //-----------// /** * Report the ordered list of staves that belong to this system part. * * @return the list of staves */ public List<TreeNode> getStaves () { return (staves == null) ? null : staves.getChildren(); } //-----------// // getSystem // //-----------// /** * Report the containing system. * * @return the containing system */ @Override public ScoreSystem getSystem () { return (ScoreSystem) getParent(); } //----------// // getTexts // //----------// public List<TreeNode> getTexts () { return texts.getChildren(); } //---------// // isDummy // //---------// public boolean isDummy () { return dummy; } //--------------// // mapSyllables // //--------------// /** * Assign each syllable to its related node. */ public void mapSyllables () { for (TreeNode node : getLyrics()) { LyricsLine line = (LyricsLine) node; line.mapSyllables(); } } //---------------------// // populateLyricsLines // //---------------------// /** * Organize the various lyrics items in aligned lyrics lines. */ public void populateLyricsLines () { // Create the lyrics lines as needed for (TreeNode tn : getTexts()) { Text text = (Text) tn; if (text instanceof LyricsItem) { LyricsItem item = (LyricsItem) text; LyricsLine.populate(item, this); } } // Assign the lines id & related staff Collections.sort(getLyrics(), LyricsLine.yComparator); for (TreeNode node : getLyrics()) { LyricsLine line = (LyricsLine) node; line.setId(lyrics.getChildren().indexOf(line) + 1); line.setStaff( getSystem().getStaffAbove(new Point(0, line.getY()))); } } //----------------------// // refineLyricSyllables // //----------------------// /** * Determine for each lyrics item of syllable kind, its precise * syllabic type (single, of part of a longer word). */ public void refineLyricSyllables () { for (TreeNode node : getLyrics()) { LyricsLine line = (LyricsLine) node; line.refineLyricSyllables(); } } //-------------------// // connectTiedVoices // //-------------------// /** * Make sure that notes tied across measures keep the same voice. * This is performed for all ties in this part. */ public void connectTiedVoices () { for (TreeNode tn : getSlurs()) { Slur slur = (Slur) tn; if (!slur.isTie()) { continue; } // Voice on left (perhaps in a previous measure / system / page) Note leftNote = slur.getLeftNote(); if (leftNote == null) { Slur leftExtension = slur.getLeftExtension(); if (leftExtension == null) { continue; } leftNote = leftExtension.getLeftNote(); if (leftNote == null) { continue; } } Chord leftChord = leftNote.getChord(); Voice leftVoice = leftChord.getVoice(); // Voice on right Note rightNote = slur.getRightNote(); if (rightNote == null) { continue; } Chord rightChord = rightNote.getChord(); Voice rightVoice = rightChord.getVoice(); if (leftVoice.getId() != rightVoice.getId()) { logger.debug("Tie to map {} and {}", leftChord, rightChord); rightChord.getMeasure().swapVoiceId(rightVoice, leftVoice.getId()); } } } //-------------------------// // retrieveSlurConnections // //-------------------------// /** * Retrieve the connections between the (orphan) slurs at the * beginning of this part and the (orphan) slurs at the end of the * preceding part. */ public void retrieveSlurConnections () { // Ending orphans in preceding system/part (if such part exists) SystemPart precedingPart = getPrecedingInPage(); connectSlursWith(precedingPart); } //----------// // setBrace // //----------// /** * @param brace the brace (or bracket) to set */ public void setBrace (Glyph brace) { this.brace = brace; } //----------// // setDummy // //----------// public void setDummy (boolean dummy) { this.dummy = dummy; } //-------// // setId // //-------// /** * Set the part id within the containing system, starting at 1. * * @param id the id value */ public void setId (int id) { this.id = id; } //---------// // setName // //---------// /** * @param name the name to set */ public void setName (String name) { this.name = name; } //--------------// // setScorePart // //--------------// public void setScorePart (ScorePart scorePart) { this.scorePart = scorePart; } //--------------------// // setStartingBarline // //--------------------// /** * Set the barline that starts the part. * * @param startingBarline the starting barline */ public void setStartingBarline (Barline startingBarline) { this.startingBarline = startingBarline; } //----------// // toString // //----------// @Override public String toString () { StringBuilder sb = new StringBuilder(); sb.append("{SystemPart #").append(getId()); if (dummy) { sb.append(" dummy"); } sb.append(" ["); if (getStaves() != null) { boolean first = true; for (TreeNode node : getStaves()) { if (node != null) { Staff staff = (Staff) node; if (!first) { sb.append(","); } sb.append(staff.getId()); } first = false; } } sb.append("]"); if (name != null) { sb.append(" name:").append(name); } sb.append("}"); return sb.toString(); } //------------// // computeBox // //------------// @Override protected void computeBox () { // Use the union of staves boxes Rectangle newBox = null; for (TreeNode node : getStaves()) { Staff staff = (Staff) node; if (newBox == null) { newBox = staff.getBox(); } else { newBox = newBox.union(staff.getBox()); } } setBox(newBox); } //---------// // getInfo // //---------// /** * Report the corresponding info within sheet structure * * @return the info */ public PartInfo getInfo () { return info; } //----------------------// // checkSlurConnections // //----------------------// public void checkSlurConnections () { List<Slur> orphans = getSlurs(Slur.isOrphan); // Discard the slurs on each end for the time being orphans.removeAll(getSlurs(Slur.isBeginningOrphan)); orphans.removeAll(getSlurs(Slur.isEndingOrphan)); for (Slur slur : orphans) { if (slur.getLeftNote() == null && slur.getLeftExtension() == null) { slur.addError("Non left-connected slur"); } if (slur.getRightNote() == null && slur.getRightExtension() == null) { slur.addError("Non right-connected slur"); } } } }