diff --git a/build.gradle b/build.gradle
index bdb3ea941..628bdbab4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
apply plugin: 'application'
-project.version = '5.3'
+project.version = '5.3.1'
sourceCompatibility = '1.17'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
@@ -331,3 +331,16 @@ fileTree("$projectDir/private").include('*.gradle').each { file ->
test {
testLogging.showStandardStreams = true
}
+
+// Utility task to print out head templates
+task(printTemplates, dependsOn: 'classes', type: JavaExec) {
+ description = "Print out head templates with various sizes"
+ mainClass = "org.audiveris.omr.image.TemplateFactory"
+ classpath = sourceSets.main.runtimeClasspath
+
+ if (project.hasProperty("cmdLineArgs")) {
+ if (cmdLineArgs) {
+ args(cmdLineArgs.split())
+ }
+ }
+}
diff --git a/docs/_pages/advanced/samples.md b/docs/_pages/advanced/samples.md
index 0f5a5cfe4..d6a40d945 100644
--- a/docs/_pages/advanced/samples.md
+++ b/docs/_pages/advanced/samples.md
@@ -80,7 +80,7 @@ repository or to assign it a correct shape.
To work on the **GLOBAL** repository, we use the `Tools | Browse Global Repository...` pulldown menu.
It will work on the `samples.zip` and `images.zip` files if any are found in our own `config/train`
-folder (see [Folders] section).
+folder (see [Train folder](../folders/essential.md#train-folder) section).
To work on a **book** repository, we use the `Book | View Book Repository...` pulldown menu.
We will be able to pick and merge book repositories into the GLOBAL repository later on,
@@ -141,6 +141,3 @@ When we are satisfied with a book repository we can push its content to the glob
We do so from the book repository interface, by selecting the pulldown menu
`Repository | Push to Global`.
-
-
-[Folders]: /folders/essential.md
diff --git a/docs/_pages/assets/images/merged_grand_staff.png b/docs/_pages/assets/images/merged_grand_staff.png
new file mode 100644
index 000000000..8a8dbea1c
Binary files /dev/null and b/docs/_pages/assets/images/merged_grand_staff.png differ
diff --git a/docs/_pages/handbook.md b/docs/_pages/handbook.md
index 4e85a338d..3ee53507e 100644
--- a/docs/_pages/handbook.md
+++ b/docs/_pages/handbook.md
@@ -93,3 +93,6 @@ Features only relevant for an advanced usage of Audiveris.
1. [References](./references.md):
Not meant to be read from A to Z, but when a specific item needs to be checked.
+
+1. [Updates](./updates.md):
+History of major software updates.
diff --git a/schemas/src/main/java/org/audiveris/schema/MyJavaDocExtractor.java b/schemas/src/main/java/org/audiveris/schema/MyJavaDocExtractor.java
index a25899b63..a009c533b 100644
--- a/schemas/src/main/java/org/audiveris/schema/MyJavaDocExtractor.java
+++ b/schemas/src/main/java/org/audiveris/schema/MyJavaDocExtractor.java
@@ -1,611 +1,638 @@
-//------------------------------------------------------------------------------------------------//
-// //
-// M y J a v a D o c E x t r a c t o r //
-// //
-//------------------------------------------------------------------------------------------------//
-package org.audiveris.schema;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import com.thoughtworks.qdox.JavaProjectBuilder;
-import com.thoughtworks.qdox.model.JavaAnnotatedElement;
-import org.apache.maven.plugin.logging.Log;
-import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocData;
-import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.SearchableDocumentation;
-import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.SortableLocation;
-import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.ClassLocation;
-import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.FieldLocation;
-import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.MethodLocation;
-import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.PackageLocation;
-import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
-import org.codehaus.mojo.jaxb2.shared.Validate;
-
-import com.thoughtworks.qdox.model.JavaAnnotation;
-import com.thoughtworks.qdox.model.JavaClass;
-import com.thoughtworks.qdox.model.JavaField;
-import com.thoughtworks.qdox.model.JavaMethod;
-import com.thoughtworks.qdox.model.JavaPackage;
-import com.thoughtworks.qdox.model.JavaSource;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlEnumValue;
-import javax.xml.bind.annotation.XmlType;
-
-/**
- * The schemagen tool operates on compiled bytecode, where JavaDoc comments are not present.
- * However, the javadoc documentation present in java source files is required within the generated
- * XSD to increase usability and produce an XSD which does not loose out on important usage
- * information.
- *
- * The JavaDocExtractor is used as a post processor after creating the XSDs within the compilation
- * unit, and injects XSD annotations into the appropriate XSD elements or types.
- *
- * HB: This is a modified version of class
- * org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocExtractor
- * which processes all relevant classes, including the inner classes.
- *
- * @author Lennart Jörelid, jGuru Europe AB
- * @since 2.0
- */
-public class MyJavaDocExtractor
-{
- //~ Static fields/initializers -----------------------------------------------------------------
-
- /**
- * The default value given as the return value from some annotation classes whenever
- * the attribute has not been supplied within the codebase.
- */
- private static final String DEFAULT_VALUE = "##default";
-
- //~ Instance fields ----------------------------------------------------------------------------
- private final JavaProjectBuilder builder;
-
- private final Log log;
-
- //~ Constructors -------------------------------------------------------------------------------
- /**
- * Creates a NestedJavaDocExtractor wrapping the supplied Maven Log.
- *
- * @param log A non-null Log.
- */
- public MyJavaDocExtractor (final Log log)
- {
-
- // Check sanity
- Validate.notNull(log, "log");
-
- // Create internal state
- this.log = log;
- this.builder = new JavaProjectBuilder();
- }
-
- //~ Methods ------------------------------------------------------------------------------------
- /**
- * Assigns the encoding of the underlying {@link JavaProjectBuilder}.
- *
- * @param encoding The non-empty encoding to be set into the underlying
- * {@link JavaProjectBuilder}.
- */
- public void setEncoding (final String encoding)
- {
- this.builder.setEncoding(encoding);
- }
-
- /**
- * Adds the supplied sourceCodeFiles for processing by this JavaDocExtractor.
- *
- * @param sourceCodeFiles The non-null List of source code files to add.
- * @return This MyJavaDocExtractor, for call chaining.
- * @throws IllegalArgumentException If any of the given sourceCodeFiles could not be read
- * properly.
- */
- public MyJavaDocExtractor addSourceFiles (final List sourceCodeFiles)
- throws IllegalArgumentException
- {
-
- // Check sanity
- Validate.notNull(sourceCodeFiles, "addSourceFiles");
-
- // Add the files.
- for (File current : sourceCodeFiles) {
- try {
- builder.addSource(current);
- } catch (IOException e) {
- throw new IllegalArgumentException("Could not add file ["
- + FileSystemUtilities.getCanonicalPath(
- current) + "]", e);
- }
- }
-
- // All done.
- return this;
- }
-
- /**
- * Adds the supplied sourceCodeFiles for processing by this JavaDocExtractor.
- *
- * @param sourceCodeURLs The non-null List of source code URLs to add.
- * @return This MyJavaDocExtractor, for call chaining.
- * @throws IllegalArgumentException If any of the given sourceCodeURLs could not be read
- * properly.
- */
- public MyJavaDocExtractor addSourceURLs (final List sourceCodeURLs)
- throws IllegalArgumentException
- {
-
- // Check sanity
- Validate.notNull(sourceCodeURLs, "sourceCodeURLs");
-
- // Add the URLs
- for (URL current : sourceCodeURLs) {
- try {
- builder.addSource(current);
- } catch (IOException e) {
- throw new IllegalArgumentException("Could not add URL [" + current.toString() + "]",
- e);
- }
- }
-
- // All done
- return this;
- }
-
- /**
- * Processes all supplied Java source Files and URLs to extract JavaDocData for all
- * ClassLocations from which JavaDoc has been collected.
- *
- * @return A SearchableDocumentation relating SortableLocations and their paths to harvested
- * JavaDocData.
- */
- public SearchableDocumentation process ()
- {
-
- // Start processing.
- final SortedMap dataHolder = new TreeMap<>();
- final Collection sources = builder.getSources();
-
- if (log.isInfoEnabled()) {
- log.info("Processing [" + sources.size() + "] java sources.");
- }
-
- for (JavaSource current : sources) {
-
- // Add the package-level JavaDoc
- final JavaPackage currentPackage = current.getPackage();
- final String packageName = currentPackage.getName();
- addEntry(dataHolder, new PackageLocation(packageName), currentPackage);
-
- if (log.isDebugEnabled()) {
- log.debug("Added package-level JavaDoc for [" + packageName + "]");
- }
-
- for (JavaClass currentClass : current.getClasses()) {
- processClass(currentClass, packageName, dataHolder);
- }
- }
-
- // All done.
- return new ReadOnlySearchableDocumentation(dataHolder);
- }
-
- private void processClass (JavaClass currentClass,
- String packageName,
- SortedMap dataHolder)
- {
- // Add the class-level JavaDoc
- final String simpleClassName = currentClass.getName();
- final String classXmlName = getAnnotationAttributeValueFrom(
- XmlType.class, "name", currentClass.getAnnotations());
-
- final ClassLocation classLocation = new ClassLocation(
- packageName, simpleClassName, classXmlName);
-
- addEntry(dataHolder, classLocation, currentClass);
-
- if (log.isDebugEnabled()) {
- log.debug("Added class-level JavaDoc for [" + classLocation + "]");
- }
-
- // Fields
- for (JavaField currentField : currentClass.getFields()) {
-
- final List currentFieldAnnotations = currentField
- .getAnnotations();
- String annotatedXmlName = null;
-
- //
- // Is this field a collection, annotated with @XmlElementWrapper?
- // If so, the documentation should pertain to the corresponding XML Sequence,
- // rather than the individual XML elements.
- //
- if (hasAnnotation(XmlElementWrapper.class, currentFieldAnnotations)) {
-
- // There are 2 cases here:
- //
- // 1: The XmlElementWrapper is named.
- // ==================================
- // @XmlElementWrapper(name = "foobar")
- // @XmlElement(name = "aString")
- // private List strings;
- //
- // ==> annotatedXmlName == "foobar"
- //
- // 2: The XmlElementWrapper is not named.
- // ======================================
- // @XmlElementWrapper
- // @XmlElement(name = "anInteger")
- // private SortedSet integerSet;
- //
- // ==> annotatedXmlName == "integerSet"
- //
- annotatedXmlName = getAnnotationAttributeValueFrom(
- XmlElementWrapper.class,
- "name",
- currentFieldAnnotations);
-
- if (annotatedXmlName == null || annotatedXmlName.equals(DEFAULT_VALUE)) {
- annotatedXmlName = currentField.getName();
- }
- }
-
- // Find the XML name if provided within an annotation.
- if (annotatedXmlName == null) {
- annotatedXmlName = getAnnotationAttributeValueFrom(
- XmlElement.class,
- "name",
- currentFieldAnnotations);
- }
-
- if (annotatedXmlName == null) {
- annotatedXmlName = getAnnotationAttributeValueFrom(
- XmlAttribute.class,
- "name",
- currentFieldAnnotations);
- }
- if (annotatedXmlName == null) {
- annotatedXmlName = getAnnotationAttributeValueFrom(
- XmlEnumValue.class,
- "value",
- currentFieldAnnotations);
- }
-
- // Add the field-level JavaDoc
- final FieldLocation fieldLocation = new FieldLocation(
- packageName,
- simpleClassName,
- classXmlName,
- currentField.getName(),
- annotatedXmlName);
-
- addEntry(dataHolder, fieldLocation, currentField);
-
- if (log.isDebugEnabled()) {
- log.debug("Added field-level JavaDoc for [" + fieldLocation + "]");
- }
- }
-
- // Methods
- for (JavaMethod currentMethod : currentClass.getMethods()) {
-
- final List currentMethodAnnotations = currentMethod
- .getAnnotations();
- String annotatedXmlName = null;
-
- //
- // Is this field a collection, annotated with @XmlElementWrapper?
- // If so, the documentation should pertain to the corresponding XML Sequence,
- // rather than the individual XML elements.
- //
- if (hasAnnotation(XmlElementWrapper.class, currentMethodAnnotations)) {
-
- // There are 2 cases here:
- //
- // 1: The XmlElementWrapper is named.
- // ==================================
- // @XmlElementWrapper(name = "foobar")
- // @XmlElement(name = "aString")
- // public List getStrings() { ... };
- //
- // ==> annotatedXmlName == "foobar"
- //
- // 2: The XmlElementWrapper is not named.
- // ======================================
- // @XmlElementWrapper
- // @XmlElement(name = "anInteger")
- // public SortedSet getIntegerSet() { ... };
- //
- // ==> annotatedXmlName == "getIntegerSet"
- //
- annotatedXmlName = getAnnotationAttributeValueFrom(
- XmlElementWrapper.class,
- "name",
- currentMethodAnnotations);
-
- if (annotatedXmlName == null || annotatedXmlName.equals(DEFAULT_VALUE)) {
- annotatedXmlName = currentMethod.getName();
- }
- }
-
- // Find the XML name if provided within an annotation.
- if (annotatedXmlName == null) {
- annotatedXmlName = getAnnotationAttributeValueFrom(
- XmlElement.class,
- "name",
- currentMethod.getAnnotations());
- }
-
- if (annotatedXmlName == null) {
- annotatedXmlName = getAnnotationAttributeValueFrom(
- XmlAttribute.class,
- "name",
- currentMethod.getAnnotations());
- }
-
- // Add the method-level JavaDoc
- final MethodLocation location = new MethodLocation(packageName,
- simpleClassName,
- classXmlName,
- currentMethod.getName(),
- annotatedXmlName,
- currentMethod.getParameters());
- addEntry(dataHolder, location, currentMethod);
-
- if (log.isDebugEnabled()) {
- log.debug("Added method-level JavaDoc for [" + location + "]");
- }
- }
-
- // HB: Recursion to inner classes if any
- for (JavaClass innerClass : currentClass.getNestedClasses()) {
- processClass(innerClass, packageName, dataHolder);
- }
- }
-
- /**
- * Finds the value of the attribute with the supplied name within the first matching
- * JavaAnnotation of
- * the given type encountered in the given annotations List. This is typically used for reading
- * values of
- * annotations such as {@link XmlElement}, {@link XmlAttribute} or {@link XmlEnumValue}.
- *
- * @param annotations The list of JavaAnnotations to filter from.
- * @param annotationType The type of annotation to read attribute values from.
- * @param attributeName The name of the attribute the value of which should be returned.
- * @return The first matching JavaAnnotation of type annotationType within the given annotations
- * List, or {@code null} if none was found.
- * @since 2.2
- */
- private static String getAnnotationAttributeValueFrom (
- final Class> annotationType,
- final String attributeName,
- final List annotations)
- {
-
- // QDox uses the fully qualified class name of the annotation for comparison.
- // Extract it.
- final String fullyQualifiedClassName = annotationType.getName();
-
- JavaAnnotation annotation = null;
- String toReturn = null;
-
- if (annotations != null) {
-
- for (JavaAnnotation current : annotations) {
- if (current.getType().isA(fullyQualifiedClassName)) {
- annotation = current;
- break;
- }
- }
-
- if (annotation != null) {
-
- final Object nameValue = annotation.getNamedParameter(attributeName);
-
- if (nameValue != null && nameValue instanceof String) {
-
- toReturn = ((String) nameValue).trim();
-
- // Remove initial and trailing " chars, if present.
- if (toReturn.startsWith("\"") && toReturn.endsWith("\"")) {
- toReturn = (((String) nameValue).trim()).substring(1, toReturn.length() - 1);
- }
- }
- }
- }
-
- // All Done.
- return toReturn;
- }
-
- private static boolean hasAnnotation (final Class> annotationType,
- final List annotations)
- {
-
- if (annotations != null && !annotations.isEmpty() && annotationType != null) {
-
- final String fullAnnotationClassName = annotationType.getName();
-
- for (JavaAnnotation current : annotations) {
- if (current.getType().isA(fullAnnotationClassName)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- //
- // Private helpers
- //
- private void addEntry (final SortedMap map,
- final SortableLocation key,
- final JavaAnnotatedElement value)
- {
-
- // Check sanity
- if (map.containsKey(key)) {
-
- // Get something to compare with
- final JavaDocData existing = map.get(key);
-
- // Is this an empty package-level documentation?
- if (key instanceof PackageLocation) {
-
- final boolean emptyExisting = existing.getComment() == null || existing.getComment()
- .isEmpty();
- final boolean emptyGiven = value.getComment() == null || value.getComment()
- .isEmpty();
-
- if (emptyGiven) {
- if (log.isDebugEnabled()) {
- log.debug("Skipping processing empty Package javadoc from [" + key + "]");
- }
- return;
- } else if (emptyExisting && log.isWarnEnabled()) {
- log.warn("Overwriting empty Package javadoc from [" + key + "]");
- }
- } else {
- final String given = "[" + value.getClass().getName() + "]: " + value.getComment();
- throw new IllegalArgumentException("Not processing duplicate SortableLocation ["
- + key + "]. "
- + "\n Existing: " + existing
- + ".\n Given: [" + given + "]");
- }
- }
-
- // Validate.isTrue(!map.containsKey(key), "Found duplicate SortableLocation [" + key + "] in map. "
- // + "Current map keySet: " + map.keySet() + ". Got comment: [" + value.getComment() + "]");
- map.put(key, new JavaDocData(value.getComment(), value.getTags()));
- }
-
- //~ Inner Classes ------------------------------------------------------------------------------
- /**
- * Standard read-only SearchableDocumentation implementation.
- */
- private class ReadOnlySearchableDocumentation
- implements SearchableDocumentation
- {
-
- // Internal state
- private final TreeMap keyMap;
-
- private final SortedMap extends SortableLocation, JavaDocData> valueMap;
-
- ReadOnlySearchableDocumentation (final SortedMap valueMap)
- {
-
- // Create internal state
- this.valueMap = valueMap;
-
- keyMap = new TreeMap<>();
- for (Map.Entry current : valueMap.entrySet()) {
-
- final SortableLocation key = current.getKey();
- keyMap.put(key.getPath(), key);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public SortedSet getPaths ()
- {
- return Collections.unmodifiableSortedSet(keyMap.navigableKeySet());
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public JavaDocData getJavaDoc (final String path)
- {
-
- // Check sanity
- Validate.notNull(path, "path");
-
- // All done.
- final SortableLocation location = getLocation(path);
- return (location == null) ? null : valueMap.get(location);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- @SuppressWarnings("unchecked")
- public T getLocation (final String path)
- {
-
- // Check sanity
- Validate.notNull(path, "path");
-
- // All done
- return (T) keyMap.get(path);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- @SuppressWarnings("unchecked")
- public SortedMap getAll ()
- {
- return (SortedMap) Collections.unmodifiableSortedMap(
- valueMap);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- @SuppressWarnings("unchecked")
- public SortedMap getAll (final Class type)
- {
-
- // Check sanity
- Validate.notNull(type, "type");
-
- // Filter the valueMap.
- final SortedMap toReturn = new TreeMap<>();
- for (Map.Entry extends SortableLocation, JavaDocData> current : valueMap.entrySet()) {
- if (type == current.getKey().getClass()) {
- toReturn.put((T) current.getKey(), current.getValue());
- }
- }
-
- // All done.
- return toReturn;
- }
- }
-}
+//------------------------------------------------------------------------------------------------//
+// //
+// M y J a v a D o c E x t r a c t o r //
+// //
+//------------------------------------------------------------------------------------------------//
+package org.audiveris.schema;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import org.apache.maven.plugin.logging.Log;
+import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocData;
+import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.SearchableDocumentation;
+import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.SortableLocation;
+import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.ClassLocation;
+import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.FieldLocation;
+import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.MethodLocation;
+import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.PackageLocation;
+import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
+import org.codehaus.mojo.jaxb2.shared.Validate;
+
+import com.thoughtworks.qdox.JavaProjectBuilder;
+import com.thoughtworks.qdox.model.JavaAnnotatedElement;
+import com.thoughtworks.qdox.model.JavaAnnotation;
+import com.thoughtworks.qdox.model.JavaClass;
+import com.thoughtworks.qdox.model.JavaField;
+import com.thoughtworks.qdox.model.JavaMethod;
+import com.thoughtworks.qdox.model.JavaPackage;
+import com.thoughtworks.qdox.model.JavaSource;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlEnumValue;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * The schemagen tool operates on compiled bytecode, where JavaDoc comments are not present.
+ * However, the javadoc documentation present in java source files is required within the generated
+ * XSD to increase usability and produce an XSD which does not loose out on important usage
+ * information.
+ *
+ *
+ * The JavaDocExtractor is used as a post processor after creating the XSDs within the compilation
+ * unit, and injects XSD annotations into the appropriate XSD elements or types.
+ *
- * Code is a very simplified version of {@link SchemaGenerationMojo} since the plain 'vanilla'
- * XSD files already exist, generated from Java sources.
- * Its role is thus limited to the injection of JavaDoc content directly retrieved from the same
- * Java sources.
- *
- * It uses an enhanced JavaDocExtractor, since the original one did not process inner classes.
- *
+ * Code is a very simplified version of {@link SchemaGenerationMojo} since the plain 'vanilla'
+ * XSD files already exist, generated from Java sources.
+ * Its role is thus limited to the injection of JavaDoc content directly retrieved from the same
+ * Java sources.
+ *
+ * It uses an enhanced JavaDocExtractor, since the original one did not process inner classes.
+ *
+ * NOTA: Unfortunately, it can only process XSD files whose names match "schemaN.xsd".
+ * This is due to final {@link AbstractXsdGeneratorMojo#SCHEMAGEN_EMITTED_FILENAME} pattern.
+ *
+ * @author Hervé Bitteur
+ */
+public class SchemaAnnotator
+ extends AbstractXsdGeneratorMojo
+{
+
+ //~ Static fields/initializers -----------------------------------------------------------------
+ //~ Instance fields ----------------------------------------------------------------------------
+ /**
+ * The directory where the plain XSD files are read.
+ * From this directory the XSDs will be copied to the outputDirectory for further processing.
+ *
+ * @see #outputDirectory
+ */
+ // "${project.build.directory}/schemagen-work/compile_scope"
+ private final File workDirectory;
+
+ /**
+ * The directory where the annotated XSD file(s) will be
+ * placed, after all transformations are done.
+ */
+ private final File outputDirectory;
+
+ /**
+ * List of paths to files and/or directories which should be recursively searched
+ * for Java source files.
+ */
+ private final List sources;
+
+ //~ Constructors -------------------------------------------------------------------------------
+ /**
+ * Create the {@code SchemaAnnotator} instance with its parameters.
+ *
+ * @param workDirectory directory where plain XSD files are read
+ * @param outputDirectory directory where annotated XSD files are written
+ * @param sources list of files/directories to be recursively searched for Java sources
+ */
+ public SchemaAnnotator (File workDirectory,
+ File outputDirectory,
+ List sources)
+ {
+ this.workDirectory = workDirectory;
+ this.outputDirectory = outputDirectory;
+ this.sources = sources;
+ }
+
+ //~ Methods ------------------------------------------------------------------------------------
+ @Override
+ protected File getWorkDirectory ()
+ {
+ return workDirectory;
+ }
+
+ @Override
+ protected List getCompiledClassNames ()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected List getSources ()
+ {
+ return null;
+ }
+
+ @Override
+ protected void addResource (Resource resource)
+ {
+ }
+
+ @Override
+ protected File getOutputDirectory ()
+ {
+ return outputDirectory;
+ }
+
+ @Override
+ protected List getClasspath ()
+ throws MojoExecutionException
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected String getStaleFileName ()
+ {
+ return SchemaGenerationMojo.STALE_FILENAME;
+ }
+
+ //---------//
+ // process //
+ //---------//
+ public final void process ()
+ throws MojoExecutionException, MojoFailureException
+ {
+ try {
+ // Copy plain XSDs files from the WorkDirectory to the OutputDirectory.
+ final List> exclusionFilters = PatternFileFilter.createIncludeFilterList(
+ getLog(),
+ "\\.class");
+
+ final List toCopy = FileSystemUtilities.resolveRecursively(
+ Arrays.asList(getWorkDirectory()),
+ exclusionFilters,
+ getLog());
+
+ for (File current : toCopy) {
+ // Get the path to the current file
+ final String currentPath = FileSystemUtilities.getCanonicalPath(
+ current.getAbsoluteFile());
+ final File target = new File(
+ getOutputDirectory(),
+ FileSystemUtilities.relativize(currentPath, getWorkDirectory(), true));
+
+ // Copy the file to the same relative structure within the output directory.
+ FileSystemUtilities.createDirectory(target.getParentFile(), false);
+ FileUtils.copyFile(current, target);
+ }
+
+ //
+ // The XSD post-processing should be applied in the following order:
+ //
+ // 1. [XsdAnnotationProcessor]: Inject JavaDoc annotations for Classes.
+ // 2. [XsdEnumerationAnnotationProcessor]: Inject JavaDoc annotations for Enums.
+ // 3. [ChangeNamespacePrefixProcessor]: Change namespace prefixes within XSDs.
+ // 4. [ChangeFilenameProcessor]: Change the fileNames of XSDs.
+ //
+ // // Map the XML Namespaces to their respective XML URIs (and reverse)
+ // // The keys are the generated 'vanilla' XSD file names.
+ // Map resolverMap = null;
+ // final Map resolverMap = XsdGeneratorHelper
+ // .getFileNameToResolverMap(getOutputDirectory());
+ getLog().info("XSD post-processing: Adding JavaDoc annotations in generated XSDs.");
+
+ // Retrieve all .java files under 'sources' directories
+ final Filter acceptJavaFilter = new FileFilterAdapter(
+ f -> f.isDirectory() || f.getName().endsWith(".java"));
+ acceptJavaFilter.initialize(getLog());
+ final List javaFiles = FileSystemUtilities.filterFiles(
+ sources,
+ acceptJavaFilter,
+ getLog());
+ getLog().info("Java files: " + javaFiles.size());
+
+ // Acquire JavaDocs, using the MyJavaDocExtractor
+ final MyJavaDocExtractor extractor = new MyJavaDocExtractor(getLog()).addSourceFiles(
+ javaFiles);
+ final SearchableDocumentation javaDocs = extractor.process();
+ ///dumpDocs(javaDocs);
+
+ // Modify the 'vanilla' generated XSDs by inserting the JavaDoc as annotations
+ final JavaDocRenderer renderer = new NoAuthorJavaDocRenderer();
+ final int numProcessedFiles = MyXsdGeneratorHelper.insertJavaDocAsAnnotations(
+ getLog(),
+ getEncoding(false),
+ getOutputDirectory(),
+ javaDocs,
+ renderer);
+
+ ///if (getLog().isDebugEnabled()) {
+ getLog().info("XSD post-processing: " + numProcessedFiles + " files processed.");
+ ///}
+ } catch (MojoExecutionException e) {
+ throw e;
+ } catch (IOException | IllegalArgumentException e) {
+
+ // Find the root exception, and print its stack trace to the Maven Log.
+ // These invocation target exceptions tend to produce really deep stack traces,
+ // hiding the actual root cause of the exception.
+ Throwable current = e;
+ while (current.getCause() != null) {
+ current = current.getCause();
+ }
+
+ getLog().error("Execution failed.");
+
+ //
+ // Print a stack trace
+ //
+ StringBuilder rootCauseBuilder = new StringBuilder();
+ rootCauseBuilder.append("\n");
+ rootCauseBuilder.append("[Exception]: ").append(current.getClass().getName()).append(
+ "\n");
+ rootCauseBuilder.append("[Message]: ").append(current.getMessage()).append("\n");
+ for (StackTraceElement el : current.getStackTrace()) {
+ rootCauseBuilder.append(" ").append(el.toString()).append("\n");
+ }
+ getLog().error(rootCauseBuilder.toString());
+ }
+ }
+
+ // HB
+ public void dumpDocs (SearchableDocumentation docs)
+ {
+ SortedMap map = docs.getAll();
+
+ for (Entry entry : map.entrySet()) {
+ getLog().info(entry.getKey() + " => " + entry.getValue());
+ }
+ }
+
+ //------//
+ // main //
+ //------//
+ /**
+ * Main entry point for the annotator.
+ *
+ * @param args the command line arguments
+ * - arg1: work directory
+ * - arg2: output directory
+ * - following args: files/directories containing Java sources
+ */
+ public static void main (String[] args)
+ {
+ if ((args == null) || args.length < 3) {
+ System.err.println("Error in SchemaAnnotator, expecting 3 arguments at least");
+ throw new RuntimeException("Incorrect arguments to SchemaAnnotator");
+ }
+
+ try {
+ final File work = new File(args[0]);
+ final File output = new File(args[1]);
+ final List sources = new ArrayList<>();
+
+ for (int i = 2; i < args.length; i++) {
+ sources.add(new File(args[i]));
+ }
+
+ final SchemaAnnotator sa = new SchemaAnnotator(work, output, sources);
+ sa.process();
+ } catch (Exception ex) {
+ System.err.println("Error in schema annotator: " + ex);
+ }
+ }
+}
diff --git a/src/main/org/audiveris/omr/image/FloodFiller.java b/src/main/org/audiveris/omr/image/FloodFiller.java
index 66f961803..fb73d2126 100644
--- a/src/main/org/audiveris/omr/image/FloodFiller.java
+++ b/src/main/org/audiveris/omr/image/FloodFiller.java
@@ -57,7 +57,8 @@ public FloodFiller (BufferedImage image)
/**
* Flood fill the area from provided (x,y) location by recursively converting
* pixels from oldColor to newColor.
- * Simplistic implementation, but sufficient for the time being.
+ *
+ * This implementation flood fills only vertically and horizontally, not in diagonal.
*
* @param x the abscissa of the pixel to process
* @param y the ordinate of the pixel to process
@@ -74,18 +75,45 @@ public void fill (int x,
if ((pix == oldColor) && (pix != newColor)) {
image.setRGB(x, y, newColor);
- fill(x - 1, y - 1, oldColor, newColor);
- fill(x - 1, y, oldColor, newColor);
- fill(x - 1, y + 1, oldColor, newColor);
+ fill(x - 1, y, oldColor, newColor); // Left
+ fill(x, y - 1, oldColor, newColor); // Top
+ fill(x, y + 1, oldColor, newColor); // Bottom
+ fill(x + 1, y, oldColor, newColor); // Right
+ }
+ }
+ }
- fill(x, y - 1, oldColor, newColor);
+ /**
+ * Check if the pixel at provided (x,y) location is an isolated pixel, and if so,
+ * replace it by newColor.
+ *
+ * @param x the abscissa of the pixel to process
+ * @param y the ordinate of the pixel to process
+ * @param newColor the color to replace with
+ */
+ public void adjust (int x,
+ int y,
+ int newColor)
+ {
+ // Check if pixel is isolated (no vertical/horizontal neighbor w/ same color)
+ final int pix = image.getRGB(x, y);
- fill(x, y + 1, oldColor, newColor);
+ if (image.getRGB(x - 1, y) == pix) {
+ return;
+ }
- fill(x + 1, y - 1, oldColor, newColor);
- fill(x + 1, y, oldColor, newColor);
- fill(x + 1, y + 1, oldColor, newColor);
- }
+ if (image.getRGB(x, y - 1) == pix) {
+ return;
+ }
+
+ if (image.getRGB(x, y + 1) == pix) {
+ return;
+ }
+
+ if (image.getRGB(x + 1, y) == pix) {
+ return;
}
+
+ image.setRGB(x, y, newColor);
}
}
diff --git a/src/main/org/audiveris/omr/image/TemplateFactory.java b/src/main/org/audiveris/omr/image/TemplateFactory.java
index 6b0ed342e..07d3d8ba0 100644
--- a/src/main/org/audiveris/omr/image/TemplateFactory.java
+++ b/src/main/org/audiveris/omr/image/TemplateFactory.java
@@ -317,6 +317,7 @@ private Template buildTemplate (Shape shape,
pointSize,
img,
fatBounds) : null;
+
// Generate the template instance
final Template tpl = new Template(
shape,
@@ -488,6 +489,29 @@ private static void addHoles (Shape shape,
}
}
+ //-------------//
+ // adjustHoles //
+ //------------//
+ /**
+ * Correct any hole pixel left over.
+ *
+ * @param img the source image
+ * @param box bounds of symbol relative to image
+ */
+ private static void adjustHoles (BufferedImage img,
+ Rectangle box)
+ {
+ final FloodFiller floodFiller = new FloodFiller(img);
+
+ for (int iy = box.y + 1, iyBreak = box.y + box.height - 1; iy < iyBreak; iy++) {
+ for (int ix = box.x + 1, ixBreak = box.x + box.width - 1; ix < ixBreak; ix++) {
+ if (isBackground(img, ix, iy)) {
+ floodFiller.adjust(ix, iy, HOLE);
+ }
+ }
+ }
+ }
+
//----------//
// binarize //
//----------//
@@ -524,6 +548,7 @@ private static void binarize (BufferedImage img,
//----------------//
/**
* Build the collection of key points to be used for matching tests.
+ *
* These are the locations where the image distance value will be checked against the recorded
* template distance value.
*
@@ -532,10 +557,11 @@ private static void binarize (BufferedImage img,
* @return the collection of key locations, with their corresponding distance value
*/
private static List buildKeyPoints (BufferedImage img,
- DistanceTable distances)
+ DistanceTable distances,
+ int pointSize)
{
final List keyPoints = new ArrayList<>();
- final int maxDist = constants.maxRawDistanceFromSymbol.getSourceValue();
+ final int maxDist = maxRawDistanceFromSymbol(pointSize);
for (int y = 0, h = img.getHeight(); y < h; y++) {
for (int x = 0, w = img.getWidth(); x < w; x++) {
@@ -588,7 +614,7 @@ private static DistanceTable computeDistances (BufferedImage img,
// Compute template distance transform
final DistanceTable distances = new ChamferDistance.Short().compute(fore);
- if (logger.isDebugEnabled()) {
+ if (logger.isTraceEnabled()) {
TableUtil.dump(shape + " distances", distances);
}
@@ -606,9 +632,10 @@ private static DistanceTable computeDistances (BufferedImage img,
* @param img the image to modify
*/
private static void flagIrrelevantPixels (BufferedImage img,
- DistanceTable distances)
+ DistanceTable distances,
+ int pointSize)
{
- final int maxDist = constants.maxRawDistanceFromSymbol.getValue();
+ final int maxDist = maxRawDistanceFromSymbol(pointSize);
for (int y = 0, h = img.getHeight(); y < h; y++) {
for (int x = 0, w = img.getWidth(); x < w; x++) {
@@ -641,7 +668,7 @@ private static double getBottom (Shape shape,
case NOTEHEAD_CROSS_VOID -> slimBox.y + slimBox.height;
case NOTEHEAD_DIAMOND_FILLED, NOTEHEAD_DIAMOND_VOID -> slimBox.y + slimBox.height / 2.0;
case NOTEHEAD_TRIANGLE_DOWN_FILLED, NOTEHEAD_TRIANGLE_DOWN_VOID -> slimBox.y;
-
+
default -> slimBox.y + slimBox.height * (1 + constants.stemDy.getValue());
};
// @formatter:on
@@ -806,7 +833,7 @@ public static void main (String... args)
final int max;
if ((args == null) || (args.length == 0)) {
- min = max = constants.defaultDecoratedPointSize.getValue();
+ min = max = constants.defaultPointSize.getValue();
} else {
min = Integer.decode(args[0]);
max = (args.length > 1) ? Integer.decode(args[1]) : min;
@@ -814,6 +841,8 @@ public static void main (String... args)
logger.info("minPointSize:{} maxPointSize{}", min, max);
+ MusicFont.populateAllSymbols();
+
final TemplateFactory factory = TemplateFactory.getInstance();
for (MusicFamily family : MusicFamily.values()) {
@@ -837,6 +866,23 @@ public static double maxDistanceFromSymbol ()
/ (double) ChamferDistance.DEFAULT_NORMALIZER;
}
+ //--------------------------//
+ // maxRawDistanceFromSymbol //
+ //--------------------------//
+ /**
+ * Report the maximum raw distance, adjusted according to provided pointSize,
+ * from symbol foreground for a pixel to be considered.
+ *
+ * @param pointSize font point size
+ * @return the (adjusted) maximum raw distance
+ */
+ private static int maxRawDistanceFromSymbol (int pointSize)
+ {
+ return (int) Math.rint(
+ constants.maxRawDistanceFromSymbol.getValue() //
+ * ((double) pointSize / constants.defaultPointSize.getValue()));
+ }
+
//-------------------//
// retrieveKeyPoints //
//-------------------//
@@ -863,19 +909,34 @@ private static List retrieveKeyPoints (Shape shape,
Rectangle fatBounds)
{
logger.debug("Building template keyPoints for {} {} {}", shape, family, pointSize);
+
// Distances to foreground
final DistanceTable distances = computeDistances(img, shape);
// Add holes if any
if (shapesWithHoles.contains(shape)) {
addHoles(shape, img, fatBounds); // B=FORE & W=BACK + HOLE pixels
+
+ // Safety check
+ if (img.getRGB(0, 0) == HOLE) {
+ logger.warn(
+ "*** UNUSABLE template *** for shape {} in family {} pointSize {}",
+ shape,
+ family,
+ pointSize);
+ }
}
- // Flag non-relevant pixels
- flagIrrelevantPixels(img, distances); // B=FORE & W=BACK + HOLE pixels + IRRELEVANT pixels
+ // Flag non-relevant pixels: B=FORE & W=BACK + HOLE pixels + IRRELEVANT pixels
+ flagIrrelevantPixels(img, distances, pointSize);
+
+ // Adjust holes if any
+ if (shapesWithHoles.contains(shape)) {
+ adjustHoles(img, fatBounds); // HOLE
+ }
// Generate key points for relevant pixels only (fore, holes or back)
- return buildKeyPoints(img, distances);
+ return buildKeyPoints(img, distances, pointSize);
}
//~ Inner Classes ------------------------------------------------------------------------------
@@ -975,10 +1036,10 @@ private static class Constants
50,
"Magnification ratio of template images for visual check");
- private final Constant.Integer defaultDecoratedPointSize = new Constant.Integer(
+ private final Constant.Integer defaultPointSize = new Constant.Integer(
"pointSize",
- 69,
- "Default point size used to generate decorated images");
+ 80,
+ "Default point size");
private final Constant.Integer minCellPerSide = new Constant.Integer(
"pixels",
diff --git a/src/main/org/audiveris/omr/score/LogicalPart.java b/src/main/org/audiveris/omr/score/LogicalPart.java
index b541f6974..cfe88af59 100644
--- a/src/main/org/audiveris/omr/score/LogicalPart.java
+++ b/src/main/org/audiveris/omr/score/LogicalPart.java
@@ -41,7 +41,7 @@
import javax.xml.bind.annotation.XmlElement;
/**
- * Class LogicalPart describes a part at score or page level.
+ * Class LogicalPart describes a part at score level.
*
* It can be "instantiated" in one or several SystemInfo by a (physical)
* {@link Part} instance.
diff --git a/src/main/org/audiveris/omr/sheet/Part.java b/src/main/org/audiveris/omr/sheet/Part.java
index 9ac4779ab..03aaccd5f 100644
--- a/src/main/org/audiveris/omr/sheet/Part.java
+++ b/src/main/org/audiveris/omr/sheet/Part.java
@@ -94,8 +94,8 @@
* {@link LogicalPart}.
*
* Generally, such LogicalPart corresponds to a separate (physical) Part instance in each
- * system but not always. For example, a singer part may not appear at the very beginning of a
- * score, but only after one or several systems played by the piano part.
+ * system but not always. For example, a singer part may appear only after one or several systems
+ * played by the piano part.
*
* We assume that the configuration of staves within the physical Part instances of the same logical
* LogicalPart do not vary (in number of staves, in number of lines in staves or in relative
@@ -106,8 +106,6 @@
* During export to MusicXML, dummy parts (and their contained dummy staves and measures) can be
* virtually inserted on-the-fly into the structure of page/system/part/measure/staff
* to ease the handling of logical parts along the pages and score.
- *
- * TODO: include the image of a "merged grand staff".
*
* @author Hervé Bitteur
*/
@@ -148,6 +146,10 @@ public class Part
/**
* This boolean indicates a merged grand staff part, made of 2 staves
* not separated by a standard gutter.
+ *
+ * See example:
+ *
+ *
*/
@XmlAttribute(name = "merged-grand-staff")
@XmlJavaTypeAdapter(type = boolean.class, value = Jaxb.BooleanPositiveAdapter.class)
diff --git a/src/main/org/audiveris/omr/sheet/Staff.java b/src/main/org/audiveris/omr/sheet/Staff.java
index cacef7ede..1d3c0881b 100644
--- a/src/main/org/audiveris/omr/sheet/Staff.java
+++ b/src/main/org/audiveris/omr/sheet/Staff.java
@@ -21,7 +21,6 @@
//
package org.audiveris.omr.sheet;
-import org.audiveris.omr.score.StaffConfig;
import org.audiveris.omr.constant.ConstantSet;
import org.audiveris.omr.glyph.Glyph;
import org.audiveris.omr.glyph.GlyphIndex;
@@ -31,6 +30,7 @@
import org.audiveris.omr.math.PointUtil;
import org.audiveris.omr.math.Population;
import org.audiveris.omr.run.Orientation;
+import org.audiveris.omr.score.StaffConfig;
import org.audiveris.omr.sheet.grid.LineInfo;
import org.audiveris.omr.sheet.grid.StaffFilament;
import org.audiveris.omr.sheet.header.StaffHeader;
@@ -538,7 +538,7 @@ private void buildLedgerLines (GeoPath line,
{
for (int i = increment;; i += increment) {
final List ledgers = ledgerMap.get(i);
- if (ledgers == null) {
+ if ((ledgers == null) || ledgers.isEmpty()) {
return;
}
diff --git a/src/main/org/audiveris/omr/sheet/doc-files/merged_grand_staff.png b/src/main/org/audiveris/omr/sheet/doc-files/merged_grand_staff.png
new file mode 100644
index 000000000..8a8dbea1c
Binary files /dev/null and b/src/main/org/audiveris/omr/sheet/doc-files/merged_grand_staff.png differ
diff --git a/src/main/org/audiveris/omr/sheet/symbol/LinksStep.java b/src/main/org/audiveris/omr/sheet/symbol/LinksStep.java
index bf9b2c3e7..f6a41b258 100644
--- a/src/main/org/audiveris/omr/sheet/symbol/LinksStep.java
+++ b/src/main/org/audiveris/omr/sheet/symbol/LinksStep.java
@@ -179,8 +179,7 @@ public void impact (UITaskList seq,
if ((opKind != OpKind.UNDO) && task instanceof AdditionTask) {
linker.linkOneSentence(sentence);
- } else if (task instanceof SentenceRoleTask) {
- SentenceRoleTask roleTask = (SentenceRoleTask) interTask;
+ } else if (task instanceof SentenceRoleTask roleTask) {
linker.unlinkOneSentence(
sentence,
(opKind == OpKind.UNDO) ? roleTask.getNewRole()
diff --git a/src/main/org/audiveris/omr/sheet/symbol/SymbolsLinker.java b/src/main/org/audiveris/omr/sheet/symbol/SymbolsLinker.java
index ce7092de8..2b4344201 100644
--- a/src/main/org/audiveris/omr/sheet/symbol/SymbolsLinker.java
+++ b/src/main/org/audiveris/omr/sheet/symbol/SymbolsLinker.java
@@ -31,6 +31,7 @@
import org.audiveris.omr.sig.inter.AbstractChordInter;
import org.audiveris.omr.sig.inter.NumberInter;
import org.audiveris.omr.sig.inter.BeamGroupInter;
+import org.audiveris.omr.sig.inter.ChordNameInter;
import org.audiveris.omr.sig.inter.DynamicsInter;
import org.audiveris.omr.sig.inter.FermataArcInter;
import org.audiveris.omr.sig.inter.FermataDotInter;
@@ -45,7 +46,6 @@
import org.audiveris.omr.sig.inter.SlurInter;
import org.audiveris.omr.sig.inter.SmallChordInter;
import org.audiveris.omr.sig.inter.WedgeInter;
-import org.audiveris.omr.sig.inter.WordInter;
import org.audiveris.omr.sig.relation.ChordGraceRelation;
import org.audiveris.omr.sig.relation.ChordNameRelation;
import org.audiveris.omr.sig.relation.ChordSentenceRelation;
@@ -61,7 +61,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.util.Collection;
@@ -370,22 +369,13 @@ public void linkOneSentence (SentenceInter sentence)
{
// Map each word with proper chord, in assigned staff
for (Inter wInter : sentence.getMembers()) {
- WordInter word = (WordInter) wInter;
- Point wordCenter = word.getCenter();
- MeasureStack stack = system.getStackAt(wordCenter);
+ final ChordNameInter word = (ChordNameInter) wInter;
+ final Link link = word.lookupLink(system);
- if (stack == null) {
- logger.info("No stack at {}", word);
+ if (link == null) {
+ logger.info("No chord below {}", word);
} else {
- AbstractChordInter chordBelow = stack.getStandardChordBelow(
- wordCenter,
- word.getBounds());
-
- if (chordBelow != null) {
- sig.addEdge(chordBelow, word, new ChordNameRelation());
- } else {
- logger.info("No chord below {}", word);
- }
+ link.applyTo(wInter);
}
}
}
diff --git a/src/main/org/audiveris/omr/sheet/ui/BookActions.java b/src/main/org/audiveris/omr/sheet/ui/BookActions.java
index a8b670a62..647351ea8 100644
--- a/src/main/org/audiveris/omr/sheet/ui/BookActions.java
+++ b/src/main/org/audiveris/omr/sheet/ui/BookActions.java
@@ -2565,9 +2565,9 @@ public boolean setSpecific (Boolean specific)
// ResetBookTask //
//---------------//
/**
- * Task that reset all valid selected sheets of book to gray or binary.
+ * Task that resets all valid selected sheets of book to gray or binary.
*/
- private static class ResetBookTask
+ private class ResetBookTask
extends VoidTask
{
@@ -2588,6 +2588,7 @@ protected Void doInBackground ()
{
book.resetTo(step);
+ setBookTranscribable(true);
return null;
}
diff --git a/src/main/org/audiveris/omr/sheet/ui/resources/SplitAndMerge_fr.properties b/src/main/org/audiveris/omr/sheet/ui/resources/SplitAndMerge_fr.properties
index 1a0464caa..ad566ae9b 100644
--- a/src/main/org/audiveris/omr/sheet/ui/resources/SplitAndMerge_fr.properties
+++ b/src/main/org/audiveris/omr/sheet/ui/resources/SplitAndMerge_fr.properties
@@ -20,11 +20,11 @@ include.Action.shortDescription = Inclure un document parmi ceux d\u00e9j\u00e0
loadFiles.Action.text = Charger fichiers...
loadFiles.Action.shortDescription = Charger des documents ou des images dans la playlist
-moveDown.Action.text = Vers le haut
-moveDown.Action.shortDescription = D\u00e9placer vers le haut l'extrait s\u00e9lectionn\u00e9
+moveDown.Action.text = Vers le bas
+moveDown.Action.shortDescription = D\u00e9placer vers le bas l'extrait s\u00e9lectionn\u00e9
-moveUp.Action.text = Vers le bas
-moveUp.Action.shortDescription = D\u00e9placer vers le bas l'extrait s\u00e9lectionn\u00e9
+moveUp.Action.text = Vers le haut
+moveUp.Action.shortDescription = D\u00e9placer vers le haut l'extrait s\u00e9lectionn\u00e9
open.Action.text = Ouvrir PlayList...
open.Action.shortDescription = Ouvrir un ficher playlist
diff --git a/src/main/org/audiveris/omr/sig/SigReducer.java b/src/main/org/audiveris/omr/sig/SigReducer.java
index 7978c2979..eca9ff3e3 100644
--- a/src/main/org/audiveris/omr/sig/SigReducer.java
+++ b/src/main/org/audiveris/omr/sig/SigReducer.java
@@ -290,6 +290,21 @@ private void analyzeChords ()
sig.insertExclusion(stem, ih, Exclusion.ExclusionCause.OVERLAP);
}
+ // Mutual head exclusion between shapes with different size (small vs standard)
+ final Set smallHeads = new LinkedHashSet<>();
+ final Set stdHeads = new LinkedHashSet<>();
+ for (Entry> headEntry : heads.entrySet()) {
+ final Set headSet = headEntry.getValue();
+ if (headSet != null) {
+ if (headEntry.getKey().isSmallHead()) {
+ smallHeads.addAll(headSet);
+ } else {
+ stdHeads.addAll(headSet);
+ }
+ }
+ }
+ exclude(smallHeads, stdHeads, INCOMPATIBLE);
+
// Mutual head exclusion between shapes with different DURATION
// Mutual head support within shapes with same DURATION
final List> allDurSets = new ArrayList<>(durs.values());
@@ -326,15 +341,15 @@ private void analyzeChords ()
}
// Head/Beam support or exclusion based on size
- for (Entry> entry : beams.entrySet()) {
- final Size size = entry.getKey();
- final Set beamSet = entry.getValue();
+ for (Entry> beamEntry : beams.entrySet()) {
+ final Size beamSize = beamEntry.getKey();
+ final Set beamSet = beamEntry.getValue();
- if (size == Size.SMALL) {
+ if (beamSize == Size.SMALL) {
// Small beams exclude all but small (oval) heads
for (Entry> headEntry : heads.entrySet()) {
if (!headEntry.getKey().isSmallHead()) {
- final Set headSet = entry.getValue();
+ final Set headSet = headEntry.getValue();
if (headSet != null) {
exclude(beamSet, headSet, INCOMPATIBLE);
diff --git a/src/main/org/audiveris/omr/sig/inter/ChordNameInter.java b/src/main/org/audiveris/omr/sig/inter/ChordNameInter.java
index 66c234734..cdd56c587 100644
--- a/src/main/org/audiveris/omr/sig/inter/ChordNameInter.java
+++ b/src/main/org/audiveris/omr/sig/inter/ChordNameInter.java
@@ -25,6 +25,7 @@
import org.audiveris.omr.glyph.Shape;
import org.audiveris.omr.math.PointUtil;
import org.audiveris.omr.sheet.SystemInfo;
+import org.audiveris.omr.sheet.rhythm.MeasureStack;
import static org.audiveris.omr.sig.inter.ChordNameInter.ChordKind.ChordType.AUGMENTED;
import static org.audiveris.omr.sig.inter.ChordNameInter.ChordKind.ChordType.AUGMENTED_SEVENTH;
import static org.audiveris.omr.sig.inter.ChordNameInter.ChordKind.ChordType.DIMINISHED;
@@ -49,6 +50,7 @@
import static org.audiveris.omr.sig.inter.ChordNameInter.ChordKind.ChordType.MINOR_SIXTH;
import static org.audiveris.omr.sig.inter.ChordNameInter.ChordKind.ChordType.SUSPENDED_FOURTH;
import static org.audiveris.omr.sig.inter.ChordNameInter.ChordKind.ChordType.SUSPENDED_SECOND;
+import org.audiveris.omr.sig.relation.ChordNameRelation;
import org.audiveris.omr.sig.relation.Containment;
import org.audiveris.omr.sig.relation.Link;
import org.audiveris.omr.sig.ui.AdditionTask;
@@ -65,8 +67,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -555,6 +560,28 @@ private static List getPatterns ()
return patterns;
}
+ //------------//
+ // lookupLink //
+ //------------//
+ /**
+ * Try to detect a link between this ChordNameInter and a HeadChord nearby.
+ *
+ * @param system system to be looked up
+ * @return the link found or null
+ */
+ public Link lookupLink (SystemInfo system)
+ {
+ final Point wordCenter = getCenter();
+ final MeasureStack stack = system.getStackAt(wordCenter);
+ final AbstractChordInter chordBelow = stack.getStandardChordBelow(wordCenter, getBounds());
+
+ if (chordBelow == null) {
+ return null;
+ }
+
+ return new Link(chordBelow, new ChordNameRelation(), false);
+ }
+
//------------//
// parseChord //
//------------//
@@ -621,6 +648,27 @@ private static ChordStructure parseChord (String value)
return null;
}
+ //-------------//
+ // searchLinks //
+ //-------------//
+ @Override
+ public Collection searchLinks (SystemInfo system)
+ {
+ final Link link = lookupLink(system);
+
+ return (link == null) ? Collections.emptyList() : Collections.singleton(link);
+ }
+
+ //---------------//
+ // searchUnlinks //
+ //---------------//
+ @Override
+ public Collection searchUnlinks (SystemInfo system,
+ Collection links)
+ {
+ return searchObsoletelinks(links, ChordNameRelation.class);
+ }
+
//----------//
// standard //
//----------//
diff --git a/src/main/org/audiveris/omr/sig/inter/HeadInter.java b/src/main/org/audiveris/omr/sig/inter/HeadInter.java
index 9f2d0da5b..1e8da4f52 100644
--- a/src/main/org/audiveris/omr/sig/inter/HeadInter.java
+++ b/src/main/org/audiveris/omr/sig/inter/HeadInter.java
@@ -1027,7 +1027,9 @@ public List extends UITask> preAdd (WrappedBoolean cancel,
if (!stemFound) {
// Head without stem
- HeadChordInter headChord = new HeadChordInter(null);
+ HeadChordInter headChord = shape.isSmallHead() //
+ ? new SmallChordInter(null)
+ : new HeadChordInter(null);
tasks.add(
new AdditionTask(
theSig,
diff --git a/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java b/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java
index 393c25e72..750d61543 100644
--- a/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java
+++ b/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java
@@ -121,6 +121,9 @@ public Voice getVoice ()
for (Relation rel : sig.getRelations(lastChord, ChordGraceRelation.class)) {
AbstractChordInter stdChord = (AbstractChordInter) sig.getOppositeInter(lastChord, rel);
+ if (stdChord instanceof SmallChordInter) {
+ continue; // Safer
+ }
return stdChord.getVoice();
}
diff --git a/src/main/org/audiveris/omr/sig/inter/StemInter.java b/src/main/org/audiveris/omr/sig/inter/StemInter.java
index 8528a1d32..c7a94603c 100644
--- a/src/main/org/audiveris/omr/sig/inter/StemInter.java
+++ b/src/main/org/audiveris/omr/sig/inter/StemInter.java
@@ -137,12 +137,9 @@ public class StemInter
//~ Constructors -------------------------------------------------------------------------------
- /**
- * No-arg constructor meant for JAXB.
- */
protected StemInter ()
{
- super(null, null, 0.0);
+ super(null, Shape.STEM, 0.0);
}
/**
diff --git a/src/main/org/audiveris/omr/sig/ui/InterController.java b/src/main/org/audiveris/omr/sig/ui/InterController.java
index 728a0c231..f0ed673e9 100644
--- a/src/main/org/audiveris/omr/sig/ui/InterController.java
+++ b/src/main/org/audiveris/omr/sig/ui/InterController.java
@@ -522,7 +522,6 @@ protected void build ()
case Lyrics ->
{
// Convert to LyricItem words, all within a single LyricLine sentence
- final WrappedBoolean cancel = new WrappedBoolean(false);
final LyricLineInter line = new LyricLineInter(
sentence.getBounds(),
sentence.getGrade(),
@@ -556,11 +555,6 @@ protected void build ()
seq.add(new LinkTask(sig, link.partner, item, link.relation));
}
- if (cancel.isSet()) {
- seq.setCancelled(true);
- return;
- }
-
// Remove the plain word
seq.add(new RemovalTask(orgWord));
}
@@ -574,37 +568,30 @@ protected void build ()
case ChordName ->
{
- // Convert to ChordName words, each within its own sentence
- final WrappedBoolean cancel = new WrappedBoolean(false);
-
+ // Convert to ChordName words within the sentence
for (Inter inter : sentence.getMembers()) {
if (!(inter instanceof ChordNameInter)) {
// Add a new ChordNameInter for any original word
- WordInter orgWord = (WordInter) inter;
- ChordNameInter cn = new ChordNameInter(orgWord);
- cn.setManual(true);
- cn.setStaff(staff);
+ final WordInter orgWord = (WordInter) inter;
+ final ChordNameInter chordName = new ChordNameInter(orgWord);
+ chordName.setManual(true);
+ chordName.setStaff(staff);
seq.add(
new AdditionTask(
sig,
- cn,
- cn.getBounds(),
+ chordName,
+ chordName.getBounds(),
Arrays.asList(
new Link(sentence, new Containment(), false))));
- if (cancel.isSet()) {
- seq.setCancelled(true);
- return;
- }
-
// Remove the original word
seq.add(new RemovalTask(orgWord));
- }
- }
- if (!seq.getTasks().isEmpty()) {
- // Remove the now useless original sentence (and its original relations)
- seq.add(new RemovalTask(sentence));
+ // Look for suitable related head chord
+ for (Link link : chordName.searchLinks(system)) {
+ seq.add(new LinkTask(sig, link.partner, chordName, link.relation));
+ }
+ }
}
}