Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions importexport/musicxml/importmxmlpass1.cpp
Original file line number Diff line number Diff line change
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 @@ -494,7 +494,7 @@ static void addText(VBox* vbx, Score* s, const QString strTxt, const Tid stl)
{
if (!strTxt.isEmpty()) {
Text* text = new Text(s, stl);
text->setXmlText(strTxt);
text->setXmlText(strTxt.trimmed());
vbx->add(text);
}
}
Expand All @@ -512,7 +512,7 @@ static void addText2(VBox* vbx, Score* s, const QString strTxt, const Tid stl, c
{
if (!strTxt.isEmpty()) {
Text* text = new Text(s, stl);
text->setXmlText(strTxt);
text->setXmlText(strTxt.trimmed());
text->setAlign(align);
text->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
text->setOffset(QPointF(0.0, yoffs));
Expand Down Expand Up @@ -667,6 +667,62 @@ static bool mustAddWordToVbox(const QString& creditType)
}

//---------------------------------------------------------
// isLikelySubtitleText
//---------------------------------------------------------

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+)|(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 likely subtitle and credits from the title string

static void inferFromTitle(QString& title, QString& inferredSubtitle, QString& inferredCredits)
{
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)) {
for (int j = titleLines.length() - 1; j >= i; --j) {
subtitleLines.push_front(titleLines[j]);
titleLines.removeAt(j);
}
continue;
}
}
title = titleLines.join("\n");
inferredSubtitle = subtitleLines.join("\n");
inferredCredits = creditLines.join("\n");
}
//---------------------------------------------------------
// addCreditWords
//---------------------------------------------------------

Expand Down Expand Up @@ -728,10 +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 @@ -740,16 +798,25 @@ static void createDefaultHeader(Score* const score)
strTitle = score->metaTag("movementTitle");
if (strTitle.isEmpty())
strTitle = score->metaTag("workTitle");
inferFromTitle(strTitle, inferredStrSubTitle, inferredStrComposer);
}
if (!(score->metaTag("movementNumber").isEmpty() && score->metaTag("workNumber").isEmpty())) {
strSubTitle = score->metaTag("movementNumber");
if (strSubTitle.isEmpty())
strSubTitle = score->metaTag("workNumber");
}
if (!inferredStrSubTitle.isEmpty()) {
strSubTitle = 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 @@ -770,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
12 changes: 12 additions & 0 deletions importexport/musicxml/importmxmlpass1.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ 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 @@ -168,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 @@ -190,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
143 changes: 133 additions & 10 deletions importexport/musicxml/importmxmlpass2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,48 @@ static void cleanFretDiagrams(Measure* measure)
}
}
}

//---------------------------------------------------------
// reformatHeaderVBox
//---------------------------------------------------------
/**
Due to inconsistencies with spacing and inferred text,
the header VBox frequently has collisions. This cleans
those (as a temporary fix for a more robust collision-prevention
system in Boxes).
*/

static void reformatHeaderVBox(MeasureBase* mb)
{
if (!mb->isVBox())
return;

VBox* headerVBox = toVBox(mb);
Score* score = mb->score();
qreal totalHeight = 0;
qreal offsetHeight = 0;
qreal lineSpacingMultiplier = 1.2;

for (auto e : headerVBox->el()) {
if (!e->isText())
continue;
Text* t = toText(e);
t->layout();

totalHeight += t->height();
if (Align(t->align() & Align::VMASK) == Align::TOP) {
totalHeight += t->lineHeight() * lineSpacingMultiplier;
t->setOffset(t->offset().x(), offsetHeight);
t->setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);
offsetHeight += t->height();
offsetHeight += t->lineHeight() * lineSpacingMultiplier;
}
}

headerVBox->setBoxHeight(Spatium(totalHeight / score->spatium()));
headerVBox->setPropertyFlags(Pid::BOX_HEIGHT, PropertyFlags::UNSTYLED);
}

//---------------------------------------------------------
// initPartState
//---------------------------------------------------------
Expand Down Expand Up @@ -1853,6 +1895,8 @@ void MusicXMLParserPass2::scorePartwise()
_score->connectArpeggios();
_score->fixupLaissezVibrer();
cleanFretDiagrams(_score->firstMeasure());
if (_pass1.hasInferredHeaderText())
reformatHeaderVBox(_score->measures()->first());
cleanUpLayoutBreaks(_score, _logger);
}

Expand Down Expand Up @@ -2873,14 +2917,24 @@ void MusicXMLParserDirection::direction(const QString& partId,
if (isLyricBracket())
return;
else if (isLikelyCredit(tick)) {
Text* t = new Text(_score, Tid::COMPOSER);
t->setXmlText(_wordsText.trimmed());
auto firstMeasure = _score->measures()->first();
VBox* vbox = firstMeasure->isVBox() ? toVBox(firstMeasure) : MusicXMLParserPass1::createAndAddVBoxForCreditWords(_score);
t->layout();
vbox->layout();
vbox->setBoxHeight(vbox->boxHeight() + Spatium(t->height()/_score->spatium())/2); // add some height
vbox->add(t);
Text* inferredText = addTextToHeader(Tid::COMPOSER);
if (inferredText) {
_pass1.setHasInferredHeaderText(true);
hideRedundantHeaderText(inferredText, {"lyricist", "composer", "poet"});
}
}
else if (isLikelySubtitle(tick)) {
Text* inferredText = addTextToHeader(Tid::SUBTITLE);
if (inferredText) {
_pass1.setHasInferredHeaderText(true);
if (_score->metaTag("source").isEmpty())
_score->setMetaTag("source", inferredText->plainText());
hideRedundantHeaderText(inferredText, {"source"});
}
}
else if (isLikelyLegallyDownloaded(tick)) {
// Ignore (TBD: print to footer?)
return;
}
else if (_wordsText != "" || _rehearsalText != "" || _metroText != "") {
TextBase* t = 0;
Expand Down Expand Up @@ -3353,6 +3407,19 @@ bool MusicXMLParserDirection::isLikelyFingering() const
&& _tpoSound < 0.1;
}

//---------------------------------------------------------
// isLikelySubtitle
//---------------------------------------------------------

bool MusicXMLParserDirection::isLikelySubtitle(const Fraction& tick) const
{
return (tick + _offset < Fraction(5, 1)) // Only early in the piece
&& _rehearsalText == ""
&& _metroText == ""
&& _tpoSound < 0.1
&& isLikelySubtitleText(_wordsText, false);
}

//---------------------------------------------------------
// isLikelyCredit
//---------------------------------------------------------
Expand All @@ -3363,10 +3430,66 @@ bool MusicXMLParserDirection::isLikelyCredit(const Fraction& tick) const
&& _rehearsalText == ""
&& _metroText == ""
&& _tpoSound < 0.1
&& _wordsText.contains(QRegularExpression("^\\s*((Words|Music|Lyrics).*)*by\\s+([A-Z][a-zA-Zö'’-]+\\s[A-Z][a-zA-Zös'’-]+.*)+"));
&& isLikelyCreditText(_wordsText, false);
}

//---------------------------------------------------------
// isLikelyLegallyDownloaded
//---------------------------------------------------------

bool MusicXMLParserDirection::isLikelyLegallyDownloaded(const Fraction& tick) const
{
return (tick + _offset < Fraction(5, 1)) // Only early in the piece
&& _rehearsalText == ""
&& _metroText == ""
&& _tpoSound < 0.1
&& _wordsText.contains(QRegularExpression("This music has been legally downloaded\\.\\sDo not photocopy\\."));
}

//---------------------------------------------------------
// addTextToHeader
//---------------------------------------------------------

Text* MusicXMLParserDirection::addTextToHeader(const Tid tid)
{
Text* t = new Text(_score, tid);
t->setXmlText(_wordsText.trimmed());
auto firstMeasure = _score->measures()->first();
VBox* vbox = firstMeasure->isVBox() ? toVBox(firstMeasure) : MusicXMLParserPass1::createAndAddVBoxForCreditWords(_score);
t->layout();
vbox->layout();
vbox->setBoxHeight(vbox->boxHeight() + Spatium(t->height()/_score->spatium())); // add the height of the text
vbox->add(t);
return t;
}

//---------------------------------------------------------
// hideRedundantHeaderText
// After inferring header text, hide redundant text.
// Redundant text is detected by checking all the
// Text elements of the inferred text's VBox against
// the contents of the eligible metaTags
//---------------------------------------------------------

void MusicXMLParserDirection::hideRedundantHeaderText(const Text* inferredText, const std::vector<QString> metaTags)
{
if (!inferredText->parent()->isVBox())
return;

for (auto e : toVBox(inferredText->parent())->el()) {
if (e == inferredText || !e->isText())
continue;

Text* t = toText(e);
for (auto metaTag : metaTags) {
if (t->plainText() == _score->metaTag(metaTag)) {
t->setVisible(false);
continue;
}
}
}
}
//---------------------------------------------------------
// MusicXMLInferredFingering
//---------------------------------------------------------

Expand Down Expand Up @@ -7471,7 +7594,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
8 changes: 6 additions & 2 deletions importexport/musicxml/importmxmlpass2.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,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 @@ -367,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 Expand Up @@ -410,6 +410,10 @@ class MusicXMLParserDirection {
bool isLikelyFingering() const;
bool isLikelyCredit(const Fraction& tick) const;
bool isLyricBracket() const;
bool isLikelySubtitle(const Fraction& tick) const;
bool isLikelyLegallyDownloaded(const Fraction& tick) const;
Text* addTextToHeader(const Tid tid);
void hideRedundantHeaderText(const Text* inferredText, const std::vector<QString> metaTags);
void textToDynamic(QString& text) const;
bool directionToDynamic();
bool isLikelyTempoText();
Expand Down
Binary file removed mtest/musicxml/io/testInferredCredits.pdf
Binary file not shown.
Binary file added mtest/musicxml/io/testInferredCredits1.pdf
Binary file not shown.
Loading