//----------------------------------------------------------------------------//
// //
// P a r t C o n n e c t i o n //
// //
//----------------------------------------------------------------------------//
// <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.Page;
import omr.score.entity.ScorePart;
import omr.score.entity.ScoreSystem;
import omr.score.entity.SystemPart;
import omr.util.TreeNode;
import com.audiveris.proxymusic.PartList;
import com.audiveris.proxymusic.PartName;
import com.audiveris.proxymusic.ScorePartwise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Class {@code PartConnection} is in charge of finding the connections of parts
* across systems (and pages) so that a part always represents the same
* instrument all along the score.
*
* <p>This work is done across:
* <ul>
* <li>The various systems of a page using Audiveris ScoreSystem instances.</li>
* <li>The various pages of a score using Audiveris Page instances.</li>
* <li>The various pages of a score using Proxymusic ScorePartwise
* instances.</li>
* </ul>
* All together, this sums up to three different cases to handle, so we have
* taken a generic approach, abstracting the different types into Candidates and
* Results.</p>
*
* <p>The strategy used to build Results out of Candidates is based on the
* following assumptions:
* <ul>
* <li>For a part of a system to be connected to a part of another system,
* they must exhibit the same count of staves.</li>
* <li>Parts cannot be swapped from one system to the other. In other words, we
* cannot have say partA followed by partB in a system, and partB followed by
* partA in another system.</li>
* <li>Additional parts appear at the top of a system, rather than at the
* bottom. So we process part connections bottom up.</li>
* <li>When possible, we use the part names (or abbreviations) to help the
* connection algorithm. (not yet fully implemented).
* </ul>
*
* @author Hervé Bitteur
*/
public class PartConnection
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(PartConnection.class);
//~ Instance fields --------------------------------------------------------
/** Input data */
private final Set<List<Candidate>> sequences;
/** Record the set of candidates per result */
private final SortedMap<Result, Set<Candidate>> resultMap = new TreeMap<>();
/** Record which result is mapped to which candidate */
private final Map<Candidate, Result> candidateMap = new LinkedHashMap<>();
//~ Constructors -----------------------------------------------------------
//----------------//
// PartConnection //
//----------------//
/**
* Creates a new PartConnection object.
* Not meant to be called directly, use proper static methods instead:
* {@link #connectPageSystems},
* {@link #connectScorePages} or
* {@link #connectProxyPages}.
*
* @param sequences a set of sequences of parts
*/
private PartConnection (Set<List<Candidate>> sequences)
{
this.sequences = sequences;
connect();
}
//~ Methods ----------------------------------------------------------------
//--------------------//
// connectPageSystems //
//--------------------//
/**
* Convenient method to connect parts across systems of a page.
* This method is to be used when processing one page, and simply connecting
* the parts of the systems that appear on this page. Here we work with
* Audiveris ScoreSystem entities.
*
* @param page the containing page
*/
public static PartConnection connectPageSystems (Page page)
{
// Build candidates (here, a candidate is a SystemPart)
Set<List<Candidate>> sequences = new LinkedHashSet<>();
for (TreeNode sn : page.getSystems()) {
ScoreSystem system = (ScoreSystem) sn;
List<Candidate> parts = new ArrayList<>();
for (TreeNode pn : system.getParts()) {
SystemPart systemPart = (SystemPart) pn;
parts.add(new SystemPartCandidate(systemPart));
}
sequences.add(parts);
}
return new PartConnection(sequences);
}
//-------------------//
// connectProxyPages //
//-------------------//
/**
* Convenient method to connect parts across pages.
* This method is to be used when merging the results of several pages.
* Here we work with ProxyMusic ScorePartwise entities, since we expect each
* page result to be provided via MusicXML.
*
* @param pages the sequence of pages, as (proxymusic) ScorePartwise
* instances
*/
public static PartConnection connectProxyPages (
SortedMap<Integer, ScorePartwise> pages)
{
// Build candidates (here a candidate is a ScorePart)
Set<List<Candidate>> sequences = new LinkedHashSet<>();
for (Entry<Integer, ScorePartwise> entry : pages.entrySet()) {
int index = entry.getKey();
ScorePartwise page = entry.getValue();
PartList partList = page.getPartList();
List<Candidate> parts = new ArrayList<>();
for (Object obj : partList.getPartGroupOrScorePart()) {
// TODO: For the time being, we ignore part-group elements.
if (obj instanceof com.audiveris.proxymusic.ScorePart) {
com.audiveris.proxymusic.ScorePart scorePart = (com.audiveris.proxymusic.ScorePart) obj;
parts.add(new PMScorePartCandidate(scorePart, page, index));
}
}
sequences.add(parts);
}
return new PartConnection(sequences);
}
//-------------------//
// connectScorePages //
//-------------------//
/**
* Convenient method to connect parts across pages.
* This method is to be used when merging the results of several pages.
* Here we work directly with Audiveris Page entities
*
* @param pages the sequence of pages, as (audiveris) Page instances
*/
public static PartConnection connectScorePages (
SortedMap<Integer, Page> pages)
{
// Build candidates (here a candidate is a ScorePart)
Set<List<Candidate>> sequences = new LinkedHashSet<>();
for (Entry<Integer, Page> entry : pages.entrySet()) {
Page page = entry.getValue();
List<ScorePart> partList = page.getPartList();
List<Candidate> parts = new ArrayList<>();
for (ScorePart scorePart : partList) {
parts.add(new ScorePartCandidate(scorePart, page));
}
sequences.add(parts);
}
return new PartConnection(sequences);
}
//-----------------//
// getCandidateMap //
//-----------------//
/**
* Report an unmodifiable view of which resulting part has been assigned
* to any given candidate
*
* @return the candidateMap (candidate -> assigned result)
*/
public Map<Candidate, Result> getCandidateMap ()
{
return Collections.unmodifiableMap(candidateMap);
}
//--------------//
// getResultMap //
//--------------//
/**
* Report which candidate parts have been mapped to any given result
*
* @return the resultMap ((sorted) result -> (unsorted) set of candidates)
*/
public SortedMap<Result, Set<Candidate>> getResultMap ()
{
return resultMap;
}
//---------//
// connect //
//---------//
/**
* The heart of the part connection algorithm, organized to work through
* interfaces in order to use the same piece of code, when we connect
* systems of one page, or when we connect parts across several pages.
*/
private void connect ()
{
/** Resulting sequence of ScorePart's */
final List<Result> results = new ArrayList<>();
/** Temporary map, to record the set of candidates per result */
final Map<Result, Set<Candidate>> rawMap = new HashMap<>();
// Process each sequence of parts in turn
// (typically a sequence of parts is a system)
for (List<Candidate> sequence : sequences) {
// Current index in results sequence (built in reverse order)
int resultIndex = -1;
if (logger.isDebugEnabled()) {
logger.debug("Processing new sequence ...");
for (Candidate candidate : sequence) {
logger.debug("- {}", candidate);
}
}
// Process the sequence in reverse order (bottom up)
for (ListIterator<Candidate> it = sequence.listIterator(
sequence.size()); it.hasPrevious();) {
Candidate candidate = it.previous();
logger.debug("Processing candidate {} count:{}",
candidate, candidate.getStaffCount());
// Check with scoreParts currently defined
resultIndex++;
logger.debug("scorePartIndex:{}", resultIndex);
if (resultIndex >= results.size()) {
logger.debug("No more scoreParts available");
// Create a brand new score part for this candidate
createResult(resultIndex, candidate, results, rawMap);
} else {
Result result = results.get(resultIndex);
logger.debug("Part:{}", result);
// Check we are connectable in terms of staves
if (result.getStaffCount() != candidate.getStaffCount()) {
logger.debug("Count incompatibility");
// Create a brand new score part for this candidate
createResult(resultIndex, candidate, results, rawMap);
} else {
// Can we use names? Just for fun for the time being
if ((candidate.getName() != null)
&& (result.getName() != null)) {
boolean namesOk = candidate.getName().
equalsIgnoreCase(
result.getName());
logger.debug("Names OK: {}", namesOk);
if (!namesOk) {
logger.debug("\"{}\" vs \"{}\"",
candidate.getName(), result.getName());
}
}
// We are compatible
candidateMap.put(candidate, result);
logger.debug("Compatible."
+ " Mapped candidate {} to result {}",
candidate, result);
rawMap.get(result).add(candidate);
}
}
}
}
// Reverse and number ScorePart instances
Collections.reverse(results);
for (int i = 0; i < results.size(); i++) {
Result result = results.get(i);
int id = i + 1;
result.setId(id);
// Forge a ScorePart name if none has been assigned
if (result.getName() == null) {
result.setName("Part_" + id);
}
logger.debug("Final {}", result);
}
// Now that results are ordered, we can deliver the sorted map
resultMap.putAll(rawMap);
}
//--------------//
// createResult //
//--------------//
private Result createResult (int resultIndex,
Candidate candidate,
List<Result> results,
Map<Result, Set<Candidate>> rawMap)
{
Set<Candidate> candidates = new LinkedHashSet<>();
Result result = candidate.createResult();
candidateMap.put(candidate, result);
logger.debug("Creation. Mapped candidate {} to result {}",
candidate, result);
candidates.add(candidate);
rawMap.put(result, candidates);
results.add(resultIndex, result);
return result;
}
//~ Inner Interfaces -------------------------------------------------------
//-----------//
// Candidate //
//-----------//
/**
* Interface {@code Candidate} is used to process part candidates,
* regardless whether they are provided:
* <ul>
* <li>as Audiveris {@link omr.score.entity.SystemPart} instances
* (produced by the scanning of just one page)</li>
* <li>as Audiveris {@link omr.score.entity.ScorePart}
* (when merging Audiveris pages)</li>
* <li>as ProxyMusic {@link com.audiveris.proxymusic.ScorePart} instances
* (used when merging MusicXML files).</li>
* </ul>
*/
public static interface Candidate
{
//~ Methods ------------------------------------------------------------
/** Create a related result instance consistent with this type */
public Result createResult ();
/** Report the abbreviation, if any, that relates to this part */
public String getAbbreviation ();
/** Index of the input: System # for SystemPart, Page # for ScorePart */
public int getInputIndex ();
/** Report the name of the part, if any */
public String getName ();
/** Report the number of staves in the part */
public int getStaffCount ();
/** Report the underlying object */
public Object getUnderlyingObject ();
}
//--------//
// Result //
//--------//
/**
* Interface {@code Result} is used to process resulting ScorePart
* instances,
* regardless whether they are instances of standard Audiveris {@link
* ScorePart} or instances of ProxyMusic
* {@link com.audiveris.proxymusic.ScorePart}.
*/
public static interface Result
extends Comparable<Result>
{
//~ Methods ------------------------------------------------------------
/** Report the part abbreviation, if any */
public String getAbbreviation ();
/** Report the candidate object used to build this result */
public Candidate getCandidate ();
/** Report the part id */
public int getId ();
/** Report the part name */
public String getName ();
/** Report the number of staves in that part */
public int getStaffCount ();
/** Report the actual underlying instance */
public Object getUnderlyingObject ();
/** Assign an abbreviation to the part */
public void setAbbreviation (String abbreviation);
/** Assign an unique id to the part */
public void setId (int id);
/** Assign a name to the part */
public void setName (String name);
}
//~ Inner Classes ----------------------------------------------------------
//----------------//
// AbstractResult //
//----------------//
private abstract static class AbstractResult
implements Result
{
//~ Instance fields ----------------------------------------------------
protected final Candidate candidate;
//~ Constructors -------------------------------------------------------
public AbstractResult (Candidate candidate)
{
this.candidate = candidate;
}
//~ Methods ------------------------------------------------------------
@Override
public int compareTo (Result other)
{
return Integer.signum(getId() - other.getId());
}
@Override
public Candidate getCandidate ()
{
return candidate;
}
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder("{");
sb.append(getClass().getSimpleName());
sb.append(" id:").append(getId());
sb.append(" name:\"").append(getName()).append("\"");
if (getAbbreviation() != null) {
sb.append(" abbr:\"").append(getAbbreviation()).append("\"");
}
sb.append(" staffCount:").append(getStaffCount());
sb.append("}");
return sb.toString();
}
}
//----------------------//
// PMScorePartCandidate //
//----------------------//
/**
* Wrapping class meant for a proxymusic ScorePart instance candidate
*/
private static class PMScorePartCandidate
implements Candidate
{
//~ Instance fields ----------------------------------------------------
private final com.audiveris.proxymusic.ScorePart scorePart;
private final ScorePartwise scorePartwise;
private final int inputIndex;
private Integer staffCount;
//~ Constructors -------------------------------------------------------
public PMScorePartCandidate (
com.audiveris.proxymusic.ScorePart scorePart,
ScorePartwise scorePartwise,
int inputIndex)
{
this.scorePart = scorePart;
this.scorePartwise = scorePartwise;
this.inputIndex = inputIndex;
}
//~ Methods ------------------------------------------------------------
@Override
public PMScorePartResult createResult ()
{
// Create a brand new score part for this candidate
// Id is irrelevant for the time being
/** Factory for proxymusic entities */
com.audiveris.proxymusic.ObjectFactory factory = new com.audiveris.proxymusic.ObjectFactory();
PMScorePartResult result = new PMScorePartResult(
this,
getStaffCount(),
factory.createScorePart());
result.setName(getName());
result.setAbbreviation(getAbbreviation());
logger.debug("Created {} from {}", result, this);
return result;
}
@Override
public String getAbbreviation ()
{
PartName partName = scorePart.getPartAbbreviation();
if (partName != null) {
return partName.getValue();
} else {
return null;
}
}
@Override
public int getInputIndex ()
{
return inputIndex;
}
@Override
public String getName ()
{
PartName partName = scorePart.getPartName();
if (partName != null) {
return partName.getValue();
} else {
return null;
}
}
@Override
public int getStaffCount ()
{
if (staffCount == null) {
// Determine the corresponding staff count
// We have to dig into the first measure of the part itself
staffCount = 1; // Default value
String id = scorePart.getId();
///logger.debug("scorePart id:" + id);
for (ScorePartwise.Part part : scorePartwise.getPart()) {
if (part.getId() != scorePart) {
continue;
}
// Get first measure of this part
ScorePartwise.Part.Measure firstMeasure = part.getMeasure().
get(0);
// Look for Attributes element
for (Object obj : firstMeasure.getNoteOrBackupOrForward()) {
if (!(obj instanceof com.audiveris.proxymusic.Attributes)) {
continue;
}
com.audiveris.proxymusic.Attributes attributes = (com.audiveris.proxymusic.Attributes) obj;
BigInteger staves = attributes.getStaves();
if (staves != null) {
staffCount = staves.intValue();
break;
}
}
break;
}
}
return staffCount;
}
@Override
public Object getUnderlyingObject ()
{
return scorePart;
}
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder("{");
sb.append(getClass().getSimpleName());
sb.append(" page#").append(inputIndex);
sb.append(" \"").append(getName()).append("\"");
sb.append(" staffCount:").append(getStaffCount());
sb.append("}");
return sb.toString();
}
}
//-------------------//
// PMScorePartResult //
//-------------------//
/**
* Wrapping class meant for a proxymusic ScorePart instance result
*/
private static class PMScorePartResult
extends AbstractResult
{
//~ Instance fields ----------------------------------------------------
private final int staffCount;
private final com.audiveris.proxymusic.ScorePart scorePart;
private int id;
//~ Constructors -------------------------------------------------------
public PMScorePartResult (Candidate candidate,
int staffCount,
com.audiveris.proxymusic.ScorePart scorePart)
{
super(candidate);
this.staffCount = staffCount;
this.scorePart = scorePart;
}
//~ Methods ------------------------------------------------------------
@Override
public String getAbbreviation ()
{
PartName partName = scorePart.getPartAbbreviation();
if (partName != null) {
return partName.getValue();
} else {
return null;
}
}
@Override
public int getId ()
{
return id;
}
@Override
public String getName ()
{
PartName partName = scorePart.getPartName();
if (partName != null) {
return partName.getValue();
} else {
return null;
}
}
@Override
public int getStaffCount ()
{
return staffCount;
}
@Override
public com.audiveris.proxymusic.ScorePart getUnderlyingObject ()
{
return scorePart;
}
@Override
public void setAbbreviation (String abbreviation)
{
com.audiveris.proxymusic.ObjectFactory factory = new com.audiveris.proxymusic.ObjectFactory();
PartName partName = factory.createPartName();
scorePart.setPartAbbreviation(partName);
partName.setValue(abbreviation);
}
@Override
public void setId (int id)
{
this.id = id;
scorePart.setId("P" + id);
}
@Override
public void setName (String name)
{
com.audiveris.proxymusic.ObjectFactory factory = new com.audiveris.proxymusic.ObjectFactory();
com.audiveris.proxymusic.PartName partName = factory.createPartName();
scorePart.setPartName(partName);
partName.setValue(name);
}
}
//--------------------//
// ScorePartCandidate //
//--------------------//
/**
* Wrapping class meant for a ScorePart instance candidate
*/
private static class ScorePartCandidate
implements Candidate
{
//~ Instance fields ----------------------------------------------------
private final ScorePart scorePart;
private final Page page;
//~ Constructors -------------------------------------------------------
public ScorePartCandidate (ScorePart scorePart,
Page page)
{
this.scorePart = scorePart;
this.page = page;
}
//~ Methods ------------------------------------------------------------
@Override
public ScorePartResult createResult ()
{
// Create a brand new score part for this candidate
// Id is irrelevant for the time being
ScorePartResult result = new ScorePartResult(
this,
new ScorePart(0, getStaffCount()));
result.setName(getName());
result.setAbbreviation(getAbbreviation());
logger.debug("Created {} from {}", result, this);
return result;
}
@Override
public String getAbbreviation ()
{
return scorePart.getAbbreviation();
}
@Override
public int getInputIndex ()
{
return page.getIndex();
}
@Override
public String getName ()
{
return scorePart.getName();
}
@Override
public int getStaffCount ()
{
return scorePart.getStaffCount();
}
@Override
public Object getUnderlyingObject ()
{
return scorePart;
}
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder("{");
sb.append(getClass().getSimpleName());
sb.append(" page#").append(getInputIndex());
sb.append(" \"").append(getName()).append("\"");
sb.append(" staffCount:").append(getStaffCount());
sb.append("}");
return sb.toString();
}
}
//-----------------//
// ScorePartResult //
//-----------------//
/**
* Wrapping class meant for a ScorePart instance result
*/
private static class ScorePartResult
extends AbstractResult
{
//~ Instance fields ----------------------------------------------------
private final ScorePart scorePart;
//~ Constructors -------------------------------------------------------
public ScorePartResult (Candidate candidate,
ScorePart scorePart)
{
super(candidate);
this.scorePart = scorePart;
}
//~ Methods ------------------------------------------------------------
@Override
public String getAbbreviation ()
{
return scorePart.getAbbreviation();
}
@Override
public int getId ()
{
return scorePart.getId();
}
@Override
public String getName ()
{
return scorePart.getName();
}
@Override
public int getStaffCount ()
{
return scorePart.getStaffCount();
}
@Override
public ScorePart getUnderlyingObject ()
{
return scorePart;
}
@Override
public void setAbbreviation (String abbreviation)
{
scorePart.setAbbreviation(abbreviation);
}
@Override
public void setId (int id)
{
scorePart.setId(id);
}
@Override
public void setName (String name)
{
scorePart.setName(name);
}
}
//---------------------//
// SystemPartCandidate //
//---------------------//
/**
* Wrapping class meant for a SystemPart instance
*/
private static class SystemPartCandidate
implements Candidate
{
//~ Instance fields ----------------------------------------------------
private final SystemPart systemPart;
//~ Constructors -------------------------------------------------------
public SystemPartCandidate (SystemPart part)
{
this.systemPart = part;
}
//~ Methods ------------------------------------------------------------
@Override
public ScorePartResult createResult ()
{
// Create a brand new score part for this candidate
// Id is irrelevant for the time being
ScorePartResult result = new ScorePartResult(
this,
new ScorePart(0, getStaffCount()));
result.setName(getName());
result.setAbbreviation(getAbbreviation());
logger.debug("Created {} from {}", result, this);
return result;
}
@Override
public String getAbbreviation ()
{
return null;
}
@Override
public int getInputIndex ()
{
return systemPart.getSystem().getId();
}
@Override
public String getName ()
{
return systemPart.getName();
}
@Override
public int getStaffCount ()
{
return systemPart.getStaves().size();
}
@Override
public Object getUnderlyingObject ()
{
return systemPart;
}
@Override
public String toString ()
{
return "S" + getInputIndex() + "-" + systemPart.toString();
}
}
}