//----------------------------------------------------------------------------//
// //
// T u p l e 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 java.util.ArrayList;
import omr.glyph.Shape;
import omr.glyph.facets.Glyph;
import omr.math.Rational;
import omr.score.visitor.ScoreVisitor;
import omr.util.TreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.awt.geom.Line2D;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Class {@code Tuplet} represents a tuplet notation and encapsulates
* the translation from tuplet glyph to the impacted chords.
*
* @author Hervé Bitteur
*/
public class Tuplet
extends AbstractNotation
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(Tuplet.class);
//~ Instance fields --------------------------------------------------------
//
/**
* Set of chords involved in the tuplet sequence.
*/
private final SortedSet<Chord> chords;
//~ Constructors -----------------------------------------------------------
//
//--------//
// Tuplet //
//--------//
/**
* Creates a new instance of Tuplet event
*
* @param measure measure that contains this tuplet
* @param point location of tuplet sign
* @param chords the embraced chords
* @param glyph the underlying glyph
*/
private Tuplet (Measure measure,
Point point,
SortedSet<Chord> chords,
Glyph glyph)
{
super(measure, point, chords.first(), glyph);
this.chords = chords;
// Apply the tuplet factor to each chord embraced
DurationFactor factor = getFactor(glyph);
for (Chord chord : chords) {
chord.setTupletFactor(factor);
}
// Link last embraced chord to this tuplet instance
chords.last().addNotation(this);
}
//~ Methods ----------------------------------------------------------------
//
//----------//
// populate //
//----------//
/**
* Used by SystemTranslator to allocate the tuplet instances.
*
* @param glyph underlying glyph
* @param measure containing measure
* @param point location for the sign
*/
public static void populate (Glyph glyph,
Measure measure,
Point point)
{
if (glyph.isVip()) {
logger.info("Tuplet. populate {}", glyph);
}
// Let's gather the set of possible chords, ordered by their distance
// (abscissa-based) to the position of the tuplet sign.
List<Chord> candidates = new ArrayList<>();
for (TreeNode node : measure.getChords()) {
Chord chord = (Chord) node;
if (chord.getReferencePoint() != null) {
// No tuplet on a whole
if (!chord.isWholeDuration()) {
candidates.add(chord);
}
}
}
Collections.sort(candidates, new DxComparator(point));
// Now, get the properly embraced chords
SortedSet<Chord> chords = getEmbracedChords(
glyph,
measure,
point,
candidates,
null);
if (chords != null) {
glyph.setTranslation(
new Tuplet(
measure,
point,
chords,
glyph));
} else {
// Nullify shape unless manual
if (!glyph.isManualShape()) {
glyph.setShape(null);
}
}
}
//--------//
// accept //
//--------//
@Override
public boolean accept (ScoreVisitor visitor)
{
return visitor.visit(this);
}
//---------------------//
// getTranslationLinks //
//---------------------//
@Override
public List<Line2D> getTranslationLinks (Glyph glyph)
{
List<Line2D> links = new ArrayList<>();
for (Chord chord : chords) {
links.addAll(chord.getTranslationLinks(glyph));
}
return links;
}
//-----------------//
// internalsString //
//-----------------//
@Override
protected String internalsString ()
{
return super.internalsString() + " " + chords.last();
}
//---------------//
// expectedCount //
//---------------//
/**
* Report the number of basic items governed by the tuplet.
* A given chord may represent several basic items (chords of base duration)
*
* @param shape the tuplet shape
* @return 3 or 6
*/
private static int expectedCount (Shape shape)
{
switch (shape) {
case TUPLET_THREE:
return 3;
case TUPLET_SIX:
return 6;
default:
logger.error("Incorrect tuplet shape");
return 0;
}
}
//-------------------//
// getEmbracedChords //
//-------------------//
/**
* Report the proper collection of chords that are embraced by the
* tuplet
*
* @param glyph underlying glyph
* @param measure measure where the sign is located
* @param point location for the sign
* @param candidates the chords candidates, ordered wrt distance to sign
* @param requiredStaff the required containing staff if known, or null
* @return the set of embraced chords, ordered from left to right, or null
* when the retrieval has failed
*/
private static SortedSet<Chord> getEmbracedChords (Glyph glyph,
Measure measure,
Point point,
List<Chord> candidates,
Staff requiredStaff)
{
logger.debug("{} {}{}",
glyph.getShape(),
glyph.idString(),
(requiredStaff != null)
? (" staff#" + requiredStaff.getId())
: "");
// We consider each candidate in turn, with its duration
// in order to determine the duration base of the tuplet
TupletCollector collector = new TupletCollector(
glyph,
new TreeSet<Chord>(Chord.byAbscissa));
// Check that all chords are on the same staff
Staff commonStaff = null;
for (Chord chord : candidates) {
Staff staff = chord.getStaff();
// If we have a constraint on the staff, let's use it
if ((requiredStaff != null) && (requiredStaff != staff)) {
continue;
}
if (commonStaff == null) {
commonStaff = staff;
} else if (staff != commonStaff) {
// We have chords in different staves, we must fix that.
// We choose the closest in ordinate of the chords so far
SortedSet<Chord> verticals = new TreeSet<>(
new DyComparator(point));
for (Chord ch : candidates) {
verticals.add(ch);
if (ch == chord) {
break;
}
}
// Now, we can impose the staff!
return getEmbracedChords(
glyph,
measure,
point,
candidates,
verticals.first().getStaff());
}
collector.include(chord);
// Check we have collected the exact amount of time
if (collector.isTooLong()) {
measure.addError(glyph, collector.getStatusMessage());
return null;
} else if (collector.isOutside()) {
measure.addError(glyph, collector.getStatusMessage());
return null;
} else if (collector.isOk()) {
if (logger.isDebugEnabled()) {
collector.dump();
}
// Normal exit
return collector.getChords();
}
}
// Candidates are exhausted, we lack chords
measure.addError(glyph, collector.getStatusMessage());
return null;
}
//-----------//
// getFactor //
//-----------//
/**
* Report the tuplet factor that corresponds to the provided tuplet
* sign
*
* @param glyph the tuplet sign
* @return the related factor
*/
private static DurationFactor getFactor (Glyph glyph)
{
switch (glyph.getShape()) {
case TUPLET_THREE:
return new DurationFactor(2, 3);
case TUPLET_SIX:
return new DurationFactor(4, 6);
default:
logger.error("Incorrect tuplet glyph shape");
return null;
}
}
//~ Inner Classes ----------------------------------------------------------
//
//--------------//
// DxComparator //
//--------------//
private static class DxComparator
implements Comparator<Chord>
{
//~ Instance fields ----------------------------------------------------
/** The location of the tuplet sign */
private final Point signPoint;
//~ Constructors -------------------------------------------------------
public DxComparator (Point signPoint)
{
this.signPoint = signPoint;
}
//~ Methods ------------------------------------------------------------
/** Compare their horizontal distance from the signPoint reference */
@Override
public int compare (Chord c1,
Chord c2)
{
int dx1 = Math.abs(c1.getReferencePoint().x - signPoint.x);
int dx2 = Math.abs(c2.getReferencePoint().x - signPoint.x);
return Integer.signum(dx1 - dx2);
}
}
//--------------//
// DyComparator //
//--------------//
private static class DyComparator
implements Comparator<Chord>
{
//~ Instance fields ----------------------------------------------------
/** The location of the tuplet sign */
private final Point signPoint;
//~ Constructors -------------------------------------------------------
public DyComparator (Point signPoint)
{
this.signPoint = signPoint;
}
//~ Methods ------------------------------------------------------------
/** Compare their vertical distance from the signPoint reference */
@Override
public int compare (Chord c1,
Chord c2)
{
int dy1 = Math.min(
Math.abs(c1.getHeadLocation().y - signPoint.y),
Math.abs(c1.getTailLocation().y - signPoint.y));
int dy2 = Math.min(
Math.abs(c2.getHeadLocation().y - signPoint.y),
Math.abs(c2.getTailLocation().y - signPoint.y));
return Integer.signum(dy1 - dy2);
}
}
//-----------------//
// TupletCollector //
//-----------------//
/**
* In charge of incrementally collecting the chords for a given
* tuplet sign.
*/
private static class TupletCollector
{
//~ Enumerations -------------------------------------------------------
/** Describe the current status of the tuplet collector */
public enum Status
{
//~ Enumeration constant initializers ------------------------------
TOO_SHORT("Too short"),
OK("Correct"),
TOO_LONG("Too long"),
OUTSIDE("Outside chords");
//~ Instance fields ------------------------------------------------
final String label;
//~ Constructors ---------------------------------------------------
private Status (String label)
{
this.label = label;
}
}
//~ Instance fields ----------------------------------------------------
/** Underlying glyph */
private final Glyph glyph;
/** Number of base items expected */
private final int expectedCount;
/** The chords collected so far */
private final SortedSet<Chord> chords;
/** The base duration as identified so far */
private Rational base = Rational.MAX_VALUE;
/** The total duration expected (using the known base) */
private Rational expectedTotal = Rational.MAX_VALUE;
/** The total duration so far */
private Rational total = Rational.ZERO;
/** Current status */
private Status status = Status.TOO_SHORT;
//~ Constructors -------------------------------------------------------
public TupletCollector (Glyph glyph,
SortedSet<Chord> chords)
{
this.glyph = glyph;
expectedCount = expectedCount(glyph.getShape());
this.chords = chords;
}
//~ Methods ------------------------------------------------------------
public void dump ()
{
StringBuilder sb = new StringBuilder();
sb.append(glyph.getShape());
sb.append(" ").append(glyph.idString());
sb.append(" ").append(status);
sb.append(" Base:").append(base);
sb.append(" ExpectedTotal:").append(expectedTotal);
sb.append(" Total:").append(total);
for (Chord chord : chords) {
sb.append("\n").append(chord);
}
logger.debug(sb.toString());
}
public SortedSet<Chord> getChords ()
{
return chords;
}
public String getStatusMessage ()
{
if (logger.isDebugEnabled()) {
dump();
}
StringBuilder sb = new StringBuilder();
sb.append(status.label).append(" sequence in ")
.append(glyph.getShape()).append(": ").append(total);
if (expectedTotal != Rational.MAX_VALUE) {
sb.append(" vs ").append(expectedTotal);
}
return sb.toString();
}
public Rational getTotal ()
{
return total;
}
/** Include a chord into the collection */
public void include (Chord chord)
{
if (chords.add(chord)) {
Rational duration = chord.getRawDuration();
total = total.plus(duration);
// If this is a shorter chord, let's update the base
if (duration.compareTo(base) < 0) {
base = duration;
expectedTotal = base.times(expectedCount);
}
// Update status
if (total.equals(expectedTotal)) {
// Check tuplet sign is within chords abscissae
if (isWithinChords()) {
status = Status.OK;
} else {
status = Status.OUTSIDE;
}
} else if (total.compareTo(expectedTotal) > 0) {
status = Status.TOO_LONG;
}
}
}
/** Include a bunch of chords, all in a row */
public void includeAll (Collection<Chord> newChords)
{
for (Chord chord : newChords) {
include(chord);
}
}
public boolean isOk ()
{
return status == Status.OK;
}
public boolean isOutside ()
{
return status == Status.OUTSIDE;
}
public boolean isTooLong ()
{
return status == Status.TOO_LONG;
}
/** Check whether the tuplet sign lies between the chords abscissae */
private boolean isWithinChords ()
{
int signX = glyph.getAreaCenter().x;
return (signX >= chords.first().getTailLocation().x)
&& (signX <= chords.last().getTailLocation().x);
}
}
}