Skip to content

Commit

Permalink
Add further header text inference cases
Browse files Browse the repository at this point in the history
This commit adds credit inferring to the title text (similar to what
already exists for subtitles). It also adds and refines the inference
cases for both credits and subtitles.

Duplicate of musescore#8678, part 8
  • Loading branch information
iveshenry18 authored and Jojo-Schmitz committed Sep 29, 2021
1 parent 9fbdd2c commit c21cb5f
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 43 deletions.
62 changes: 46 additions & 16 deletions importexport/musicxml/importmxmlpass1.cpp
Expand Up @@ -183,7 +183,7 @@ static void copyOverlapData(VoiceOverlapDetector& vod, VoiceList& vcLst)
//---------------------------------------------------------

MusicXMLParserPass1::MusicXMLParserPass1(Score* score, MxmlLogger* logger)
: _divs(0), _score(score), _logger(logger), _hasBeamingInfo(false)
: _divs(0), _score(score), _logger(logger), _hasBeamingInfo(false), _hasInferredHeaderText(false)
{
// nothing
}
Expand Down Expand Up @@ -675,27 +675,52 @@ bool isLikelySubtitleText(const QString& text, const bool caseInsensitive = true
QRegularExpression::PatternOption caseOption = caseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption;
return (text.trimmed().contains(QRegularExpression("^[Ff]rom\\s+(?!$)", caseOption))
|| text.trimmed().contains(QRegularExpression("^Theme from\\s+(?!$)", caseOption))
|| text.trimmed().contains(QRegularExpression("(Op\\.?\\s?\\d+)\\s?(No\\.?\\s?\\d+)?", caseOption))
|| text.trimmed().contains(QRegularExpression("^\\(.*[Ff]rom\\s.*\\)$", caseOption)));
|| text.trimmed().contains(QRegularExpression("(((Op\\.?\\s?\\d+)|(No\\.?\\s?\\d+))\\s?)+", caseOption))
|| text.trimmed().contains(QRegularExpression("\\(.*[Ff]rom\\s.*\\)", caseOption)));
}

//---------------------------------------------------------
// isLikelyCreditText
//---------------------------------------------------------

bool isLikelyCreditText(const QString& text, const bool caseInsensitive = true)
{
QRegularExpression::PatternOption caseOption = caseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption;
return (text.trimmed().contains(QRegularExpression("^((Words|Music|Lyrics|Composed),?(\\sand|\\s&|\\s&)?\\s)*[Bb]y\\s+(?!$)", caseOption))
|| text.trimmed().contains(QRegularExpression("^(Traditional|Trad\\.)", caseOption)));
}

//---------------------------------------------------------
// inferSubTitleFromTitle
//---------------------------------------------------------

// Extracts a likely subtitle from the title string
// Returns the inferred subtitle
// Extracts likely subtitle and credits from the title string

static QString inferSubTitleFromTitle(const QString& title)
static void inferFromTitle(QString& title, QString& inferredSubtitle, QString& inferredCredits)
{
QString inferredSubTitle = "";
for (auto line : title.split(QRegularExpression("\\n"))) {
QStringList subtitleLines;
QStringList creditLines;
QStringList titleLines = title.split(QRegularExpression("\\n"));
for (int i = titleLines.length() - 1; i > 0; --i) {
QString line = titleLines[i];
if (isLikelyCreditText(line, true)) {
for (int j = titleLines.length() - 1; j >= i; --j) {
creditLines.push_front(titleLines[j]);
titleLines.removeAt(j);
}
continue;
}
if (isLikelySubtitleText(line, true)) {
inferredSubTitle = line;
break;
for (int j = titleLines.length() - 1; j >= i; --j) {
subtitleLines.push_front(titleLines[j]);
titleLines.removeAt(j);
}
continue;
}
}
return inferredSubTitle;
title = titleLines.join("\n");
inferredSubtitle = subtitleLines.join("\n");
inferredCredits = creditLines.join("\n");
}
//---------------------------------------------------------
// addCreditWords
Expand Down Expand Up @@ -759,11 +784,12 @@ static VBox* addCreditWords(Score* const score, const CreditWordsList& crWords,
// createMeasuresAndVboxes
//---------------------------------------------------------

static void createDefaultHeader(Score* const score)
void MusicXMLParserPass1::createDefaultHeader(Score* const score)
{
QString strTitle;
QString strSubTitle;
QString inferredStrSubTitle;
QString inferredStrComposer;
QString strComposer;
QString strPoet;
QString strTranslator;
Expand All @@ -772,21 +798,25 @@ static void createDefaultHeader(Score* const score)
strTitle = score->metaTag("movementTitle");
if (strTitle.isEmpty())
strTitle = score->metaTag("workTitle");
inferredStrSubTitle = inferSubTitleFromTitle(strTitle);
inferFromTitle(strTitle, inferredStrSubTitle, inferredStrComposer);
}
if (!(score->metaTag("movementNumber").isEmpty() && score->metaTag("workNumber").isEmpty())) {
strSubTitle = score->metaTag("movementNumber");
if (strSubTitle.isEmpty())
strSubTitle = score->metaTag("workNumber");
}
else if (!inferredStrSubTitle.isEmpty()) {
if (!inferredStrSubTitle.isEmpty()) {
strSubTitle = inferredStrSubTitle;
strTitle.replace(inferredStrSubTitle, "");
_hasInferredHeaderText = true;
}
QString metaComposer = score->metaTag("composer");
QString metaPoet = score->metaTag("poet");
QString metaTranslator = score->metaTag("translator");
if (!metaComposer.isEmpty()) strComposer = metaComposer;
if (!inferredStrComposer.isEmpty()) {
strComposer = inferredStrComposer;
_hasInferredHeaderText = true;
}
if (metaPoet.isEmpty()) metaPoet = score->metaTag("lyricist");
if (!metaPoet.isEmpty()) strPoet = metaPoet;
if (!metaTranslator.isEmpty()) strTranslator = metaTranslator;
Expand All @@ -807,7 +837,7 @@ static void createDefaultHeader(Score* const score)
Create required measures with correct number, start tick and length for Score \a score.
*/

static void createMeasuresAndVboxes(Score* const score,
void MusicXMLParserPass1::createMeasuresAndVboxes(Score* const score,
const QVector<Fraction>& ml, const QVector<Fraction>& ms,
const std::set<int>& systemStartMeasureNrs,
const std::set<int>& pageStartMeasureNrs,
Expand Down
11 changes: 11 additions & 0 deletions importexport/musicxml/importmxmlpass1.h
Expand Up @@ -104,6 +104,7 @@ using MxmlTupletStates = std::map<QString, MxmlTupletState>;

void determineTupletFractionAndFullDuration(const Fraction duration, Fraction& fraction, Fraction& fullDuration);
Fraction missingTupletDuration(const Fraction duration);
bool isLikelyCreditText(const QString& text, const bool caseInsensitive);
bool isLikelySubtitleText(const QString& text, const bool caseInsensitive);


Expand Down Expand Up @@ -169,8 +170,17 @@ class MusicXMLParserPass1 {
bool hasBeamingInfo() const { return _hasBeamingInfo; }
bool isVocalStaff(const QString& id) const { return _parts[id].isVocalStaff(); }
static VBox* createAndAddVBoxForCreditWords(Score* const score, const int miny = 0, const int maxy = 75);
void createDefaultHeader(Score* const score);
void createMeasuresAndVboxes(Score* const score,
const QVector<Fraction>& ml, const QVector<Fraction>& ms,
const std::set<int>& systemStartMeasureNrs,
const std::set<int>& pageStartMeasureNrs,
const CreditWordsList& crWords,
const QSize pageSize);
QString supportsTranspose() const { return _supportsTranspose; }
void addInferredTranspose(const QString& partId);
void setHasInferredHeaderText(bool b) { _hasInferredHeaderText = b; }
bool hasInferredHeaderText() const { return _hasInferredHeaderText; }

private:
// functions
Expand All @@ -191,6 +201,7 @@ class MusicXMLParserPass1 {
MxmlLogger* _logger; ///< Error logger
bool _hasBeamingInfo; ///< Whether the score supports or contains beaming info
QString _supportsTranspose; ///< Whether the score supports transposition info
bool _hasInferredHeaderText;

// part specific data (TODO: move to part-specific class)
Fraction _timeSigDura; ///< Measure duration according to last timesig read
Expand Down
12 changes: 6 additions & 6 deletions importexport/musicxml/importmxmlpass2.cpp
Expand Up @@ -1317,7 +1317,7 @@ static void handleSpannerStop(SLine* cur_sp, int track2, const Fraction& tick, M
//---------------------------------------------------------

MusicXMLParserPass2::MusicXMLParserPass2(Score* score, MusicXMLParserPass1& pass1, MxmlLogger* logger)
: _divs(0), _score(score), _pass1(pass1), _logger(logger), _hasInferredHeaderText(false)
: _divs(0), _score(score), _pass1(pass1), _logger(logger)
{
// nothing
}
Expand Down Expand Up @@ -1898,7 +1898,7 @@ void MusicXMLParserPass2::scorePartwise()
_score->connectArpeggios();
_score->fixupLaissezVibrer();
cleanFretDiagrams(_score->firstMeasure());
if (_hasInferredHeaderText)
if (_pass1.hasInferredHeaderText())
reformatHeaderVBox(_score->measures()->first());
cleanUpLayoutBreaks(_score, _logger);
}
Expand Down Expand Up @@ -2923,14 +2923,14 @@ void MusicXMLParserDirection::direction(const QString& partId,
else if (isLikelyCredit(tick)) {
Text* inferredText = addTextToHeader(Tid::COMPOSER);
if (inferredText) {
_pass2.setHasInferredHeaderText(true);
_pass1.setHasInferredHeaderText(true);
hideRedundantHeaderText(inferredText, {"lyricist", "composer", "poet"});
}
}
else if (isLikelySubtitle(tick)) {
Text* inferredText = addTextToHeader(Tid::SUBTITLE);
if (inferredText) {
_pass2.setHasInferredHeaderText(true);
_pass1.setHasInferredHeaderText(true);
if (_score->metaTag("source").isEmpty())
_score->setMetaTag("source", inferredText->plainText());
hideRedundantHeaderText(inferredText, {"source"});
Expand Down Expand Up @@ -3439,7 +3439,7 @@ bool MusicXMLParserDirection::isLikelyCredit(const Fraction& tick) const
&& _rehearsalText == ""
&& _metroText == ""
&& _tpoSound < 0.1
&& _wordsText.contains(QRegularExpression("^\\s*((Words|Music|Lyrics),?(\\sand|\\s&amp;)?\\s)*[Bb]y\\s+(?!$)"));
&& isLikelyCreditText(_wordsText, false);
}

//---------------------------------------------------------
Expand Down Expand Up @@ -7604,7 +7604,7 @@ void MusicXMLParserNotations::tuplet()

MusicXMLParserDirection::MusicXMLParserDirection(QXmlStreamReader& e,
Score* score,
const MusicXMLParserPass1& pass1,
MusicXMLParserPass1& pass1,
MusicXMLParserPass2& pass2,
MxmlLogger* logger)
: _e(e), _score(score), _pass1(pass1), _pass2(pass2), _logger(logger),
Expand Down
6 changes: 2 additions & 4 deletions importexport/musicxml/importmxmlpass2.h
Expand Up @@ -267,7 +267,6 @@ class MusicXMLParserPass2 {
MusicXmlExtendedSpannerDesc& getSpanner(const MusicXmlSpannerDesc& desc);
void clearSpanner(const MusicXmlSpannerDesc& desc);
void deleteHandledSpanner(SLine* const& spanner);
void setHasInferredHeaderText(bool b) { _hasInferredHeaderText = b; }

private:
void initPartState(const QString& partId);
Expand Down Expand Up @@ -339,7 +338,6 @@ class MusicXMLParserPass2 {
std::map<int, Tie*> _ties;
Volta* _lastVolta;
bool _hasDrumset; ///< drumset defined TODO: move to pass 1
bool _hasInferredHeaderText;

MusicXmlSpannerMap _spanners;

Expand All @@ -360,7 +358,7 @@ class MusicXMLParserPass2 {

class MusicXMLParserDirection {
public:
MusicXMLParserDirection(QXmlStreamReader& e, Score* score, const MusicXMLParserPass1& pass1, MusicXMLParserPass2& pass2, MxmlLogger* logger);
MusicXMLParserDirection(QXmlStreamReader& e, Score* score, MusicXMLParserPass1& pass1, MusicXMLParserPass2& pass2, MxmlLogger* logger);
void direction(const QString& partId, Measure* measure, const Fraction& tick, const int divisions,
MusicXmlSpannerMap& spanners, DelayedDirectionsList& delayedDirections, InferredFingeringsList& inferredFingerings);
qreal totalY() const { return _defaultY + _relativeY; }
Expand All @@ -369,7 +367,7 @@ class MusicXMLParserDirection {
private:
QXmlStreamReader& _e;
Score* const _score; // the score
const MusicXMLParserPass1& _pass1; // the pass1 results
MusicXMLParserPass1& _pass1; // the pass1 results
MusicXMLParserPass2& _pass2; // the pass2 results
MxmlLogger* _logger; ///< Error logger

Expand Down
10 changes: 1 addition & 9 deletions mtest/musicxml/io/testInferredCredits2.xml
Expand Up @@ -4,6 +4,7 @@
<work>
<work-title>Inferred Credits 2
from MUSESCORE: the musical: a tone poem
Words &amp; Music by also Henry Ives (and ampersands)
</work-title>
</work>
<identification>
Expand Down Expand Up @@ -84,15 +85,6 @@ from MUSESCORE: the musical: a tone poem
<line>2</line>
</clef>
</attributes>
<direction placement="above">
<direction-type>
<words default-y="34.66" relative-y="10.00">Words &amp; Music by Henry Ives (and ampersands)
</words>
<words>
</words>
<words></words>
</direction-type>
</direction>
<direction placement="above">
<direction-type>
<words default-y="34.66" relative-y="10.00">Lyrics to be sung by candlelight
Expand Down
11 changes: 3 additions & 8 deletions mtest/musicxml/io/testInferredCredits2_ref.mscx
Expand Up @@ -52,6 +52,7 @@
<metaTag name="workNumber"></metaTag>
<metaTag name="workTitle">Inferred Credits 2
from MUSESCORE: the musical: a tone poem
Words &amp; Music by also Henry Ives (and ampersands)
</metaTag>
<Part>
<Staff id="1">
Expand Down Expand Up @@ -119,7 +120,7 @@ from MUSESCORE: the musical: a tone poem
</Part>
<Staff id="1">
<VBox>
<height>19.8846</height>
<height>18.03</height>
<Text>
<style>Title</style>
<text>Inferred Credits 2</text>
Expand All @@ -130,19 +131,13 @@ from MUSESCORE: the musical: a tone poem
<text>from MUSESCORE: the musical: a tone poem</text>
</Text>
<Text>
<visible>0</visible>
<style>Composer</style>
<text>Henry Ives</text>
<text>Words &amp; Music by also Henry Ives (and ampersands)</text>
</Text>
<Text>
<visible>0</visible>
<style>Lyricist</style>
<text>Henry Ives</text>
</Text>
<Text>
<style>Composer</style>
<text>Words &amp; Music by Henry Ives (and ampersands)</text>
</Text>
<Text>
<style>Subtitle</style>
<offset x="0" y="17.1591"/>
Expand Down

0 comments on commit c21cb5f

Please sign in to comment.