//----------------------------------------------------------------------------//
// //
// K e y S i g n a t u 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.entity;
import omr.constant.Constant;
import omr.constant.ConstantSet;
import omr.glyph.Glyphs;
import omr.glyph.Shape;
import static omr.glyph.Shape.*;
import omr.glyph.ShapeSet;
import omr.glyph.facets.Glyph;
import omr.math.Histogram;
import omr.math.Histogram.PeakEntry;
import omr.run.Orientation;
import static omr.score.entity.Note.Step.*;
import omr.score.visitor.ScoreVisitor;
import omr.sheet.Scale;
import omr.sheet.SystemInfo;
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.List;
/**
* Class {@code KeySignature} encapsulates a key signature, which may be
* composed of one or several glyphs (all sharp-based or all flat-based).
*
* @author Hervé Bitteur
*/
public class KeySignature
extends MeasureNode
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(KeySignature.class);
/** Standard (in G clef) pitch position for the members of the sharp keys */
private static final double[] sharpItemPositions = new double[]{
-4, // F - Fa
-1, // C - Do
-5, // G - Sol
-2, // D - Ré
+1, // A - La
-3, // E - Mi
0 // B - Si
};
/** Standard (in G clef) pitch position of any sharp key signature */
private static final double[] sharpKeyPositions = new double[1 + 7];
static {
for (int k = 0; k < sharpKeyPositions.length; k++) {
sharpKeyPositions[k] = getStandardPosition(k);
}
}
/** Note steps according to sharp key */
private static final Note.Step[] sharpSteps = new Note.Step[]{
F, C, G, D, A, E, B
};
/** Standard(in G clef) pitch position for the members of the flat keys */
private static final double[] flatItemPositions = new double[]{
0, // B - Si
-3, // E - Mi
+1, // A - La
-2, // D - Ré
+2, // G - Sol
-1, // C - Do
+3 // F - Fa
};
/** Standard (in G clef) pitch position of any flat key signature */
private static final double[] flatKeyPositions = new double[1 + 7];
static {
for (int k = 0; k < flatKeyPositions.length; k++) {
flatKeyPositions[k] = getStandardPosition(-k);
}
}
/** Note steps according to flat key */
private static final Note.Step[] flatSteps = new Note.Step[]{
B, E, A, D, G, C, F
};
//~ Instance fields --------------------------------------------------------
/** Precise key signature. 0 for none, +n for n sharps, -n for n flats */
private Integer key;
/** Global pitch position */
private Double pitchPosition;
/** Related shape for drawing */
private Shape shape;
/** Center of mass of the key sig */
private Point centroid;
/** Related Clef kind (G, F or C) */
private Shape clefKind;
/** Sequence of items abscissa references */
private List<Integer> refList;
//~ Constructors -----------------------------------------------------------
//--------------//
// KeySignature //
//--------------//
/**
* Create a key signature, with containing measure
*
* @param measure the containing measure
* @param staff the related staff
*/
public KeySignature (Measure measure,
Staff staff)
{
super(measure);
setStaff(staff);
logger.debug("{} KeySignature created: {}",
getContextString(), this);
}
//--------------//
// KeySignature //
//--------------//
/**
* Create a key signature, with containing measure, by cloning
* another one
*
* @param measure the containing measure
* @param staff the related staff
* @param other the key sig to clone
*/
public KeySignature (Measure measure,
Staff staff,
KeySignature other)
{
super(measure);
setStaff(staff);
key = other.getKey();
pitchPosition = other.getPitchPosition();
shape = other.getShape();
clefKind = other.clefKind;
// Nota: Center.y is irrelevant
setCenter(new Point(other.getCenter().x, 0));
logger.debug("{} KeySignature cloned: {}",
getContextString(), this);
}
//~ Methods ----------------------------------------------------------------
//--------//
// accept //
//--------//
@Override
public boolean accept (ScoreVisitor visitor)
{
return visitor.visit(this);
}
//-----------------//
// createDummyCopy //
//-----------------//
public KeySignature createDummyCopy (Measure measure,
Point center)
{
KeySignature dummy = new KeySignature(measure, null);
dummy.key = this.key;
dummy.setCenter(center);
return dummy;
}
//-------------//
// getClefKind //
//-------------//
/**
* Report the current clef kind, if known
*
* @return the current clef kind, or null
*/
public Shape getClefKind ()
{
if (clefKind == null) {
// First is there a clef right before, within the same measure?
Clef clef = getMeasure().getMeasureClefBefore(getCenter(), null);
if (clef != null) {
return clefKind = getClefKind(clef.getShape());
}
// Second, guess the clef based on key position
clefKind = guessClefKind();
}
return clefKind;
}
//----------------------//
// getItemPixelAbscissa //
//----------------------//
/**
* Report the actual reference abscissa for the nth item.
* The reference is the left edge of stick for a flat item and the center
* of the two sticks for a sharp item.
*
* @param n the signed index (one-based) of the desired item
* @return the absolute pixel abscissa of the item reference, or null if
* not available
*/
public Integer getItemPixelAbscissa (int n)
{
try {
return getRefSequence().get(Math.abs(n) - 1);
} catch (Exception ex) {
addError("KeySignature with no items references");
return null;
}
}
//-----------------//
// getItemPosition //
//-----------------//
/**
* Report the pitch position of the nth item, within the given clef.
* 'n' is negative for flats and positive for sharps, and start at 1 for
* sharps (and at -1 for flats)
*
* @param n the signed index (one-based) of the desired item
* @param clefKind the kind (G_CLEF, F_CLEF or C_CLEF) of the active clef
* @return the pitch position of the item (sharp or flat)
*/
public static int getItemPosition (int n,
Shape clefKind)
{
if (clefKind == null) {
clefKind = G_CLEF;
}
int stdPitch = (int) Math.rint(
(n >= 0) ? sharpItemPositions[n - 1] : flatItemPositions[-n - 1]);
return stdPitch + clefToDelta(clefKind);
}
//---------------------//
// getStandardPosition //
//---------------------//
/**
* Compute the standard mean pitch position of the provided key
*
* @param k the provided key value
* @return the corresponding standard mean pitch position
*/
public static double getStandardPosition (int k)
{
if (k == 0) {
return 0;
}
double sum = 0;
if (k > 0) {
for (int i = 0; i < k; i++) {
sum += sharpItemPositions[i];
}
} else {
for (int i = 0; i > k; i--) {
sum -= flatItemPositions[-i];
}
}
return sum / k;
}
//----------//
// populate //
//----------//
/**
* Populate the score with a key signature built from the provided
* glyph
*
* @param glyph the source glyph
* @param measure containing measure
* @param staff related staff
* @param center glyph center wrt system
*
* @return true if population is successful, false otherwise
*/
public static boolean populate (Glyph glyph,
Measure measure,
Staff staff,
Point center)
{
logger.debug("Populating keysig for {}", glyph);
ScoreSystem system = measure.getSystem();
SystemInfo systemInfo = system.getInfo();
// Make sure the glyph pitch position is within the bounds
double pitchPosition = staff.pitchPositionOf(center);
if ((pitchPosition < -5) || (pitchPosition > 5)) {
logger.debug("Glyph not within vertical bounds");
return false;
}
// Make sure we have no note nearby
// Use a enlarged rectangular box around the glyph, and check what's in
// Check for lack of stem symbols (beam, beam hook, note head, flags),
// or stand-alone note (THIS IS TOO RESTRICTIVE!!!)
Rectangle glyphFatBox = glyph.getBounds();
glyphFatBox.grow(
measure.getScale().toPixels(constants.xMargin),
measure.getScale().toPixels(constants.yMargin));
List<Glyph> neighbors = systemInfo.lookupIntersectedGlyphs(
glyphFatBox,
glyph);
for (Glyph g : neighbors) {
Shape shape = g.getShape();
if (ShapeSet.StemSymbols.contains(shape)
|| ShapeSet.Notes.getShapes().contains(shape)) {
logger.debug("Cannot accept {} as neighbor", shape);
return false;
}
}
// Do we have a key signature just before in the same measure & staff?
KeySignature keysig = null;
boolean found = false;
for (TreeNode node : measure.getKeySignatures()) {
keysig = (KeySignature) node;
if (keysig.getCenter().x > center.x) {
break;
}
// Check distance
if (!glyphFatBox.intersects(keysig.getBox())) {
logger.debug("Glyph {} too far from {}", glyph.getId(), keysig);
continue;
} else if (((glyph.getShape().isSharpBased())
&& (keysig.getKey() < 0))
|| ((glyph.getShape().isFlatBased())
&& (keysig.getKey() > 0))) {
// Check sharp or flat key sig, wrt current glyph
logger.debug(
"Cannot extend opposite key signature with glyph {}",
glyph.getId());
return false;
} else {
// Everything is OK
found = true;
logger.debug("Extending {}", keysig);
break;
}
}
// If not found create a brand new one
if (!found) {
// Check pitch position
Clef clef = measure.getClefBefore(
measure.computeGlyphCenter(glyph),
staff);
if (!checkPitchPosition(glyph, center, staff, clef)) {
logger.debug("Cannot start a new key signature with glyph {}",
glyph.getId());
return false;
}
keysig = new KeySignature(measure, staff);
}
// Extend the keysig with this glyph
keysig.addGlyph(glyph);
keysig.getKey();
glyph.setTranslation(keysig);
logger.debug("OK: {}", keysig);
return true;
}
//-------------//
// getAlterFor //
//-------------//
public int getAlterFor (Note.Step step)
{
getKey();
if (key > 0) {
for (int k = 0; k < key; k++) {
if (step == sharpSteps[k]) {
return 1;
}
}
} else {
for (int k = 0; k < -key; k++) {
if (step == flatSteps[k]) {
return -1;
}
}
}
return 0;
}
//--------//
// getKey //
//--------//
/**
* Report the key signature
*
* @return the (lazily determined) key
*/
public Integer getKey ()
{
if (key == null) {
retrieveKey();
}
return key;
}
//------------------//
// getPitchPosition //
//------------------//
/**
* Report the pitch position for the global key symbol
*
* @return the pitch position of the signature symbol
*/
public Double getPitchPosition ()
{
if (pitchPosition == null) {
clefKind = getClefKind();
if (clefKind == null) {
clefKind = G_CLEF;
}
pitchPosition = getStandardPosition(getKey())
+ clefToDelta(clefKind);
}
return pitchPosition;
}
//----------------//
// getRefSequence //
//----------------//
/**
* Report the sequence of reference abscissae for signature items
*
* @return the list of (pixel) abscissa references
*/
public List<Integer> getRefSequence ()
{
if (refList == null) {
List<Integer> refs = new ArrayList<>();
Histogram<Integer> histo = getPage().getSheet().getNest().
getHistogram(
Orientation.VERTICAL,
getGlyphs());
if (logger.isDebugEnabled()) {
histo.print(System.out);
}
List<PeakEntry<Integer>> peaks = histo.getPeaks(
(int) Math.rint(
histo.getMaxCount() * constants.heightRatio.getValue()),
true,
false);
if (logger.isDebugEnabled()) {
for (PeakEntry<Integer> peak : peaks) {
logger.debug(peak.toString());
}
}
key = getKey();
if (key > 0) {
// Sharps : use center of the two vertical sticks
if (peaks.size() == (2 * key)) {
for (int i = 0; i < key; i++) {
PeakEntry<Integer> left = peaks.get(2 * i);
PeakEntry<Integer> right = peaks.get((2 * i) + 1);
refs.add(
(int) Math.rint(
(left.getKey().first + left.getKey().second
+ right.getKey().first
+ right.getKey().second) / 4d));
}
}
} else {
// Flats : use vertical stick on left
if (peaks.size() == -key) {
for (PeakEntry<Integer> peak : peaks) {
refs.add(peak.getKey().first);
}
}
}
refList = refs;
}
return refList;
}
//----------//
// getShape //
//----------//
/**
* Report the related symbol
*
* @return related symbol
*/
public Shape getShape ()
{
if (shape == null) {
switch (getKey()) {
case -1:
return KEY_FLAT_1;
case -2:
return KEY_FLAT_2;
case -3:
return KEY_FLAT_3;
case -4:
return KEY_FLAT_4;
case -5:
return KEY_FLAT_5;
case -6:
return KEY_FLAT_6;
case -7:
return KEY_FLAT_7;
case 1:
return KEY_SHARP_1;
case 2:
return KEY_SHARP_2;
case 3:
return KEY_SHARP_3;
case 4:
return KEY_SHARP_4;
case 5:
return KEY_SHARP_5;
case 6:
return KEY_SHARP_6;
case 7:
return KEY_SHARP_7;
default:
return null;
}
}
return shape;
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder();
sb.append("{KeySignature");
try {
sb.append(" key=").append(key);
sb.append(" center=").append(getCenter());
sb.append(" box=").append(getBox());
sb.append(" pitch=").append(getPitchPosition());
sb.append(" ").append(Glyphs.toString(glyphs));
} catch (Exception e) {
sb.append("INVALID");
}
sb.append("}");
return sb.toString();
}
//-------//
// reset //
//-------//
/**
* Invalidate cached data, so that it gets lazily recomputed when
* needed.
*/
@Override
protected void reset ()
{
super.reset();
setCenter(null);
key = null;
pitchPosition = null;
shape = null;
centroid = null;
clefKind = null;
refList = null;
}
//--------------------//
// checkPitchPosition //
//--------------------//
/**
* Check that the glyph is at the correct pitch position, knowing
* its index in the signature, its shape (sharp or flat) and the
* current clef kind at this location
*
* @param glyph the glyph to check
* @param center the (flat-corrected) glyph center
* @param the containing staff
* @param clef clef at this location, if known
* @return true if OK, false otherwise
*/
private static boolean checkPitchPosition (Glyph glyph,
Point center,
Staff staff,
Clef clef)
{
Shape glyphShape = glyph.getShape();
if (glyphShape == SHARP) {
return checkPosition(
glyph,
center,
staff,
sharpItemPositions,
0,
clef);
} else if (glyphShape.isSharpBased()) {
return checkPosition(
glyph,
center,
staff,
sharpKeyPositions,
keyOf(glyphShape),
clef);
}
if (glyphShape == FLAT) {
return checkPosition(
glyph,
center,
staff,
flatItemPositions,
0,
clef);
} else if (glyphShape.isFlatBased()) {
return checkPosition(
glyph,
center,
staff,
flatKeyPositions,
-keyOf(glyphShape),
clef);
}
return false;
}
//---------------//
// checkPosition //
//---------------//
/**
* Check the pitch position of the glyph, knowing the theoretical
* positions based on glyph shape
*
* @param glyph the glyph to check
* @param center the (flat-corrected) glyph center
* @param the containing staff
* @param positions the array of positions (for G clef kind)
* @param index index of glyph within signature
* @param clef current clef, if known
* @return true if OK, false otherwise
*/
private static boolean checkPosition (Glyph glyph,
Point center,
Staff staff,
double[] positions,
int index,
Clef clef)
{
int[] deltas = (clef != null)
? new int[]{clefToDelta(clef.getShape())}
: new int[]{0, 2, 1};
for (int delta : deltas) {
double dif = staff.pitchPositionOf(center) - positions[index]
- delta;
if (Math.abs(dif) <= (constants.keyYMargin.getValue() * 2)) {
logger.debug("Correct pitch position for glyph {}",
glyph.getId());
return true;
}
}
logger.debug("No valid pitch position for glyph {}", glyph.getId());
return false;
}
//-------------//
// clefToDelta //
//-------------//
/**
* Report the delta in pitch position (wrt standard G_CLEF positions)
* according to a given clef
*
* @param clef the clef
* @return the delta in pitch position
*/
private static int clefToDelta (Shape clef)
{
switch (getClefKind(clef)) {
case F_CLEF:
return 2;
case C_CLEF:
return 1;
default:
case G_CLEF:
return 0;
}
}
//-------------//
// deltaToClef //
//-------------//
/**
* Determine clef kind, based on delta pitch position
*
* @param delta the delta in pitch position between the actual glyphs
* position and the theoretical position based on key
* @return the kind of clef
*/
private Shape deltaToClef (int delta)
{
switch (delta) {
case 0:
return G_CLEF;
case 1:
return C_CLEF;
case 2:
return F_CLEF;
default:
return null;
}
}
//-------------//
// getCentroid //
//-------------//
/**
* Report the actual center of mass of the glyphs that compose the
* signature
*
* @return the Point that represent the center of mass
*/
private Point getCentroid ()
{
if (centroid == null) {
centroid = new Point();
double totalWeight = 0;
for (Glyph glyph : glyphs) {
Point c = glyph.getCentroid();
double w = glyph.getWeight();
centroid.x += (c.x * w);
centroid.y += (c.y * w);
totalWeight += w;
}
centroid.x /= totalWeight;
centroid.y /= totalWeight;
}
return centroid;
}
//-------------//
// getClefKind //
//-------------//
/**
* Classify clefs by clef kinds, since for example the same key is
* represented with identical pitch positions for G_CLEF,
* G_CLEF_8VA and G_CLEF_8VB.
*
* @param shape the precise clef shape
* @return the clef kind
*/
private static Shape getClefKind (Shape shape)
{
switch (shape) {
case G_CLEF:
case G_CLEF_SMALL:
case G_CLEF_8VA:
case G_CLEF_8VB:
return G_CLEF;
case F_CLEF:
case F_CLEF_SMALL:
case F_CLEF_8VA:
case F_CLEF_8VB:
return F_CLEF;
case C_CLEF:
return C_CLEF;
case PERCUSSION_CLEF:
return PERCUSSION_CLEF;
default:
logger.error("No base kind defined for clef {}", shape);
return null;
}
}
//---------------//
// guessClefKind //
//---------------//
/**
* Guess what the current clef kind (G, F or C) is, based on the
* pitch positions of the member glyphs
*
* @return the kind of clef
*/
private Shape guessClefKind ()
{
double theoPos = getStandardPosition(getKey());
double realPos = getStaff().pitchPositionOf(getCentroid());
// Correction for flats
if (glyphs.first().getShape() == Shape.FLAT) {
realPos += 0.75;
}
int delta = (int) Math.rint(realPos - theoPos);
logger.debug("theoPos={} realPos={} delta={}",
theoPos, realPos, delta);
Shape kind = deltaToClef(delta);
if (kind == null) {
logger.debug("Cannot guess Clef from Key signature");
}
return kind;
}
//-------//
// keyOf //
//-------//
private static Integer keyOf (Shape shape)
{
switch (shape) {
case KEY_FLAT_1:
return -1;
case KEY_FLAT_2:
return -2;
case KEY_FLAT_3:
return -3;
case KEY_FLAT_4:
return -4;
case KEY_FLAT_5:
return -5;
case KEY_FLAT_6:
return -6;
case KEY_FLAT_7:
return -7;
case KEY_SHARP_1:
return 1;
case KEY_SHARP_2:
return 2;
case KEY_SHARP_3:
return 3;
case KEY_SHARP_4:
return 4;
case KEY_SHARP_5:
return 5;
case KEY_SHARP_6:
return 6;
case KEY_SHARP_7:
return 7;
default:
return null;
}
}
//-------------//
// retrieveKey //
//-------------//
/**
* Compute the key of this signature, based on the member glyphs
* (shape and number).
*/
private void retrieveKey ()
{
if ((glyphs != null) && !glyphs.isEmpty()) {
// Check we have only sharps or only flats
Shape kind = null;
int k = 0;
for (Glyph glyph : glyphs) {
Shape glyphShape = glyph.getShape();
if (glyphShape.isFlatBased()) {
if (kind == SHARP) {
logger.debug("Inconsistent key signature {}", this);
return;
} else {
kind = FLAT;
}
}
if (glyphShape.isSharpBased()) {
if (kind == FLAT) {
logger.debug("Inconsistent key signature {}", this);
return;
} else {
kind = SHARP;
}
}
// Update key value
if (glyphShape == SHARP) {
k += 1;
} else if (glyphShape == FLAT) {
k -= 1;
} else {
k += keyOf(glyphShape);
}
}
key = k;
} else {
addError("Empty key signature");
}
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction xMargin = new Scale.Fraction(
1d,
"Abscissa margin when looking up for glyph neighbors");
Scale.Fraction yMargin = new Scale.Fraction(
1d,
"Ordinate margin when looking up for glyph neighbors");
Scale.Fraction keyYMargin = new Scale.Fraction(
0.25d,
"Margin when checking vertical position of single-glyph key");
Constant.Ratio heightRatio = new Constant.Ratio(
0.5d,
"Histogram ratio for detection of sharp/flat sticks ");
}
}