//----------------------------------------------------------------------------// // // // S c o r e // // // //----------------------------------------------------------------------------// // <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.Main; import omr.constant.Constant; import omr.constant.ConstantSet; import omr.text.Language; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import omr.math.Rational; import omr.run.FilterDescriptor; import omr.score.entity.MeasureId.MeasureRange; import omr.score.entity.Page; import omr.score.entity.ScoreNode; import omr.score.entity.ScorePart; import omr.score.entity.Tempo; import omr.score.ui.ScoreTree; import omr.score.visitor.ScoreVisitor; import omr.script.ParametersTask.PartData; import omr.script.Script; import omr.script.ScriptActions; import omr.sheet.Sheet; import omr.sheet.picture.PictureLoader; import omr.sheet.ui.SheetActions; import omr.sheet.ui.SheetsController; import omr.step.StepException; import omr.util.Param; import omr.util.FileUtil; import omr.util.TreeNode; import java.awt.image.RenderedImage; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.SortedMap; import java.util.SortedSet; import javax.swing.JFrame; /** * Class {@code Score} handles a score hierarchy, composed of one or * several pages. * * @author Hervé Bitteur */ public class Score extends ScoreNode { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(Score.class); /** Number of lines in a staff */ public static final int LINE_NB = 5; //~ Instance fields -------------------------------------------------------- /** Input file of the related image(s) */ private final File imageFile; /** The related file radix (name w/o extension) */ private final String radix; /** True if the score contains several pages */ private boolean multiPage; /** The recording of key processing data */ private ScoreBench bench; /** Dominant text language in the score */ private String language; /** Greatest duration divisor */ private Integer durationDivisor; /** ScorePart list for the whole score */ private List<ScorePart> partList; /** The specified volume, if any */ private Integer volume; /** Potential measure range, if not all score is to be played */ private MeasureRange measureRange; /** Browser tree on this score */ private ScoreTree scoreTree; /** Where the MusicXML output is to be stored */ private File exportFile; /** Where the script is to be stored */ private File scriptFile; /** Where the MIDI data is to be stored */ private File midiFile; /** Where the sheet PDF data is to be stored */ private File printFile; /** The script of user actions on this score */ private Script script; /** Handling of binarization filter parameter. */ private final Param<FilterDescriptor> filterParam = new Param<>(FilterDescriptor.defaultFilter); /** Handling of tempo parameter. */ private final Param<Integer> tempoParam = new Param<>(Tempo.defaultTempo); /** Handling of language parameter. */ private final Param<String> textParam = new Param<>(Language.defaultSpecification); /** Handling of parts name and program. */ private final Param<List<PartData>> partsParam = new PartsParam(); //~ Constructors ----------------------------------------------------------- //-------// // Score // //-------// /** * Create a Score with a path to an input image file. * * @param imageFile the input image file (which may contain several images) */ public Score (File imageFile) { super(null); // No container this.imageFile = imageFile; radix = FileUtil.getNameSansExtension(imageFile); // Related bench bench = new ScoreBench(this); // Register this score instance ScoresManager.getInstance().addInstance(this); } //~ Methods ---------------------------------------------------------------- //--------// // accept // //--------// @Override public boolean accept (ScoreVisitor visitor) { return visitor.visit(this); } //-------// // close // //-------// /** * Close this score instance, as well as its view if any. */ public void close () { logger.info("Closing {}", this); // Check whether the score script has been saved (or user has declined) if ((Main.getGui() != null) && !ScriptActions.checkStored(getScript())) { return; } // Close contained sheets (and pages) for (TreeNode pn : new ArrayList<>(getPages())) { Page page = (Page) pn; Sheet sheet = page.getSheet(); sheet.remove(true); } // Close tree if any if (scoreTree != null) { scoreTree.close(); } // Complete and store all bench data ScoresManager.getInstance().storeBench(bench, null, true); // Remove from score instances ScoresManager.getInstance().removeInstance(this); } //-------------// // createPages // //-------------// /** * Create as many pages (and related sheets) as there are images * in the input image file. * * @param pages set of page ids (1-based) explicitly included. * if set is empty or null all pages are loaded */ public void createPages (SortedSet<Integer> pages) { SortedMap<Integer, RenderedImage> images = PictureLoader.loadImages( imageFile, pages); if (images != null) { Page firstPage = null; setMultiPage(images.size() > 1); // Several images in the file for (Entry<Integer, RenderedImage> entry : images.entrySet()) { int index = entry.getKey(); RenderedImage image = entry.getValue(); Page page = null; try { page = new Page(this, index, image); if (firstPage == null) { firstPage = page; // Let the UI focus on first page if (Main.getGui() != null) { SheetsController.getInstance().showAssembly(firstPage. getSheet()); } } } catch (StepException ex) { // Remove page from score, if already included if ((page != null) && getPages().remove(page)) { logger.info("Page #{} removed", index); } } } // Remember (even across runs) the parent directory ScoresManager.getInstance().setDefaultInputDirectory(getImageFile(). getParent()); // Insert in sheet history ScoresManager.getInstance().getHistory().add(getImagePath()); if (Main.getGui() != null) { SheetActions.HistoryMenu.getInstance().setEnabled(true); } } } //------// // dump // //------// /** * Dump a whole score hierarchy. */ public void dump () { System.out.println( "----------------------------------------------------------------"); if (dumpNode()) { dumpChildren(1); } System.out.println( "----------------------------------------------------------------"); } //----------------// // getFilterParam // //----------------// public Param<FilterDescriptor> getFilterParam () { return filterParam; } //----------------// // getTempoParam // //----------------// public Param<Integer> getTempoParam () { return tempoParam; } //--------------// // getTextParam // //--------------// public Param<String> getTextParam () { return textParam; } //---------------// // getPartsParam // //---------------// public Param<List<PartData>> getPartsParam () { return partsParam; } //------------------// // getDefaultVolume // //------------------// /** * Report default value for Midi volume. * * @return the default volume value */ public static int getDefaultVolume () { return constants.defaultVolume.getValue(); } //--------------------// // getDurationDivisor // //--------------------// /** * Report the common divisor used for this score when * simplifying the durations. * * @return the computed divisor (GCD), or null if not computable */ public Integer getDurationDivisor () { if (durationDivisor == null) { accept(new ScoreReductor()); } return durationDivisor; } //---------------// // getExportFile // //---------------// /** * Report to which file, if any, the score is to be exported. * * @return the exported xml file, or null */ public File getExportFile () { return exportFile; } //--------------// // getFirstPage // //--------------// public Page getFirstPage () { if (children.isEmpty()) { return null; } else { return (Page) children.get(0); } } //--------------// // getImageFile // //--------------// /** * @return the imageFile */ public File getImageFile () { return imageFile; } //--------------// // getImagePath // //--------------// /** * Report the (canonical) file name of the score image(s). * * @return the file name */ public String getImagePath () { return imageFile.getPath(); } //--------------------// // getMeasureIdOffset // //--------------------// /** * Report the offset to add to page-based measure ids of the * provided page to get absolute (score-based) ids. * * @param page the provided page * @return the measure id offset for the page */ public Integer getMeasureIdOffset (Page page) { int offset = 0; for (TreeNode pn : getPages()) { Page p = (Page) pn; if (p == page) { return offset; } else { Integer delta = p.getDeltaMeasureId(); if (delta != null) { offset += delta; } else { // This page has no measures yet, so ... return null; } } } throw new IllegalArgumentException(page + " not found in score"); } //-----------------// // getMeasureRange // //-----------------// /** * Report the potential range of selected measures. * * @return the selected measure range, perhaps null */ public MeasureRange getMeasureRange () { return measureRange; } //-------------// // getMidiFile // //-------------// /** * Report to which file, if any, the MIDI data is to be exported. * * @return the Midi file, or null */ public File getMidiFile () { return midiFile; } //---------// // getPage // //---------// /** * Report the page with provided page-index. * * @param pageIndex the desired value for page index * @return the proper page, or null if not found */ public Page getPage (int pageIndex) { for (TreeNode pn : getPages()) { Page page = (Page) pn; if (page.getIndex() == pageIndex) { return page; } } return null; } //----------// // getPages // //----------// /** * Report the collection of pages in that score. * * @return the pages */ public List<TreeNode> getPages () { return getChildren(); } //-------------// // isMultiPage // //-------------// /** * @return the multiPage */ public boolean isMultiPage () { return multiPage; } //--------// // isIdle // //--------// /** * Check whether this score is idle or not. * The score is busy when at least one of its pages/sheets is under a step * processing. * * @return true if idle, false if busy */ public boolean isIdle () { for (TreeNode pn : getPages()) { Page page = (Page) pn; Sheet sh = page.getSheet(); if (sh != null && sh.getCurrentStep() != null) { return false; } } return true; } //-----------------// // setDefaultTempo // //-----------------// /** * Assign default value for Midi tempo. * * @param tempo the default tempo value */ public static void setDefaultTempo (int tempo) { constants.defaultTempo.setValue(tempo); } //------------------// // setDefaultVolume // //------------------// /** * Assign default value for Midi volume. * * @param volume the default volume value */ public static void setDefaultVolume (int volume) { constants.defaultVolume.setValue(volume); } //----------// // getBench // //----------// /** * Report the related sheet bench. * * @return the related bench */ public ScoreBench getBench () { return bench; } //-----------------// // getBrowserFrame // //-----------------// /** * Create a dedicated frame, where all score elements can be * browsed in the tree hierarchy. * * @return the created frame */ public JFrame getBrowserFrame () { if (scoreTree == null) { // Build the ScoreTree on the score scoreTree = new ScoreTree(this); } return scoreTree.getFrame(); } //-------------// // getLastPage // //-------------// public Page getLastPage () { if (children.isEmpty()) { return null; } else { return (Page) children.get(children.size() - 1); } } //------------------// // getMeasureOffset // //------------------// /** * Report the offset to add to page-based measure index of the * provided page to get absolute (score-based) indices. * * @param page the provided page * @return the measure index offset for the page */ public int getMeasureOffset (Page page) { int offset = 0; for (TreeNode pn : getPages()) { Page p = (Page) pn; if (p == page) { return offset; } else { offset += p.getMeasureCount(); } } throw new IllegalArgumentException(page + " not found in score"); } //-------------// // getPartList // //-------------// /** * Report the global list of parts. * * @return partList the list of score parts */ public List<ScorePart> getPartList () { return partList; } //--------------// // getPrintFile // //--------------// /** * Report to which file, if any, the sheet PDF data is to be written. * * @return the sheet PDF file, or null */ public File getPrintFile () { return printFile; } //----------// // getRadix // //----------// /** * Report the radix of the file that corresponds to the score. * It is based on the simple file name of the score, with no path and no * extension. * * @return the score input file radix */ public String getRadix () { return radix; } //--------------// // getLogPrefix // //--------------// /** * Report the proper prefix to use when logging a message * * @return the proper prefix */ public String getLogPrefix () { if (ScoresManager.isMultiScore()) { return "[" + radix + "] "; } else { return ""; } } //-----------// // getScript // //-----------// public Script getScript () { if (script == null) { script = new Script(this); } return script; } //---------------// // getScriptFile // //---------------// /** * Report the file, if any, where the script should be written. * * @return the related script file or null */ public File getScriptFile () { return scriptFile; } //-----------// // getVolume // //-----------// /** * Report the assigned volume, if any. * If the value is not yet set, it is set to the default value and returned. * * @return the assigned volume, or null */ public Integer getVolume () { if (!hasVolume()) { volume = getDefaultVolume(); } return volume; } //-------------// // hasLanguage // //-------------// /** * Check whether a language has been defined for this score. * * @return true if a language is defined */ public boolean hasLanguage () { return language != null; } //-----------// // hasVolume // //-----------// /** * Check whether a volumehas been defined for this score. * * @return true if a volume is defined */ public boolean hasVolume () { return volume != null; } //--------// // remove // //--------// /** * Remove a page */ public void remove (Page page) { getPages().remove(page); setMultiPage(getPages().size() > 1); } //--------------------// // setDurationDivisor // //--------------------// /** * Remember the common divisor used for this score when * simplifying the durations. * * @param durationDivisor the computed divisor (GCD), or null */ public void setDurationDivisor (Integer durationDivisor) { this.durationDivisor = durationDivisor; } //---------------// // setExportFile // //---------------// /** * Remember to which file the score is to be exported. * * @param exportFile the exported xml file */ public void setExportFile (File exportFile) { this.exportFile = exportFile; } //-------------// // setLanguage // //-------------// /** * Set the score dominant language. * * @param language the dominant language */ public void setLanguage (String language) { this.language = language; } //-----------------// // setMeasureRange // //-----------------// /** * Remember a range of measure for this score. * * @param measureRange the range of selected measures */ public void setMeasureRange (MeasureRange measureRange) { this.measureRange = measureRange; } //-------------// // setMidiFile // //-------------// /** * Remember to which file the MIDI data is to be exported. * * @param midiFile the Midi file */ public void setMidiFile (File midiFile) { this.midiFile = midiFile; } //-------------// // setPartList // //-------------// /** * Assign a part list valid for the whole score. * * @param partList the list of score parts */ public void setPartList (List<ScorePart> partList) { this.partList = partList; } //--------------// // setPrintFile // //--------------// /** * Remember to which file the sheet PDF data is to be exported. * * @param sheetPdfFile the sheet PDF file */ public void setPrintFile (File sheetPdfFile) { this.printFile = sheetPdfFile; } //---------------// // setScriptFile // //---------------// /** * Remember the file where the script is written. * * @param scriptFile the related script file */ public void setScriptFile (File scriptFile) { this.scriptFile = scriptFile; } //-----------// // setVolume // //-----------// /** * Assign a volume value. * * @param volume the volume value to be assigned */ public void setVolume (Integer volume) { this.volume = volume; } //------------------// // simpleDurationOf // //------------------// /** * Export a duration to its simplest form, based on the greatest * duration divisor of the score. * * @param value the raw duration * @return the simple duration expression, in the param of proper * divisions */ public int simpleDurationOf (Rational value) { return value.num * (getDurationDivisor() / value.den); } //----------// // toString // //----------// /** * Report a readable description. * * @return a string based on its XML file name */ @Override public String toString () { if (getRadix() != null) { return "{Score " + getRadix() + "}"; } else { return "{Score }"; } } //--------------// // setMultiPage // //--------------// /** * @param multiPage the multiPage to set. */ private void setMultiPage (boolean multiPage) { this.multiPage = multiPage; } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- Constant.Integer defaultTempo = new Constant.Integer( "QuartersPerMn", 120, "Default tempo, stated in number of quarters per minute"); Constant.Integer defaultVolume = new Constant.Integer( "Volume", 78, "Default Volume in 0..127 range"); } //------------// // PartsParam // //------------// private class PartsParam extends Param<List<PartData>> { @Override public List<PartData> getSpecific () { List<ScorePart> list = getPartList(); if (list != null) { List<PartData> data = new ArrayList<>(); for (ScorePart scorePart : list) { // Initial setting for part midi program int prog = (scorePart.getMidiProgram() != null) ? scorePart.getMidiProgram() : scorePart.getDefaultProgram(); data.add(new PartData(scorePart.getName(), prog)); } return data; } else { return null; } } @Override public boolean setSpecific (List<PartData> specific) { try { for (int i = 0; i < specific.size(); i++) { PartData data = specific.get(i); ScorePart scorePart = getPartList().get(i); // Part name scorePart.setName(data.name); // Part midi program scorePart.setMidiProgram(data.program); } logger.info("Score parts have been updated"); return true; } catch (Exception ex) { logger.warn("Error updating score parts", ex); } return false; } } }