//----------------------------------------------------------------------------//
// //
// S y m b o l s M o d e l //
// //
//----------------------------------------------------------------------------//
// <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.glyph;
import omr.glyph.facets.Glyph;
import omr.score.entity.TimeRational;
import omr.sheet.Sheet;
import omr.sheet.SystemInfo;
import omr.step.Steps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import omr.text.TextBuilder;
import omr.text.TextLine;
import omr.text.TextRole;
import omr.text.TextRoleInfo;
import omr.text.TextWord;
/**
* Class {@code SymbolsModel} is a GlyphsModel specifically meant for
* symbol glyphs.
*
* @author Hervé Bitteur
*/
public class SymbolsModel
extends GlyphsModel
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(SymbolsModel.class);
//~ Instance fields --------------------------------------------------------
/** Standard evaluator */
private ShapeEvaluator evaluator = GlyphNetwork.getInstance();
//~ Constructors -----------------------------------------------------------
//--------------//
// SymbolsModel //
//--------------//
/**
* Creates a new SymbolsModel object.
*
* @param sheet the related sheet
*/
public SymbolsModel (Sheet sheet)
{
super(sheet, sheet.getNest(), Steps.valueOf(Steps.SYMBOLS));
}
//~ Methods ----------------------------------------------------------------
//------------//
// assignText //
//------------//
/**
* Assign a collection of glyphs as textual element
*
* @param glyphs the collection of glyphs
* @param roleInfo the text role
* @param textContent the ascii content
* @param grade the grade wrt this assignment
*/
public void assignText (Collection<Glyph> glyphs,
TextRoleInfo roleInfo,
String textContent,
double grade)
{
// Do the job
for (Glyph glyph : glyphs) {
SystemInfo system = glyph.getSystem();
String language = system.getSheet().getPage().getTextParam().getTarget();
TextBuilder textBuilder = system.getTextBuilder();
TextWord word = glyph.getTextWord();
List<TextLine> lines = new ArrayList<>();
if (word == null) {
word = TextWord.createManualWord(glyph, textContent);
glyph.setTextWord(language, word);
TextLine line = new TextLine(system, Arrays.asList(word));
lines = Arrays.asList(line);
lines = textBuilder.recomposeLines(lines);
system.getSentences().remove(line);
system.getSentences().addAll(lines);
} else if (word.getTextLine() != null) {
lines = Arrays.asList(word.getTextLine());
}
// Force text role
glyph.setManualRole(roleInfo);
for (TextLine line : lines) {
// For Chord role, we don't spread the role to other words
// but rather trigger a line split
if (roleInfo.role == TextRole.Chord
&& line.getWords().size() > 1) {
line.setRole(roleInfo);
List<TextLine> subLines = textBuilder.recomposeLines(
Arrays.asList(line));
system.getSentences().remove(line);
for (TextLine l : subLines) {
if (!l.getWords().contains(word)) {
l.setRole(null);
}
}
system.getSentences().addAll(subLines);
} else {
line.setRole(roleInfo);
}
}
// Force text only if it is not empty
if ((textContent != null) && (textContent.length() > 0)) {
glyph.setManualValue(textContent);
}
}
}
//--------------------//
// assignTimeRational //
//--------------------//
/**
* Assign a time rational value to collection of glyphs
*
* @param glyphs the collection of glyphs
* @param timeRational the time rational value
* @param grade the grade wrt this assignment
*/
public void assignTimeRational (Collection<Glyph> glyphs,
TimeRational timeRational,
double grade)
{
// Do the job
for (Glyph glyph : glyphs) {
glyph.setTimeRational(timeRational);
}
}
//-------------//
// cancelStems //
//-------------//
/**
* Cancel one or several stems, turning them back to just a set of sections,
* and rebuilding glyphs from their member sections together with the
* neighbouring non-assigned sections
*
* @param stems a list of stems
*/
public void cancelStems (List<Glyph> stems)
{
/**
* To remove a stem, several infos need to be modified : shape from
* STEM to null, result from STEM to null, and the Stem must
* be removed from system list of stems.
*
* The stem glyph must be removed (as well as all other non-recognized
* glyphs that are connected to the former stem)
*
* Then, re-glyph extraction from sections when everything is ready
* (GlyphBuilder). Should work on a micro scale : just the former stem
* and the neighboring (non-assigned) glyphs.
*/
Set<SystemInfo> impactedSystems = new HashSet<>();
for (Glyph stem : stems) {
SystemInfo system = sheet.getSystemOf(stem);
system.removeGlyph(stem);
super.deassignGlyph(stem);
impactedSystems.add(system);
}
// Extract brand new glyphs from impactedSystems
for (SystemInfo system : impactedSystems) {
system.extractNewGlyphs();
}
}
//---------------//
// deassignGlyph //
//---------------//
/**
* Deassign the shape of a glyph.
* This overrides the basic deassignment, in order to delegate the handling
* of some specific shapes.
*
* @param glyph the glyph to deassign
*/
@Override
public void deassignGlyph (Glyph glyph)
{
// Safer
if (glyph.getShape() == null) {
return;
}
// Processing depends on shape at hand
switch (glyph.getShape()) {
case STEM:
logger.debug("Deassigning a Stem as glyph {}", glyph.getId());
cancelStems(Collections.singletonList(glyph));
break;
case NOISE:
logger.info("Skipping Noise as glyph {}", glyph.getId());
break;
default:
super.deassignGlyph(glyph);
break;
}
}
//---------------//
// segmentGlyphs //
//---------------//
public void segmentGlyphs (Collection<Glyph> glyphs,
boolean isShort)
{
deassignGlyphs(glyphs);
for (Glyph glyph : new ArrayList<>(glyphs)) {
SystemInfo system = sheet.getSystemOf(glyph);
system.segmentGlyphOnStems(glyph, isShort);
}
}
//-----------//
// trimSlurs //
//-----------//
public void trimSlurs (Collection<Glyph> glyphs)
{
List<Glyph> slurs = new ArrayList<>();
for (Glyph glyph : new ArrayList<>(glyphs)) {
SystemInfo system = sheet.getSystemOf(glyph);
Glyph slur = system.trimSlur(glyph);
if (slur != null) {
slurs.add(slur);
}
}
if (!slurs.isEmpty()) {
assignGlyphs(slurs, Shape.SLUR, false, Evaluation.MANUAL);
}
}
//-------------//
// assignGlyph //
//-------------//
/**
* Assign a Shape to a glyph, inserting the glyph to its containing
* system and lag if it is still transient
*
* @param glyph the glyph to be assigned
* @param shape the assigned shape, which may be null
* @param grade the grade about shape
*/
@Override
protected Glyph assignGlyph (Glyph glyph,
Shape shape,
double grade)
{
if (glyph == null) {
return null;
}
// Test on glyph weight (noise-like)
// To prevent to assign a non-noise shape to a noise glyph
if ((shape == Shape.NOISE) || evaluator.isBigEnough(glyph)) {
// Force a recomputation of glyph parameters
// (since environment may have changed since the time they
// have been computed)
SystemInfo system = sheet.getSystemOf(glyph);
if (system != null) {
system.computeGlyphFeatures(glyph);
return super.assignGlyph(glyph, shape, grade);
}
}
return glyph;
}
}