//----------------------------------------------------------------------------//
// //
// D o t T r a n s l a 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.entity;
import omr.constant.ConstantSet;
import omr.glyph.Shape;
import static omr.glyph.Shape.*;
import omr.glyph.facets.Glyph;
import omr.sheet.Scale;
import omr.util.TreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Class {@code DotTranslation} is a set of functions forassigning a dot
* glyph, since a dot can be an augmentation dot, part of a repeat sign,
* a staccato sign.
*
* @author Hervé Bitteur
*/
public class DotTranslation
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(DotTranslation.class);
/** Sequence of dot trials */
private static final List<? extends Trial> trials = Arrays.asList(
new StaccatoTrial(),
new RepeatTrial(),
new AugmentationTrial());
//~ Constructors -----------------------------------------------------------
//
//----------------//
// DotTranslation //
//----------------//
private DotTranslation ()
{
}
//~ Methods ----------------------------------------------------------------
//
//-------------//
// populateDot //
//-------------//
/**
* Try to find the best assignment for a dot (variant) glyph.
*
* @param glyph the glyph of dot
* @param measure the containing measure
* @param dotCenter the location of the dot
*/
public static void populateDot (Glyph glyph,
Measure measure,
Point dotCenter)
{
logger.debug("{} populateDot {}",
measure.getContextString(), glyph);
// Keep specific shape only if manually assigned
if (!glyph.isManualShape()) {
glyph.setShape(DOT_set);
}
Shape shape = glyph.getShape();
/** To remember results of trials */
SortedSet<Trial.Result> results = new TreeSet<>();
// Try the various possibilities
for (Trial trial : trials) {
if ((shape == DOT_set) || (shape == trial.targetShape)) {
Trial.Result result = trial.process(glyph, measure, dotCenter);
if (result != null) {
results.add(result);
}
}
}
// Debug
if (logger.isDebugEnabled()) {
for (Trial.Result info : results) {
logger.debug(info.toString());
}
}
// Choose best result, if any
if (!results.isEmpty()) {
Trial.Result result = results.first();
Shape targetShape = result.getTargetShape();
// Assign proper glyph shape (and thus color)
if (glyph.getShape() != targetShape) {
glyph.setShape(targetShape);
}
// Assign proper translation
result.commit(glyph, measure, dotCenter);
} else {
measure.addError(glyph, "Dot unassigned");
}
}
//~ Inner Classes ----------------------------------------------------------
//-------------------//
// AugmentationTrial //
//-------------------//
/**
* Try to assign a dot as a chord augmentation dot
*/
private static class AugmentationTrial
extends Trial
{
//~ Constructors -------------------------------------------------------
public AugmentationTrial ()
{
super(AUGMENTATION_DOT);
}
//~ Methods ------------------------------------------------------------
@Override
Result process (Glyph glyph,
Measure measure,
Point dotCenter)
{
Scale scale = measure.getScale();
final int maxDx = scale.toPixels(
constants.maxAugmentationDotDx);
final int maxDy = scale.toPixels(
constants.maxAugmentationDotDy);
SortedMap<Double, Note> distances = new TreeMap<>();
// Check for a note/rest nearby:
// - on the left w/ same even pitch (note w/ even pitch)
// - slighly above or below (note with odd pitch = on a staff line)
ChordLoop:
for (TreeNode node : measure.getChords()) {
Chord chord = (Chord) node;
for (TreeNode n : chord.getNotes()) {
Note note = (Note) n;
if (!note.getShape().isMeasureRest()) {
Point noteRef = note.getCenterRight();
Point toDot = new Point(
dotCenter.x - noteRef.x,
dotCenter.y - noteRef.y);
logger.debug("Augmentation {} {}", toDot, note);
if (((glyph.getShape() == getTargetShape())
&& glyph.isManualShape())
|| ((toDot.x > 0) && (toDot.x <= maxDx)
&& (Math.abs(toDot.y) <= maxDy))) {
distances.put(toDot.distanceSq(0, 0), note);
} else if (toDot.x < (-2 * maxDx)) {
break ChordLoop; // Speed up
}
}
}
}
if (!distances.isEmpty()) {
Double firstKey = distances.firstKey();
Note note = distances.get(firstKey);
// Beware of mirrored notes
// Choose the one with longest duration
Note mirror = note.getMirroredNote();
if ((mirror == null)
|| (note.getChord().getDuration().compareTo(mirror.
getChord().getDuration()) > 0)) {
return new AugmentationResult(note, firstKey);
} else {
return new AugmentationResult(mirror, firstKey);
}
} else {
return null;
}
}
//~ Inner Classes ------------------------------------------------------
public class AugmentationResult
extends Result
{
//~ Instance fields ------------------------------------------------
final Note note;
//~ Constructors ---------------------------------------------------
public AugmentationResult (Note note,
double dist)
{
super(dist);
this.note = note;
}
//~ Methods --------------------------------------------------------
@Override
public void commit (Glyph glyph,
Measure measure,
Point dotCenter)
{
// Is there a second dot on the right?
Glyph second = secondDot(glyph, measure, dotCenter);
note.setDots(glyph, second);
glyph.setTranslation(note);
note.getChord().setDotsNumber((second != null) ? 2 : 1);
logger.debug("{} dot#{} Augmented {}",
note.getContextString(), glyph.getId(), note);
}
@Override
protected String internals ()
{
return "note:" + note;
}
private Glyph secondDot (Glyph glyph,
Measure measure,
Point dotCenter)
{
Scale scale = measure.getScale();
final int maxDx = scale.toPixels(
constants.maxAugmentationDoubleDotsDx);
final int maxDy = scale.toPixels(
constants.maxAugmentationDoubleDotsDy);
boolean started = false;
// Check for a suitable second dot nearby
for (Glyph g : measure.getSystem().getInfo().getGlyphs()) {
if (g == glyph) {
started = true;
continue;
}
if (!started) {
continue;
}
if (!g.isTranslated()
&& ((g.getShape() == DOT_set)
|| (g.getShape() == AUGMENTATION_DOT))) {
// Check relative position
Point gCenter = g.getLocation();
int dx = gCenter.x - dotCenter.x;
int dy = gCenter.y - dotCenter.y;
if (dx > maxDx) {
return null;
}
if ((dx > 0) && (Math.abs(dy) <= maxDy)) {
logger.debug("Double dot with {}", g);
g.setTranslation(note);
// Assign proper glyph shape (and thus color)
if (g.getShape() != targetShape) {
g.setShape(targetShape);
}
return g;
}
}
}
return null;
}
}
}
//-------------//
// RepeatTrial //
//-------------//
/**
* Try to assign a dot to the relevant repeat barline if any
*/
private static class RepeatTrial
extends Trial
{
//~ Constructors -------------------------------------------------------
public RepeatTrial ()
{
super(REPEAT_DOT);
}
//~ Methods ------------------------------------------------------------
@Override
RepeatResult process (Glyph glyph,
Measure measure,
Point dotCenter)
{
if (glyph.isVip()) {
logger.info("RepeatTrial. process {}", glyph.idString());
}
SortedMap<Double, Barline> distances = new TreeMap<>();
// Check vertical pitch position within the staff: close to +1 or -1
double pitchDif = Math.abs(Math.abs(glyph.getPitchPosition()) - 1);
if (pitchDif > (2 * constants.maxRepeatDotDy.getValue())) {
return null;
}
final Scale scale = measure.getScale();
final int maxDx = scale.toPixels(constants.maxRepeatDotDx);
// Check wrt inside/starting barline on left & ending barline on right
Barline leftBar;
if (measure.getInsideBarline() != null) {
leftBar = measure.getInsideBarline();
} else {
Measure prevMeasure = (Measure) measure.getPreviousSibling();
leftBar = (prevMeasure != null) ? prevMeasure.getBarline()
: measure.getPart().getStartingBarline();
}
Barline rightBar = measure.getBarline();
for (Barline bar : Arrays.asList(leftBar, rightBar)) {
if (bar != null) {
final int dx = (bar == leftBar)
? (dotCenter.x - bar.getRightX())
: (bar.getLeftX() - dotCenter.x);
logger.debug("Repeat dx:{} {}", dx, bar);
if (((glyph.getShape() == getTargetShape())
&& glyph.isManualShape())
|| ((dx > 0) && (dx <= maxDx))) {
distances.put(new Double(dx * dx), bar);
}
}
}
// Take the best, if any
if (!distances.isEmpty()) {
Double firstKey = distances.firstKey();
return new RepeatResult(distances.get(firstKey), firstKey);
} else {
return null;
}
}
//~ Inner Classes ------------------------------------------------------
public class RepeatResult
extends Trial.Result
{
//~ Instance fields ------------------------------------------------
final Barline barline;
//~ Constructors ---------------------------------------------------
public RepeatResult (Barline barline,
double dist)
{
super(dist);
this.barline = barline;
}
//~ Methods --------------------------------------------------------
@Override
public void commit (Glyph glyph,
Measure measure,
Point dotCenter)
{
barline.addGlyph(glyph);
logger.debug("{} dot#{} Repeat dot for {}",
barline.getContextString(), glyph.getId(), barline);
}
@Override
protected String internals ()
{
return "barline:" + barline;
}
}
}
//---------------//
// StaccatoTrial //
//---------------//
/**
* Try to assign a dot as a staccato
*/
private static class StaccatoTrial
extends Trial
{
//~ Constructors -------------------------------------------------------
public StaccatoTrial ()
{
super(STACCATO);
}
//~ Methods ------------------------------------------------------------
@Override
StaccatoResult process (Glyph glyph,
Measure measure,
Point dotCenter)
{
Scale scale = measure.getScale();
final int maxDx = scale.toPixels(
constants.maxStaccatoDotDx);
final int maxDy = scale.toPixels(
constants.maxStaccatoDotDy);
SortedMap<Double, Chord> distances = new TreeMap<>();
ChordLoop:
for (TreeNode node : measure.getChords()) {
Chord chord = (Chord) node;
for (TreeNode n : chord.getNotes()) {
Note note = (Note) n;
if (!note.isRest()) {
// Check distance wrt both top & bottom of note
for (Point noteRef : Arrays.asList(
note.getCenterTop(),
note.getCenterBottom())) {
Point toDot = new Point(
dotCenter.x - noteRef.x,
dotCenter.y - noteRef.y);
logger.debug("Staccato {} {}", toDot, note);
if (((glyph.getShape() == getTargetShape())
&& glyph.isManualShape())
|| ((Math.abs(toDot.x) <= maxDx)
&& (Math.abs(toDot.y) <= maxDy))) {
distances.put(toDot.distanceSq(0, 0), chord);
} else if (toDot.x < (-2 * maxDx)) {
break ChordLoop; // Speed up
}
}
}
}
}
if (!distances.isEmpty()) {
Double firstKey = distances.firstKey();
return new StaccatoResult(distances.get(firstKey), firstKey);
} else {
return null;
}
}
//~ Inner Classes ------------------------------------------------------
private class StaccatoResult
extends Result
{
//~ Instance fields ------------------------------------------------
final Chord chord;
//~ Constructors ---------------------------------------------------
public StaccatoResult (Chord chord,
double dist)
{
super(dist);
this.chord = chord;
}
//~ Methods --------------------------------------------------------
@Override
public void commit (Glyph glyph,
Measure measure,
Point dotCenter)
{
glyph.setTranslation(
new Articulation(measure, dotCenter, chord, glyph));
logger.debug("{} dot#{} Staccato {}",
chord.getContextString(), glyph.getId(), chord);
}
@Override
protected String internals ()
{
return "chord:" + chord;
}
}
}
//-------//
// Trial //
//-------//
private abstract static class Trial
{
//~ Instance fields ----------------------------------------------------
public final Shape targetShape;
//~ Constructors -------------------------------------------------------
public Trial (Shape targetShape)
{
this.targetShape = targetShape;
}
//~ Methods ------------------------------------------------------------
public Shape getTargetShape ()
{
return targetShape;
}
abstract Result process (Glyph glyph,
Measure measure,
Point dotCenter);
//~ Inner Classes ------------------------------------------------------
/**
* Remember information about possible assignment of a dot
*/
public abstract class Result
implements Comparable<Result>
{
//~ Instance fields ------------------------------------------------
/* The measured distance to the related entity */
final double dist;
//~ Constructors ---------------------------------------------------
public Result (double dist)
{
this.dist = dist;
}
//~ Methods --------------------------------------------------------
public abstract void commit (Glyph glyph,
Measure measure,
Point dotCenter);
@Override
public int compareTo (Result other)
{
return Double.compare(this.dist, other.dist);
}
public Shape getTargetShape ()
{
return targetShape;
}
@Override
public String toString ()
{
return "{" + getClass().getSimpleName() + " dist:" + (float) dist
+ " " + internals() + "}";
}
protected String internals ()
{
return "";
}
}
}
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction maxAugmentationDotDx = new Scale.Fraction(
1.7d,
"Maximum dx between note and augmentation dot");
Scale.Fraction maxAugmentationDotDy = new Scale.Fraction(
1d,
"Maximum absolute dy between note and augmentation dot");
Scale.Fraction maxAugmentationDoubleDotsDx = new Scale.Fraction(
1.5d,
"Maximum dx between two augmentation dots");
Scale.Fraction maxAugmentationDoubleDotsDy = new Scale.Fraction(
0.2d,
"Maximum absolute dy between two augmentation dots");
Scale.Fraction maxRepeatDotDy = new Scale.Fraction(
0.5d,
"Margin for vertical position of a dot againt a repeat barline");
Scale.Fraction maxRepeatDotDx = new Scale.Fraction(
1.5d,
"Maximum dx between dot and edge of repeat barline");
Scale.Fraction maxStaccatoDotDy = new Scale.Fraction(
6d,
"Maximum absolute dy between note and staccato dot");
Scale.Fraction maxStaccatoDotDx = new Scale.Fraction(
0.75d,
"Maximum dx between note and staccato dot");
}
}