Skip to content
Permalink
Browse files

fix #202271: Copy-paste sextuplets/octuplets and their removal leads …

…to corruption
  • Loading branch information...
lasconic committed Jun 5, 2017
1 parent 25c3cbb commit 979a28a9869486064a07fdd76ba2eef89f67271c
Showing with 99 additions and 15 deletions.
  1. +13 −0 libmscore/durationtype.cpp
  2. +1 −0 libmscore/durationtype.h
  3. +1 −10 libmscore/measure.cpp
  4. +54 −2 libmscore/tuplet.cpp
  5. +1 −0 libmscore/tuplet.h
  6. +21 −0 libmscore/xml.cpp
  7. +1 −0 libmscore/xml.h
  8. +7 −3 mscore/tupletdialog.cpp
@@ -806,5 +806,18 @@ void TDuration::setType(DurationType t)
if (_val == DurationType::V_MEASURE)
_dots = 0;
}

//---------------------------------------------------------
// isValid
//---------------------------------------------------------

bool TDuration::isValid(Fraction f)
{
TDuration t;
t.setType(DurationType::V_LONG);
t.setDots(MAX_DOTS);
t.truncateToFraction(f, 4);
return ((t.fraction() - f).numerator() == 0);
}
}

@@ -75,6 +75,7 @@ class TDuration {
void setDots(int v);
Fraction fraction() const;
QString durationTypeUserName() const;
static bool isValid(Fraction f);
};

QList<TDuration> toDurationList(Fraction l, bool useDots, int maxDots = MAX_DOTS, bool printRestRemains = true);
@@ -2407,16 +2407,7 @@ void Measure::read(XmlReader& e, int staffIdx)

}
}
foreach (Tuplet* tuplet, e.tuplets()) {
if (tuplet->elements().isEmpty()) {
// this should not happen and is a sign of input file corruption
qDebug("Measure:read(): empty tuplet id %d (%p), input file corrupted?",
tuplet->id(), tuplet);
delete tuplet;
}
else
tuplet->setParent(this);
}
e.checkTuplets();
}

//---------------------------------------------------------
@@ -688,8 +688,9 @@ void Tuplet::read(XmlReader& e)
else if (!DurationElement::readProperties(e))
e.unknown();
}
Fraction f(_ratio.reduced().denominator(), _baseLen.fraction().denominator());
setDuration(f);
Fraction r = (_ratio == 1) ? _ratio : _ratio.reduced();
Fraction f(r.denominator(), _baseLen.fraction().denominator());
setDuration(f.reduced());
if (bl != -1) { // obsolete
TDuration d;
d.setVal(bl);
@@ -970,5 +971,56 @@ QVariant Tuplet::propertyDefault(P_ID id) const
}
}

//---------------------------------------------------------
// sanitizeTuplet
/// Check validity of tuplets and coherence between duration
/// and baselength. Needed for importing old files due to a bug
/// in the released version for corner-case tuplets.
/// See issue #136406 and Pull request #2881
//---------------------------------------------------------

void Tuplet::sanitizeTuplet()
{
Fraction tupletDuration = duration().reduced();
Fraction baseLenDuration = (Fraction(ratio().denominator(),1) * baseLen().fraction()).reduced();
if ((tupletDuration - baseLenDuration).reduced().numerator() == 0)
return;
// Mismatch between the duration and the duration computed from the base length.
// A tentative will now be made to retrieve the correct duration by summing up all the
// durations of the elements constituting the tuplet. This does not work for
// not-completely filled tuplets, such as tuplets in voices > 0 with
// gaps (for example, a tuplet in second voice with a deleted chordrest element)
Fraction testDuration(0,1);
for (DurationElement* de : elements()) {
if (de == 0)
continue;
Fraction elementDuration(0,1);
if (de->type() == Element::Type::TUPLET){
Tuplet* t = static_cast<Tuplet*>(de);
t->sanitizeTuplet();
elementDuration = t->duration();
}
else {
elementDuration = de->duration();
}
testDuration += elementDuration;
}
testDuration = testDuration / ratio();
testDuration.reduce();
if (((testDuration - tupletDuration).reduced().numerator() != 0) || ((testDuration - baseLenDuration).reduced().numerator() != 0)) {
Fraction f = testDuration * Fraction(1, ratio().denominator());
f.reduce();
Fraction fbl(1, f.denominator());
if (TDuration::isValid(fbl)) {
setDuration(testDuration);
setBaseLen(fbl);
qDebug("Tuplet %p sanitized",this);
}
else {
qDebug("Impossible to sanitize the tuplet");
}
}
}

}

@@ -124,6 +124,7 @@ class Tuplet : public DurationElement {
QVariant getProperty(P_ID propertyId) const;
bool setProperty(P_ID propertyId, const QVariant& v);
QVariant propertyDefault(P_ID id) const;
void sanitizeTuplet();
};


@@ -231,6 +231,27 @@ bool XmlReader::readBool()
return val;
}

//---------------------------------------------------------
// checkTuplets
//---------------------------------------------------------

void XmlReader::checkTuplets()
{
for (Tuplet* tuplet : tuplets()) {
if (tuplet->elements().empty()) {
// this should not happen and is a sign of input file corruption
qDebug("Measure:read(): empty tuplet id %d (%p), input file corrupted?",
tuplet->id(), tuplet);
delete tuplet;
}
else {
//sort tuplet elements. Needed for nested tuplets #22537
tuplet->sortElements();
tuplet->sanitizeTuplet();
}
}
}

//---------------------------------------------------------
// compareProperty
//---------------------------------------------------------
@@ -129,6 +129,7 @@ class XmlReader : public XmlStreamReader {
void setTransposeDiatonic(int v) { _transpose.diatonic = v; }

QList<std::pair<int, ClefType>>& clefs(int idx);
void checkTuplets();
};

//---------------------------------------------------------
@@ -107,10 +107,10 @@ Tuplet* MuseScore::tupletDialog()
tuplet->setTrack(cr->track());
tuplet->setTick(cr->tick());
td.setupTuplet(tuplet);
// tuplet->setRatio(tuplet->ratio().reduced());

Fraction f1(cr->duration());
tuplet->setDuration(f1);
Fraction f = f1 * tuplet->ratio();
Fraction f = f1 * Fraction(1, tuplet->ratio().denominator());
f.reduce();

qDebug("len %s ratio %s base %s",
@@ -124,7 +124,11 @@ Tuplet* MuseScore::tupletDialog()
return 0;
}

tuplet->setBaseLen(Fraction(1, f.denominator()));
Fraction fbl(1, f.denominator());
if (TDuration::isValid(fbl))
tuplet->setBaseLen(fbl);
else
tuplet->setBaseLen(TDuration::DurationType::V_INVALID);

if (tuplet->baseLen() == TDuration::DurationType::V_INVALID) {
QMessageBox::warning(0,

0 comments on commit 979a28a

Please sign in to comment.
You can’t perform that action at this time.