Skip to content

Commit

Permalink
ENG-6: Add partial default-y support
Browse files Browse the repository at this point in the history
Due to inconsistencies in the MusicXML spec and its implementation
with regards to default-y and relative-y, precise implementation is
not currently reasonable. Instead, this commit adds two essential
functions which provide a sufficient amount of support for the vertical
layout of direction elements:

- In the case that the placement (above/below) isn't specified,
this is inferred by the value of default-y + relative-y (here called
totalY).
- Rather than being added to their Segment upon read, direction elements
with a default-y or relative-y specified are collected, sorted by
totalY, then added to their Segment in ascending order. Autoplace places
elements in the order they are added, so this allows at least the
ordering of default-y and relative-y to be respected
  • Loading branch information
iveshenry18 committed Jun 4, 2021
1 parent 73807f9 commit 13e6c0f
Show file tree
Hide file tree
Showing 7 changed files with 1,227 additions and 7 deletions.
1 change: 1 addition & 0 deletions importexport/musicxml/exportxml.cpp
Expand Up @@ -6085,6 +6085,7 @@ static void annotationsWithoutNote(ExportMusicXml* exp, const int strack, const
if (!element->isFiguredBass() && !element->isHarmony()) { // handled elsewhere
const auto wtrack = findTrackForAnnotations(element->track(), segment); // track to write annotation
if (strack <= element->track() && element->track() < (strack + VOICES * staves) && wtrack < 0)
// TODO: the logic for staves here appears to be incorrect.
commonAnnotations(exp, element, staves > 1 ? 1 : 0);
}
}
Expand Down
43 changes: 37 additions & 6 deletions importexport/musicxml/importmxmlpass2.cpp
Expand Up @@ -1995,6 +1995,8 @@ void MusicXMLParserPass2::measure(const QString& partId,
FiguredBassList fbl; // List of figured bass elements under a single note
MxmlTupletStates tupletStates; // Tuplet state for each voice in the current part
Tuplets tuplets; // Current tuplet for each voice in the current part
DelayedDirectionsList delayedDirections; // Directions to be added to score *after* collecting all and sorting


// collect candidates for courtesy accidentals to work out at measure end
QMap<Note*, int> alterMap;
Expand All @@ -2004,7 +2006,7 @@ void MusicXMLParserPass2::measure(const QString& partId,
attributes(partId, measure, time + mTime);
else if (_e.name() == "direction") {
MusicXMLParserDirection dir(_e, _score, _pass1, *this, _logger);
dir.direction(partId, measure, time + mTime, _divs, _spanners);
dir.direction(partId, measure, time + mTime, _divs, _spanners, delayedDirections);
}
else if (_e.name() == "figured-bass") {
FiguredBass* fb = figuredBass();
Expand Down Expand Up @@ -2127,6 +2129,18 @@ void MusicXMLParserPass2::measure(const QString& partId,
removeBeam(beam);
}

// Sort and add delayed directions
std::sort(delayedDirections.begin(), delayedDirections.end(),
// Lambda: sort by absolute value of totalY
[](const MusicXMLDelayedDirectionElement* a, const MusicXMLDelayedDirectionElement* b) -> bool {
return std::abs(a->totalY()) < std::abs(b->totalY());
}
);
for (auto direction : delayedDirections) {
direction->addElem();
delete direction;
}

// TODO:
// - how to handle _timeSigDura.isZero (shouldn't happen ?)
// - how to handle unmetered music
Expand Down Expand Up @@ -2376,6 +2390,12 @@ static Fraction calcTicks(const QString& text, int divs, MxmlLogger* logger, con
return dura;
}


void MusicXMLDelayedDirectionElement::addElem()
{
addElemOffset(_element, _track, _placement, _measure, _tick);
}

//---------------------------------------------------------
// direction
//---------------------------------------------------------
Expand All @@ -2388,7 +2408,8 @@ void MusicXMLParserDirection::direction(const QString& partId,
Measure* measure,
const Fraction& tick,
const int divisions,
MusicXmlSpannerMap& spanners)
MusicXmlSpannerMap& spanners,
DelayedDirectionsList& delayedDirections)
{
Q_ASSERT(_e.isStartElement() && _e.name() == "direction");
//qDebug("direction tick %s", qPrintable(tick.print()));
Expand Down Expand Up @@ -2486,8 +2507,17 @@ void MusicXMLParserDirection::direction(const QString& partId,
t->setFrameRound(0);
}

//TODO:ws if (_hasDefaultY) t->textStyle().setYoff(_defaultY);
addElemOffset(t, track, placement, measure, tick + _offset);
if (placement == "" && hasTotalY())
placement = totalY() < 0 ? "above" : "below";
if (hasTotalY()) {
// Add element to score later, after collecting all the others and sorting by default-y
// This allows default-y to be at least respected by the order of elements
MusicXMLDelayedDirectionElement* delayedDirection = new MusicXMLDelayedDirectionElement(totalY(), t, track, placement, measure, tick + _offset);
delayedDirections.push_back(delayedDirection);
}
else {
addElemOffset(t, track, placement, measure, tick + _offset);
}
}
}
else if (_tpoSound > 0) {
Expand All @@ -2504,7 +2534,7 @@ void MusicXMLParserDirection::direction(const QString& partId,
t->setTempo(tpo);
t->setFollowText(true);

// TBD may want ro use tick + _offset if sound is affected
// TBD may want to use tick + _offset if sound is affected
_score->setTempo(tick, tpo);

addElemOffset(t, track, placement, measure, tick + _offset);
Expand Down Expand Up @@ -2598,6 +2628,7 @@ void MusicXMLParserDirection::directionType(QList<MusicXmlSpannerDesc>& starts,

while (_e.readNextStartElement()) {
_defaultY = _e.attributes().value("default-y").toDouble(&_hasDefaultY) * -0.1;
_relativeY = _e.attributes().value("relative-y").toDouble(&_hasRelativeY) * -0.1;
QString number = _e.attributes().value("number").toString();
int n = 0;
if (number != "") {
Expand Down Expand Up @@ -6515,7 +6546,7 @@ MusicXMLParserDirection::MusicXMLParserDirection(QXmlStreamReader& e,
MusicXMLParserPass2& pass2,
MxmlLogger* logger)
: _e(e), _score(score), _pass1(pass1), _pass2(pass2), _logger(logger),
_hasDefaultY(false), _defaultY(0.0), _coda(false), _segno(false),
_hasDefaultY(false), _defaultY(0.0), _hasRelativeY(false), _relativeY(0.0), _coda(false), _segno(false),
_tpoMetro(0), _tpoSound(0), _offset(0, 1)
{
// nothing
Expand Down
34 changes: 33 additions & 1 deletion importexport/musicxml/importmxmlpass2.h
Expand Up @@ -179,7 +179,9 @@ class Glissando;
class Pedal;
class Trill;
class MxmlLogger;
class MusicXMLDelayedDirectionElement;

using DelayedDirectionsList = QList<MusicXMLDelayedDirectionElement*>;
using SlurStack = std::array<SlurDesc, MAX_NUMBER_LEVEL>;
using TrillStack = std::array<Trill*, MAX_NUMBER_LEVEL>;
using BracketsStack = std::array<MusicXmlExtendedSpannerDesc, MAX_NUMBER_LEVEL>;
Expand Down Expand Up @@ -340,7 +342,8 @@ class MusicXMLParserPass2 {
class MusicXMLParserDirection {
public:
MusicXMLParserDirection(QXmlStreamReader& e, Score* score, const MusicXMLParserPass1& pass1, MusicXMLParserPass2& pass2, MxmlLogger* logger);
void direction(const QString& partId, Measure* measure, const Fraction& tick, const int divisions, MusicXmlSpannerMap& spanners);
void direction(const QString& partId, Measure* measure, const Fraction& tick, const int divisions, MusicXmlSpannerMap& spanners, DelayedDirectionsList& delayedDirections);
qreal totalY() const { return _defaultY + _relativeY; }

private:
QXmlStreamReader& _e;
Expand All @@ -364,6 +367,9 @@ class MusicXMLParserDirection {
QString _sndFine;
bool _hasDefaultY;
qreal _defaultY;
bool _hasRelativeY;
qreal _relativeY;
bool hasTotalY() { return _hasRelativeY || _hasDefaultY; }
bool _coda;
bool _segno;
double _tpoMetro; // tempo according to metronome
Expand All @@ -384,5 +390,31 @@ class MusicXMLParserDirection {
void skipLogCurrElem();
};

//---------------------------------------------------------
// MusicXMLDelayedDirectionElement
//---------------------------------------------------------
/**
Helper class to allow Direction elements to be sorted by _totalY
before being added to the score.
*/

class MusicXMLDelayedDirectionElement {
public:
MusicXMLDelayedDirectionElement(qreal totalY, Element* element, int track,
QString placement, Measure* measure, Fraction tick) :
_totalY(totalY), _element(element), _track(track), _placement(placement),
_measure(measure), _tick(tick) {}
void addElem();
qreal totalY() const { return _totalY; }

private:
qreal _totalY;
Element* _element;
int _track;
QString _placement;
Measure* _measure;
Fraction _tick;
};

} // namespace Ms
#endif
Binary file added mtest/musicxml/io/testTextOrder.pdf
Binary file not shown.

0 comments on commit 13e6c0f

Please sign in to comment.