MuseScore/libmscore/read206.cpp
Joachim Schmitz d9abdeb0ea fix #297326: Crash on opening Musescore 2.x file that uses leading space setting on the initial clef
which seems to not belong to any segment, so dereferences a NULL pointer
leading to a segment violation (pun intended).
The fix simply ignores this, and this avoids the crash, but also looses
the 'visible' property of that clef for some yet unknown but unrelated reason.
2019-11-19 10:27:51 +01:00

4045 lines
172 KiB
C++

//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2016 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENSE.GPL
//=============================================================================
#include "xml.h"
#include "score.h"
#include "staff.h"
#include "revisions.h"
#include "part.h"
#include "page.h"
#include "style.h"
#include "sym.h"
#include "arpeggio.h"
#include "audio.h"
#include "sig.h"
#include "barline.h"
#include "measure.h"
#include "ambitus.h"
#include "bend.h"
#include "chordline.h"
#include "hook.h"
#include "tuplet.h"
#include "systemdivider.h"
#include "spacer.h"
#include "keysig.h"
#include "stafftext.h"
#include "dynamic.h"
#include "drumset.h"
#include "timesig.h"
#include "slur.h"
#include "tie.h"
#include "chord.h"
#include "rest.h"
#include "breath.h"
#include "repeat.h"
#include "utils.h"
#include "read206.h"
#include "excerpt.h"
#include "articulation.h"
#include "volta.h"
#include "pedal.h"
#include "hairpin.h"
#include "glissando.h"
#include "ottava.h"
#include "trill.h"
#include "rehearsalmark.h"
#include "box.h"
#include "textframe.h"
#include "textline.h"
#include "fingering.h"
#include "fermata.h"
#include "image.h"
#include "stem.h"
#include "stemslash.h"
#include "undo.h"
#include "lyrics.h"
#include "tempotext.h"
#include "measurenumber.h"
#include "marker.h"
#ifdef OMR
#include "omr/omr.h"
#include "omr/omrpage.h"
#endif
namespace Ms {
static void readText206(XmlReader& e, TextBase* t, Element* be);
//---------------------------------------------------------
// StyleVal206
// this is a list of default style values which are
// different in 3.x
//
// TODO: remove style values which are equal
//---------------------------------------------------------
struct StyleVal2 {
Sid idx;
QVariant val;
};
static const StyleVal2 style206[] = {
{ Sid::staffUpperBorder, Spatium(7.0) },
{ Sid::staffLowerBorder, Spatium(7.0) },
{ Sid::staffDistance, Spatium(6.5) },
{ Sid::akkoladeDistance, Spatium(6.5) },
{ Sid::minSystemDistance, Spatium(8.5) },
{ Sid::maxSystemDistance, Spatium(15.0) },
// { Sid::lyricsMinBottomDistance, Spatium(4.0) }, // no longer makes sense
{ Sid::lyricsLineHeight, QVariant(1.0) },
{ Sid::lyricsDashForce, QVariant(false) },
{ Sid::figuredBassFontFamily, QVariant(QString("MScoreBC")) },
{ Sid::figuredBassFontSize, QVariant(8.0) },
{ Sid::figuredBassYOffset, QVariant(6.0) },
{ Sid::figuredBassLineHeight, QVariant(1.0) },
{ Sid::figuredBassAlignment, QVariant(0) },
{ Sid::figuredBassStyle, QVariant(0) },
{ Sid::systemFrameDistance, Spatium(7.0) },
{ Sid::frameSystemDistance, Spatium(7.0) },
{ Sid::minMeasureWidth, Spatium(5.0) },
{ Sid::barWidth, Spatium(0.16) }, // 0.1875
{ Sid::doubleBarWidth, Spatium(0.16) },
{ Sid::endBarWidth, Spatium(0.5) }, // 0.5
{ Sid::doubleBarDistance, Spatium(0.46) }, // 0.3 + doubleBarWidth
{ Sid::endBarDistance, Spatium(0.65) }, // 0.3
{ Sid::repeatBarTips, QVariant(false) },
{ Sid::startBarlineSingle, QVariant(false) },
{ Sid::startBarlineMultiple, QVariant(true) },
{ Sid::bracketWidth, Spatium(0.45) },
{ Sid::bracketDistance, Spatium(0.1) },
{ Sid::akkoladeWidth, Spatium(1.6) },
{ Sid::akkoladeBarDistance, Spatium(.4) },
{ Sid::clefLeftMargin, Spatium(0.64) },
{ Sid::keysigLeftMargin, Spatium(0.5) },
{ Sid::timesigLeftMargin, Spatium(0.5) },
{ Sid::clefKeyRightMargin, Spatium(1.75) },
{ Sid::clefBarlineDistance, Spatium(0.5) },
{ Sid::stemWidth, Spatium(0.13) }, // 0.09375
{ Sid::shortenStem, QVariant(true) },
{ Sid::shortStemProgression, Spatium(0.25) },
{ Sid::shortestStem, Spatium(2.25) },
{ Sid::beginRepeatLeftMargin, Spatium(1.0) },
{ Sid::minNoteDistance, Spatium(0.25) }, // 0.4
{ Sid::barNoteDistance, Spatium(1.2) },
{ Sid::barAccidentalDistance, Spatium(.3) },
{ Sid::multiMeasureRestMargin, Spatium(1.2) },
{ Sid::noteBarDistance, Spatium(1.0) },
{ Sid::measureSpacing, QVariant(1.2) },
{ Sid::staffLineWidth, Spatium(0.08) }, // 0.09375
{ Sid::ledgerLineWidth, Spatium(0.16) }, // 0.1875
{ Sid::ledgerLineLength, Spatium(.6) }, // notehead width + this value
{ Sid::accidentalDistance, Spatium(0.22) },
{ Sid::accidentalNoteDistance, Spatium(0.22) },
{ Sid::beamWidth, Spatium(0.5) }, // was 0.48
{ Sid::beamDistance, QVariant(0.5) }, // 0.25sp
{ Sid::beamMinLen, QVariant(1.32) }, // 1.316178 exactly notehead width
{ Sid::beamNoSlope, QVariant(false) },
{ Sid::dotMag, QVariant(1.0) },
{ Sid::dotNoteDistance, QVariant(0.35) },
{ Sid::dotRestDistance, QVariant(0.25) },
{ Sid::dotDotDistance, QVariant(0.5) },
{ Sid::propertyDistanceHead, QVariant(1.0) },
{ Sid::propertyDistanceStem, QVariant(1.8) },
{ Sid::propertyDistance, QVariant(1.0) },
{ Sid::articulationMag, QVariant(1.0) },
{ Sid::lastSystemFillLimit, QVariant(0.3) },
{ Sid::hairpinPosBelow, QPointF(0.0, 3.5) },
{ Sid::hairpinHeight, QVariant(1.2) },
{ Sid::hairpinContHeight, QVariant(0.5) },
{ Sid::hairpinLineWidth, QVariant(0.13) },
{ Sid::pedalPosBelow, QPointF(0.0, 4) },
{ Sid::pedalLineWidth, QVariant(.15) },
{ Sid::pedalLineStyle, QVariant(int(Qt::SolidLine)) },
{ Sid::trillPosAbove, QPointF(0.0, -1) },
{ Sid::harmonyFretDist, QVariant(0.5) },
{ Sid::minHarmonyDistance, QVariant(0.5) },
{ Sid::maxHarmonyBarDistance, QVariant(3.0) },
{ Sid::capoPosition, QVariant(0) },
{ Sid::fretNumMag, QVariant(2.0) },
{ Sid::fretNumPos, QVariant(0) },
{ Sid::fretY, QVariant(2.0) },
{ Sid::showPageNumber, QVariant(true) },
{ Sid::showPageNumberOne, QVariant(false) },
{ Sid::pageNumberOddEven, QVariant(true) },
{ Sid::showMeasureNumber, QVariant(true) },
{ Sid::showMeasureNumberOne, QVariant(false) },
{ Sid::measureNumberInterval, QVariant(5) },
{ Sid::measureNumberSystem, QVariant(true) },
{ Sid::measureNumberAllStaffs, QVariant(false) },
{ Sid::smallNoteMag, QVariant(.7) },
{ Sid::graceNoteMag, QVariant(0.7) },
{ Sid::smallStaffMag, QVariant(0.7) },
{ Sid::smallClefMag, QVariant(0.8) },
{ Sid::genClef, QVariant(true) },
{ Sid::genKeysig, QVariant(true) },
{ Sid::genCourtesyTimesig, QVariant(true) },
{ Sid::genCourtesyKeysig, QVariant(true) },
{ Sid::genCourtesyClef, QVariant(true) },
{ Sid::swingRatio, QVariant(60) },
{ Sid::swingUnit, QVariant(QString("")) },
{ Sid::useStandardNoteNames, QVariant(true) },
{ Sid::useGermanNoteNames, QVariant(false) },
{ Sid::useFullGermanNoteNames, QVariant(false) },
{ Sid::useSolfeggioNoteNames, QVariant(false) },
{ Sid::useFrenchNoteNames, QVariant(false) },
{ Sid::automaticCapitalization, QVariant(true) },
{ Sid::lowerCaseMinorChords, QVariant(false) },
{ Sid::lowerCaseBassNotes, QVariant(false) },
{ Sid::allCapsNoteNames, QVariant(false) },
{ Sid::chordStyle, QVariant(QString("std")) },
{ Sid::chordsXmlFile, QVariant(false) },
{ Sid::chordDescriptionFile, QVariant(QString("chords_std.xml")) },
{ Sid::concertPitch, QVariant(false) },
{ Sid::createMultiMeasureRests, QVariant(false) },
{ Sid::minEmptyMeasures, QVariant(2) },
{ Sid::minMMRestWidth, Spatium(4) },
{ Sid::hideEmptyStaves, QVariant(false) },
{ Sid::dontHideStavesInFirstSystem, QVariant(true) },
{ Sid::hideInstrumentNameIfOneInstrument, QVariant(true) },
{ Sid::gateTime, QVariant(100) },
{ Sid::tenutoGateTime, QVariant(100) },
{ Sid::staccatoGateTime, QVariant(50) },
{ Sid::slurGateTime, QVariant(100) },
{ Sid::ArpeggioNoteDistance, QVariant(.5) },
{ Sid::ArpeggioLineWidth, QVariant(.18) },
{ Sid::ArpeggioHookLen, QVariant(.8) },
{ Sid::SlurEndWidth, QVariant(.07) },
{ Sid::SlurMidWidth, QVariant(.15) },
{ Sid::SlurDottedWidth, QVariant(.1) },
{ Sid::MinTieLength, QVariant(1.0) },
{ Sid::SectionPause, QVariant(qreal(3.0)) },
{ Sid::MusicalSymbolFont, QVariant(QString("Emmentaler")) },
{ Sid::MusicalTextFont, QVariant(QString("MScore Text")) },
{ Sid::showHeader, QVariant(false) },
{ Sid::headerFirstPage, QVariant(false) },
{ Sid::headerOddEven, QVariant(true) },
{ Sid::evenHeaderL, QVariant(QString()) },
{ Sid::evenHeaderC, QVariant(QString()) },
{ Sid::evenHeaderR, QVariant(QString()) },
{ Sid::oddHeaderL, QVariant(QString()) },
{ Sid::oddHeaderC, QVariant(QString()) },
{ Sid::oddHeaderR, QVariant(QString()) },
{ Sid::showFooter, QVariant(true) },
{ Sid::footerFirstPage, QVariant(true) },
{ Sid::footerOddEven, QVariant(true) },
{ Sid::evenFooterL, QVariant(QString("$p")) },
{ Sid::evenFooterC, QVariant(QString("$:copyright:")) },
{ Sid::evenFooterR, QVariant(QString()) },
{ Sid::oddFooterL, QVariant(QString()) },
{ Sid::oddFooterC, QVariant(QString("$:copyright:")) },
{ Sid::oddFooterR, QVariant(QString("$p")) },
{ Sid::voltaPosAbove, QPointF(0.0, -3.0) },
{ Sid::voltaHook, QVariant(1.9) },
{ Sid::voltaLineWidth, QVariant(.1) },
{ Sid::voltaLineStyle, QVariant(int(Qt::SolidLine)) },
{ Sid::ottavaPosAbove, QPointF(0.0, -3.0) },
{ Sid::ottavaHookAbove, QVariant(1.9) },
{ Sid::ottavaHookBelow, QVariant(-1.9) },
{ Sid::ottavaLineWidth, QVariant(.1) },
{ Sid::ottavaLineStyle, QVariant(int(Qt::DashLine)) },
{ Sid::ottavaNumbersOnly, true },
{ Sid::tabClef, QVariant(int(ClefType::TAB)) },
{ Sid::tremoloWidth, QVariant(1.2) }, // tremolo stroke width: notehead width
{ Sid::tremoloBoxHeight, QVariant(0.65) },
{ Sid::tremoloStrokeWidth, QVariant(0.5) }, // was 0.35
{ Sid::tremoloDistance, QVariant(0.8) },
// TODO { Sid::tremoloBeamLengthMultiplier, QVariant(0.62) },
// TODO { Sid::tremoloMaxBeamLength, QVariant(12.0) },
{ Sid::linearStretch, QVariant(qreal(1.5)) },
{ Sid::crossMeasureValues, QVariant(false) },
{ Sid::keySigNaturals, QVariant(int(KeySigNatural::NONE)) },
{ Sid::tupletMaxSlope, QVariant(qreal(0.5)) },
{ Sid::tupletOufOfStaff, QVariant(true) },
{ Sid::tupletVHeadDistance, QVariant(.5) },
{ Sid::tupletVStemDistance, QVariant(.25) },
{ Sid::tupletStemLeftDistance, QVariant(.5) },
{ Sid::tupletStemRightDistance, QVariant(.5) },
{ Sid::tupletNoteLeftDistance, QVariant(0.0) },
{ Sid::tupletNoteRightDistance, QVariant(0.0) },
{ Sid::barreLineWidth, QVariant(1.0) },
{ Sid::fretMag, QVariant(1.0) },
{ Sid::scaleBarlines, QVariant(true) },
{ Sid::barGraceDistance, QVariant(.6) },
{ Sid::rehearsalMarkFrameRound, QVariant(20) },
{ Sid::dynamicsFontStyle, int(FontStyle::Italic) },
// { Sid::staffTextFontFace, "FreeSerif" },
// { Sid::staffTextFontSize, 10.0 },
// { Sid::staffTextFontBold, false },
// { Sid::staffTextFontItalic, false },
// { Sid::staffTextFontUnderline, false },
{ Sid::staffTextAlign, QVariant::fromValue(Align::LEFT | Align::TOP) }, // different from 3.x
// { Sid::staffTextOffsetType, int(OffsetType::SPATIUM) },
// { Sid::staffTextPlacement, int(Placement::ABOVE) },
{ Sid::staffTextPosAbove, QPointF(.0, -4.0) },
// { Sid::staffTextMinDistance, Spatium(0.5) },
// { Sid::staffTextFrameType, int(FrameType::NO_FRAME) },
// { Sid::staffTextFramePadding, 0.2 },
// { Sid::staffTextFrameWidth, 0.1 },
// { Sid::staffTextFrameRound, 0 },
// { Sid::staffTextFrameFgColor, QColor(0, 0, 0, 255) },
// { Sid::staffTextFrameBgColor, QColor(255, 255, 255, 0) },
{ Sid::defaultFrameRound, QVariant(25) },
{ Sid::defaultFrameWidth, Spatium(0.2) },
{ Sid::defaultFramePadding, Spatium(0.5) },
{ Sid::titleFrameRound, QVariant(25) },
{ Sid::titleFrameWidth, Spatium(0.2) },
{ Sid::titleFramePadding, Spatium(0.5) },
{ Sid::subTitleFrameRound, QVariant(25) },
{ Sid::subTitleFrameWidth, Spatium(0.2) },
{ Sid::subTitleFramePadding, Spatium(0.5) },
{ Sid::composerFrameRound, QVariant(25) },
{ Sid::composerFrameWidth, Spatium(0.2) },
{ Sid::composerFramePadding, Spatium(0.5) },
{ Sid::lyricistFrameRound, QVariant(25) },
{ Sid::lyricistFrameWidth, Spatium(0.2) },
{ Sid::lyricistFramePadding, Spatium(0.5) },
{ Sid::fingeringFrameRound, QVariant(25) },
{ Sid::fingeringFrameWidth, Spatium(0.2) },
{ Sid::fingeringFramePadding, Spatium(0.5) },
{ Sid::lhGuitarFingeringFrameRound, QVariant(25) },
{ Sid::lhGuitarFingeringFrameWidth, Spatium(0.2) },
{ Sid::lhGuitarFingeringFramePadding, Spatium(0.5) },
{ Sid::rhGuitarFingeringFrameRound, QVariant(25) },
{ Sid::rhGuitarFingeringFrameWidth, Spatium(0.2) },
{ Sid::rhGuitarFingeringFramePadding, Spatium(0.5) },
{ Sid::partInstrumentFrameRound, QVariant(25) },
{ Sid::partInstrumentFrameWidth, Spatium(0.2) },
{ Sid::partInstrumentFramePadding, Spatium(0.5) },
{ Sid::tempoFrameRound, QVariant(0) },
{ Sid::tempoFrameWidth, Spatium(0.2) },
{ Sid::tempoFramePadding, Spatium(0.5) },
{ Sid::tempoFrameRound, QVariant(25) },
{ Sid::tempoFrameWidth, Spatium(0.2) },
{ Sid::tempoFramePadding, Spatium(0.5) },
{ Sid::systemTextFrameRound, QVariant(25) },
{ Sid::systemTextFrameWidth, Spatium(0.2) },
{ Sid::systemTextFramePadding, Spatium(0.5) },
{ Sid::staffTextFrameRound, QVariant(25) },
{ Sid::staffTextFrameWidth, Spatium(0.2) },
{ Sid::staffTextFramePadding, Spatium(0.5) },
{ Sid::rehearsalMarkFrameRound, QVariant(20) },
{ Sid::rehearsalMarkFrameWidth, Spatium(0.2) },
{ Sid::rehearsalMarkFramePadding, Spatium(0.5) },
{ Sid::repeatLeftFrameRound, QVariant(25) },
{ Sid::repeatLeftFrameWidth, Spatium(0.2) },
{ Sid::repeatLeftFramePadding, Spatium(0.5) },
{ Sid::repeatRightFrameRound, QVariant(25) },
{ Sid::repeatRightFrameWidth, Spatium(0.2) },
{ Sid::repeatRightFramePadding, Spatium(0.5) },
};
//---------------------------------------------------------
// excessTextStyles206
// The first map has the name of the style as the string
// The second map has the mapping of each Sid that the style identifies
// to the default value for that sid.
//---------------------------------------------------------
static std::map<QString, std::map<Sid, QVariant>> excessTextStyles206;
//---------------------------------------------------------
// setPageFormat
// set Style from PageFormat
//---------------------------------------------------------
void setPageFormat(MStyle* style, const PageFormat& pf)
{
style->set(Sid::pageWidth, pf.size().width());
style->set(Sid::pageHeight, pf.size().height());
style->set(Sid::pagePrintableWidth, pf.printableWidth());
style->set(Sid::pageEvenLeftMargin, pf.evenLeftMargin());
style->set(Sid::pageOddLeftMargin, pf.oddLeftMargin());
style->set(Sid::pageEvenTopMargin, pf.evenTopMargin());
style->set(Sid::pageEvenBottomMargin, pf.evenBottomMargin());
style->set(Sid::pageOddTopMargin, pf.oddTopMargin());
style->set(Sid::pageOddBottomMargin, pf.oddBottomMargin());
style->set(Sid::pageTwosided, pf.twosided());
}
//---------------------------------------------------------
// initPageFormat
// initialize PageFormat from Style
//---------------------------------------------------------
void initPageFormat(MStyle* style, PageFormat* pf)
{
QSizeF sz;
sz.setWidth(style->value(Sid::pageWidth).toReal());
sz.setHeight(style->value(Sid::pageHeight).toReal());
pf->setSize(sz);
pf->setPrintableWidth(style->value(Sid::pagePrintableWidth).toReal());
pf->setEvenLeftMargin(style->value(Sid::pageEvenLeftMargin).toReal());
pf->setOddLeftMargin(style->value(Sid::pageOddLeftMargin).toReal());
pf->setEvenTopMargin(style->value(Sid::pageEvenTopMargin).toReal());
pf->setEvenBottomMargin(style->value(Sid::pageEvenBottomMargin).toReal());
pf->setOddTopMargin(style->value(Sid::pageOddTopMargin).toReal());
pf->setOddBottomMargin(style->value(Sid::pageOddBottomMargin).toReal());
pf->setTwosided(style->value(Sid::pageTwosided).toBool());
}
//---------------------------------------------------------
// readPageFormat
//---------------------------------------------------------
void readPageFormat(MStyle* style, XmlReader& e)
{
PageFormat pf;
initPageFormat(style, &pf);
pf.read(e);
setPageFormat(style, pf);
}
//---------------------------------------------------------
// readTextStyle206
//---------------------------------------------------------
void readTextStyle206(MStyle* style, XmlReader& e, std::map<QString, std::map<Sid, QVariant>>& excessStyles)
{
QString family = "FreeSerif";
double size = 10;
bool sizeIsSpatiumDependent = false;
FontStyle fontStyle = FontStyle::Normal;
Align align = Align::LEFT;
QPointF offset;
OffsetType offsetType = OffsetType::SPATIUM;
FrameType frameType = FrameType::NO_FRAME;
Spatium paddingWidth(0.0);
Spatium frameWidth(0.0);
QColor foregroundColor = QColor(0, 0, 0, 255);
QColor backgroundColor = QColor(255, 255, 255, 0);
Placement placement = Placement::ABOVE;
bool placementValid = false;
QString name = e.attribute("name");
QColor frameColor = QColor(0, 0, 0, 255);
bool systemFlag = false;
qreal lineWidth = -1.0;
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "name")
name = e.readElementText();
else if (tag == "family")
family = e.readElementText();
else if (tag == "size")
size = e.readDouble();
else if (tag == "bold") {
if (e.readInt())
fontStyle = fontStyle + FontStyle::Bold;
}
else if (tag == "italic") {
if (e.readInt())
fontStyle = fontStyle + FontStyle::Italic;
}
else if (tag == "underline") {
if (e.readInt())
fontStyle = fontStyle + FontStyle::Underline;
}
else if (tag == "align")
align = Align(e.readInt());
else if (tag == "anchor") // obsolete
e.skipCurrentElement();
else if (tag == "halign") {
const QString& val(e.readElementText());
if (val == "center")
align = align | Align::HCENTER;
else if (val == "right")
align = align | Align::RIGHT;
else if (val == "left")
;
else
qDebug("Text::readProperties: unknown alignment: <%s>", qPrintable(val));
}
else if (tag == "valign") {
const QString& val(e.readElementText());
if (val == "center")
align = align | Align::VCENTER;
else if (val == "bottom")
align = align | Align::BOTTOM;
else if (val == "baseline")
align = align | Align::BASELINE;
else if (val == "top")
;
else
qDebug("Text::readProperties: unknown alignment: <%s>", qPrintable(val));
}
else if (tag == "xoffset") {
qreal xo = e.readDouble();
if (offsetType == OffsetType::ABS)
xo /= INCH;
offset.setX(xo);
}
else if (tag == "yoffset") {
qreal yo = e.readDouble();
if (offsetType == OffsetType::ABS)
yo /= INCH;
offset.setY(yo);
}
else if (tag == "rxoffset" || tag == "ryoffset") // obsolete
e.readDouble();
else if (tag == "offsetType") {
const QString& val(e.readElementText());
OffsetType ot = OffsetType::ABS;
if (val == "spatium" || val == "1")
ot = OffsetType::SPATIUM;
if (ot != offsetType) {
offsetType = ot;
if (ot == OffsetType::ABS)
offset /= INCH; // convert spatium -> inch
else
offset *= INCH; // convert inch -> spatium
}
}
else if (tag == "sizeIsSpatiumDependent" || tag == "spatiumSizeDependent")
sizeIsSpatiumDependent = e.readInt();
else if (tag == "frameWidth") { // obsolete
frameType = FrameType::SQUARE;
/*frameWidthMM =*/ e.readDouble();
}
else if (tag == "frameWidthS") {
frameType = FrameType::SQUARE;
frameWidth = Spatium(e.readDouble());
}
else if (tag == "frame")
frameType = e.readInt() ? FrameType::SQUARE : FrameType::NO_FRAME;
else if (tag == "paddingWidth") // obsolete
/*paddingWidthMM =*/ e.readDouble();
else if (tag == "paddingWidthS")
paddingWidth = Spatium(e.readDouble());
else if (tag == "frameRound")
e.readInt();
else if (tag == "frameColor")
frameColor = e.readColor();
else if (tag == "foregroundColor")
foregroundColor = e.readColor();
else if (tag == "backgroundColor")
backgroundColor = e.readColor();
else if (tag == "circle")
frameType = e.readInt() ? FrameType::CIRCLE : FrameType::NO_FRAME;
else if (tag == "systemFlag")
systemFlag = e.readInt();
else if (tag == "placement") {
QString value(e.readElementText());
if (value == "above")
placement = Placement::ABOVE;
else if (value == "below")
placement = Placement::BELOW;
placementValid = true;
}
else if (tag == "lineWidth")
lineWidth = e.readDouble();
else
e.unknown();
}
if (family == "MuseJazz")
family = "MuseJazz Text";
struct StyleTable {
const char* name;
Tid ss;
} styleTable[] = {
{ "", Tid::DEFAULT },
{ "Title", Tid::TITLE },
{ "Subtitle", Tid::SUBTITLE },
{ "Composer", Tid::COMPOSER },
{ "Lyricist", Tid::POET },
{ "Lyrics Odd Lines", Tid::LYRICS_ODD },
{ "Lyrics Even Lines", Tid::LYRICS_EVEN },
{ "Fingering", Tid::FINGERING },
{ "LH Guitar Fingering", Tid::LH_GUITAR_FINGERING },
{ "RH Guitar Fingering", Tid::RH_GUITAR_FINGERING },
{ "String Number", Tid::STRING_NUMBER },
{ "Instrument Name (Long)", Tid::INSTRUMENT_LONG },
{ "Instrument Name (Short)", Tid::INSTRUMENT_SHORT },
{ "Instrument Name (Part)", Tid::INSTRUMENT_EXCERPT },
{ "Dynamics", Tid::DYNAMICS },
{ "Technique", Tid::EXPRESSION },
{ "Tempo", Tid::TEMPO },
{ "Metronome", Tid::METRONOME },
{ "Measure Number", Tid::MEASURE_NUMBER },
{ "Translator", Tid::TRANSLATOR },
{ "Tuplet", Tid::TUPLET },
{ "System", Tid::SYSTEM },
{ "Staff", Tid::STAFF },
{ "Chord Symbol", Tid::HARMONY_A },
{ "Rehearsal Mark", Tid::REHEARSAL_MARK },
{ "Repeat Text Left", Tid::REPEAT_LEFT },
{ "Repeat Text Right", Tid::REPEAT_RIGHT },
{ "Frame", Tid::FRAME },
{ "Text Line", Tid::TEXTLINE },
{ "Glissando", Tid::GLISSANDO },
{ "Ottava", Tid::OTTAVA },
{ "Pedal", Tid::PEDAL },
{ "Hairpin", Tid::HAIRPIN },
{ "Bend", Tid::BEND },
{ "Header", Tid::HEADER },
{ "Footer", Tid::FOOTER },
{ "Instrument Change", Tid::INSTRUMENT_CHANGE },
{ "Repeat Text", Tid::IGNORED_STYLES, }, // Repeat Text style no longer exists
{ "Figured Bass", Tid::IGNORED_STYLES, }, // F.B. data are in style properties
{ "Volta", Tid::VOLTA },
};
Tid ss = Tid::TEXT_STYLES;
for (const auto& i : styleTable) {
if (name == i.name) {
ss = i.ss;
break;
}
}
if (ss == Tid::IGNORED_STYLES)
return;
bool isExcessStyle = false;
if (ss == Tid::TEXT_STYLES) {
ss = e.addUserTextStyle(name);
if (ss == Tid::TEXT_STYLES) {
qDebug("unhandled substyle <%s>", qPrintable(name));
isExcessStyle = true;
}
else {
int idx = int(ss) - int(Tid::USER1);
if ((idx < 0) || (idx > 5)) {
qDebug("User style index %d outside of range [0,5].", idx);
return;
}
Sid sid[] = { Sid::user1Name, Sid::user2Name, Sid::user3Name, Sid::user4Name, Sid::user5Name, Sid::user6Name };
style->set(sid[idx], name);
}
}
std::map<Sid, QVariant> excessPairs;
const TextStyle* ts;
if (isExcessStyle)
ts = textStyle("User-1");
else
ts = textStyle(ss);
for (const auto& i : *ts) {
QVariant value;
if (i.sid == Sid::NOSTYLE)
break;
switch (i.pid) {
case Pid::SUB_STYLE:
value = int(ss);
break;
case Pid::BEGIN_FONT_FACE:
case Pid::CONTINUE_FONT_FACE:
case Pid::END_FONT_FACE:
case Pid::FONT_FACE:
value = family;
break;
case Pid::BEGIN_FONT_SIZE:
case Pid::CONTINUE_FONT_SIZE:
case Pid::END_FONT_SIZE:
case Pid::FONT_SIZE:
value = size;
break;
case Pid::BEGIN_FONT_STYLE:
case Pid::CONTINUE_FONT_STYLE:
case Pid::END_FONT_STYLE:
case Pid::FONT_STYLE:
value = int(fontStyle);
break;
case Pid::FRAME_TYPE:
value = int(frameType);
break;
case Pid::FRAME_WIDTH:
value = frameWidth;
break;
case Pid::FRAME_PADDING:
value = paddingWidth;
break;
case Pid::FRAME_FG_COLOR:
value = frameColor;
break;
case Pid::FRAME_BG_COLOR:
value = backgroundColor;
break;
case Pid::SIZE_SPATIUM_DEPENDENT:
value = sizeIsSpatiumDependent;
break;
case Pid::BEGIN_TEXT_ALIGN:
case Pid::CONTINUE_TEXT_ALIGN:
case Pid::END_TEXT_ALIGN:
case Pid::ALIGN:
value = QVariant::fromValue(align);
break;
#if 0 //TODO-offset
case Pid::OFFSET:
if (offsetValid) {
if (ss == Tid::TEMPO) {
style->set(Sid::tempoPosAbove, Spatium(offset.y()));
offset = QPointF();
}
else if (ss == Tid::STAFF) {
style->set(Sid::staffTextPosAbove, Spatium(offset.y()));
offset = QPointF();
}
else if (ss == Tid::REHEARSAL_MARK) {
style->set(Sid::rehearsalMarkPosAbove, Spatium(offset.y()));
offset = QPointF();
}
value = offset;
}
break;
case Pid::OFFSET_TYPE:
value = int(offsetType);
break;
#endif
case Pid::SYSTEM_FLAG:
value = systemFlag;
break;
case Pid::BEGIN_HOOK_HEIGHT:
case Pid::END_HOOK_HEIGHT:
value = QVariant();
break;
case Pid::PLACEMENT:
if (placementValid)
value = int(placement);
break;
case Pid::LINE_WIDTH:
if (lineWidth != -1.0)
value = lineWidth;
break;
default:
// qDebug("unhandled property <%s>%d", propertyName(i.pid), int (i.pid));
break;
}
if (value.isValid()) {
if (isExcessStyle)
excessPairs[i.sid] = value;
else
style->set(i.sid, value);
}
// else
// qDebug("invalid style value <%s> pid<%s>", MStyle::valueName(i.sid), propertyName(i.pid));
}
if (isExcessStyle && excessPairs.size() > 0)
excessStyles[name] = excessPairs;
}
//---------------------------------------------------------
// readAccidental206
//---------------------------------------------------------
void readAccidental206(Accidental* a, XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "bracket") {
int i = e.readInt();
if (i == 0 || i == 1)
a->setBracket(AccidentalBracket(i));
}
else if (tag == "subtype") {
QString text = e.readElementText();
const static std::map<QString, AccidentalType> accMap = {
{"none", AccidentalType::NONE},
{"sharp", AccidentalType::SHARP},
{"flat", AccidentalType::FLAT},
{"natural", AccidentalType::NATURAL},
{"double sharp", AccidentalType::SHARP2},
{"double flat", AccidentalType::FLAT2},
{"flat-slash", AccidentalType::FLAT_SLASH},
{"flat-slash2", AccidentalType::FLAT_SLASH2},
{"mirrored-flat2", AccidentalType::MIRRORED_FLAT2},
{"mirrored-flat", AccidentalType::MIRRORED_FLAT},
{"sharp-slash", AccidentalType::SHARP_SLASH},
{"sharp-slash2", AccidentalType::SHARP_SLASH2},
{"sharp-slash3", AccidentalType::SHARP_SLASH3},
{"sharp-slash4", AccidentalType::SHARP_SLASH4},
{"sharp arrow up", AccidentalType::SHARP_ARROW_UP},
{"sharp arrow down", AccidentalType::SHARP_ARROW_DOWN},
{"flat arrow up", AccidentalType::FLAT_ARROW_UP},
{"flat arrow down", AccidentalType::FLAT_ARROW_DOWN},
{"natural arrow up", AccidentalType::NATURAL_ARROW_UP},
{"natural arrow down", AccidentalType::NATURAL_ARROW_DOWN},
{"sori", AccidentalType::SORI},
{"koron", AccidentalType::KORON}
};
auto it = accMap.find(text);
if (it == accMap.end()) {
qDebug("invalid type %s", qPrintable(text));
a->setAccidentalType(AccidentalType::NONE);
}
else
a->setAccidentalType(it->second);
}
else if (tag == "role") {
AccidentalRole r = AccidentalRole(e.readInt());
if (r == AccidentalRole::AUTO || r == AccidentalRole::USER)
a->setRole(r);
}
else if (tag == "small")
a->setSmall(e.readInt());
else if (a->Element::readProperties(e))
;
else
e.unknown();
}
}
static NoteHead::Group convertHeadGroup(int i)
{
NoteHead::Group val;
switch (i) {
case 1:
val = NoteHead::Group::HEAD_CROSS;
break;
case 2:
val = NoteHead::Group::HEAD_DIAMOND;
break;
case 3:
val = NoteHead::Group::HEAD_TRIANGLE_DOWN;
break;
case 4:
val = NoteHead::Group::HEAD_MI;
break;
case 5:
val = NoteHead::Group::HEAD_SLASH;
break;
case 6:
val = NoteHead::Group::HEAD_XCIRCLE;
break;
case 7:
val = NoteHead::Group::HEAD_DO;
break;
case 8:
val = NoteHead::Group::HEAD_RE;
break;
case 9:
val = NoteHead::Group::HEAD_FA;
break;
case 10:
val = NoteHead::Group::HEAD_LA;
break;
case 11:
val = NoteHead::Group::HEAD_TI;
break;
case 12:
val = NoteHead::Group::HEAD_SOL;
break;
case 13:
val = NoteHead::Group::HEAD_BREVIS_ALT;
break;
case 0:
default:
val = NoteHead::Group::HEAD_NORMAL;
}
return val;
}
static NoteHead::Type convertHeadType(int i)
{
NoteHead::Type val;
switch (i) {
case 0:
val = NoteHead::Type::HEAD_WHOLE;
break;
case 1:
val = NoteHead::Type::HEAD_HALF;
break;
case 2:
val = NoteHead::Type::HEAD_QUARTER;
break;
case 3:
val = NoteHead::Type::HEAD_BREVIS;
break;
default:
val = NoteHead::Type::HEAD_AUTO;;
}
return val;
}
//---------------------------------------------------------
// ArticulationNames
//---------------------------------------------------------
static struct ArticulationNames {
SymId id;
const char* name;
} articulationNames[] = {
{ SymId::fermataAbove, "fermata", },
{ SymId::fermataShortAbove, "shortfermata", },
{ SymId::fermataLongAbove, "longfermata", },
{ SymId::fermataVeryLongAbove, "verylongfermata", },
{ SymId::articAccentAbove, "sforzato", },
{ SymId::articStaccatoAbove, "staccato", },
{ SymId::articStaccatissimoAbove, "staccatissimo", },
{ SymId::articTenutoAbove, "tenuto", },
{ SymId::articTenutoStaccatoAbove, "portato", },
{ SymId::articMarcatoAbove, "marcato", },
{ SymId::guitarFadeIn, "fadein", },
{ SymId::guitarFadeOut, "fadeout", },
{ SymId::guitarVolumeSwell, "volumeswell", },
{ SymId::wiggleSawtooth, "wigglesawtooth", },
{ SymId::wiggleSawtoothWide, "wigglesawtoothwide", },
{ SymId::wiggleVibratoLargeFaster, "wigglevibratolargefaster", },
{ SymId::wiggleVibratoLargeSlowest, "wigglevibratolargeslowest", },
{ SymId::brassMuteOpen, "ouvert", },
{ SymId::brassMuteClosed, "plusstop", },
{ SymId::stringsUpBow, "upbow", },
{ SymId::stringsDownBow, "downbow", },
{ SymId::ornamentTurnInverted, "reverseturn", },
{ SymId::ornamentTurn, "turn", },
{ SymId::ornamentTrill, "trill", },
{ SymId::ornamentMordent, "prall", },
{ SymId::ornamentMordentInverted, "mordent", },
{ SymId::ornamentTremblement, "prallprall", },
{ SymId::ornamentPrallMordent, "prallmordent", },
{ SymId::ornamentUpPrall, "upprall", },
{ SymId::ornamentUpMordent, "upmordent", },
{ SymId::ornamentDownMordent, "downmordent", },
{ SymId::ornamentPrallDown, "pralldown", },
{ SymId::ornamentPrallUp, "prallup", },
{ SymId::ornamentLinePrall, "lineprall", },
{ SymId::ornamentPrecompSlide, "schleifer", },
{ SymId::pluckedSnapPizzicatoAbove, "snappizzicato", },
{ SymId::stringsThumbPosition, "thumb", },
{ SymId::luteFingeringRHThumb, "lutefingeringthumb", },
{ SymId::luteFingeringRHFirst, "lutefingering1st", },
{ SymId::luteFingeringRHSecond, "lutefingering2nd", },
{ SymId::luteFingeringRHThird, "lutefingering3rd", },
{ SymId::ornamentPrecompMordentUpperPrefix, "downprall" },
{ SymId::ornamentPrecompMordentUpperPrefix, "ornamentDownPrall" },
};
//---------------------------------------------------------
// oldArticulationNames2SymId
//---------------------------------------------------------
SymId oldArticulationNames2SymId(const QString& s)
{
for (auto i : articulationNames) {
if (i.name == s)
return i.id;
}
return SymId::noSym;
}
//---------------------------------------------------------
// readDrumset
//---------------------------------------------------------
static void readDrumset(Drumset* ds, XmlReader& e)
{
int pitch = e.intAttribute("pitch", -1);
if (pitch < 0 || pitch > 127) {
qDebug("load drumset: invalid pitch %d", pitch);
return;
}
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "head")
ds->drum(pitch).notehead = convertHeadGroup(e.readInt());
else if (tag == "variants") {
while(e.readNextStartElement()) {
const QStringRef& tagv(e.name());
if (tagv == "variant") {
DrumInstrumentVariant div;
div.pitch = e.attribute("pitch").toInt();
while (e.readNextStartElement()) {
const QStringRef& taga(e.name());
if (taga == "articulation") {
QString oldArticulationName = e.readElementText();
SymId oldId = oldArticulationNames2SymId(oldArticulationName);
div.articulationName = Articulation::symId2ArticulationName(oldId);
}
else if (taga == "tremolo") {
div.tremolo = Tremolo::name2Type(e.readElementText());
}
}
ds->drum(pitch).addVariant(div);
}
}
}
else if (ds->readProperties(e, pitch))
;
else
e.unknown();
}
}
//---------------------------------------------------------
// readInstrument
//---------------------------------------------------------
static void readInstrument(Instrument *i, Part* p, XmlReader& e)
{
int program = -1;
int bank = 0;
int volume = 100;
int pan = 60;
int chorus = 30;
int reverb = 30;
bool customDrumset = false;
i->clearChannels(); // remove default channel
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "Drum") {
// if we see one of this tags, a custom drumset will
// be created
if (!i->drumset())
i->setDrumset(new Drumset(*smDrumset));
if (!customDrumset) {
i->drumset()->clear();
customDrumset = true;
}
readDrumset(i->drumset(), e);
}
else if (i->readProperties(e, p, &customDrumset))
;
else
e.unknown();
}
// Read single-note dynamics from template
i->setSingleNoteDynamicsFromTemplate();
if (i->channel().empty()) { // for backward compatibility
Channel* a = new Channel;
a->setName(Channel::DEFAULT_NAME);
a->setProgram(program);
a->setBank(bank);
a->setVolume(volume);
a->setPan(pan);
a->setReverb(reverb);
a->setChorus(chorus);
i->appendChannel(a);
}
if (i->useDrumset()) {
if (i->channel()[0]->bank() == 0)
i->channel()[0]->setBank(128);
}
}
//---------------------------------------------------------
// readStaff
//---------------------------------------------------------
static void readStaff(Staff* staff, XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "type") { // obsolete
int staffTypeIdx = e.readInt();
qDebug("obsolete: Staff::read staffTypeIdx %d", staffTypeIdx);
}
else if (tag == "neverHide") {
bool v = e.readInt();
if (v)
staff->setHideWhenEmpty(Staff::HideMode::NEVER);
}
else if (tag == "barLineSpan") {
staff->setBarLineFrom(e.intAttribute("from", 0));
staff->setBarLineTo(e.intAttribute("to", 0));
int span = e.readInt();
staff->setBarLineSpan(span - 1);
}
else if (staff->readProperties(e))
;
else
e.unknown();
}
}
//---------------------------------------------------------
// readPart
//---------------------------------------------------------
void readPart206(Part* part, XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "Instrument") {
Instrument* i = part->_instruments.instrument(/* tick */ -1);
readInstrument(i, part, e);
Drumset* ds = i->drumset();
Staff* s = part->staff(0);
int lld = s ? qRound(s->lineDistance(Fraction(0,1))) : 1;
if (ds && s && lld > 1) {
for (int j = 0; j < DRUM_INSTRUMENTS; ++j)
ds->drum(j).line /= lld;
}
}
else if (tag == "Staff") {
Staff* staff = new Staff(part->score());
staff->setPart(part);
part->score()->staves().push_back(staff);
part->staves()->push_back(staff);
readStaff(staff, e);
}
else if (part->readProperties(e))
;
else
e.unknown();
}
}
//---------------------------------------------------------
// readAmbitus
//---------------------------------------------------------
static void readAmbitus(Ambitus* ambitus, XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "head")
ambitus->setNoteHeadGroup(convertHeadGroup(e.readInt()));
else if (tag == "headType")
ambitus->setNoteHeadType(convertHeadType(e.readInt()));
else if (ambitus->readProperties(e))
;
else
e.unknown();
}
}
//---------------------------------------------------------
// readNote
//---------------------------------------------------------
static void readNote(Note* note, XmlReader& e)
{
note->setTpc1(Tpc::TPC_INVALID);
note->setTpc2(Tpc::TPC_INVALID);
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "Accidental") {
Accidental* a = new Accidental(note->score());
a->setTrack(note->track());
readAccidental206(a, e);
note->add(a);
}
else if (tag == "head") {
int i = e.readInt();
NoteHead::Group val = convertHeadGroup(i);
note->setHeadGroup(val);
}
else if (tag == "headType") {
int i = e.readInt();
NoteHead::Type val = convertHeadType(i);
note->setHeadType(val);
}
else if (readNoteProperties206(note, e))
;
else
e.unknown();
}
// ensure sane values:
note->setPitch(limit(note->pitch(), 0, 127));
if (!tpcIsValid(note->tpc1()) && !tpcIsValid(note->tpc2())) {
Key key = (note->staff() && note->chord()) ? note->staff()->key(note->chord()->tick()) : Key::C;
int tpc = pitch2tpc(note->pitch(), key, Prefer::NEAREST);
if (note->concertPitch())
note->setTpc1(tpc);
else
note->setTpc2(tpc);
}
if (!(tpcIsValid(note->tpc1()) && tpcIsValid(note->tpc2()))) {
Fraction tick = note->chord() ? note->chord()->tick() : Fraction(-1,1);
Interval v = note->staff() ? note->part()->instrument(tick)->transpose() : Interval();
if (tpcIsValid(note->tpc1())) {
v.flip();
if (v.isZero())
note->setTpc2(note->tpc1());
else
note->setTpc2(Ms::transposeTpc(note->tpc1(), v, true));
}
else {
if (v.isZero())
note->setTpc1(note->tpc2());
else
note->setTpc1(Ms::transposeTpc(note->tpc2(), v, true));
}
}
#if 0
// TODO - adapt this code
// check consistency of pitch, tpc1, tpc2, and transposition
// see note in InstrumentChange::read() about a known case of tpc corruption produced in 2.0.x
// but since there are other causes of tpc corruption (eg, https://musescore.org/en/node/74746)
// including perhaps some we don't know about yet,
// we will attempt to fix some problems here regardless of version
if (staff() && !staff()->isDrumStaff(e.tick()) && !e.pasteMode() && !MScore::testMode) {
int tpc1Pitch = (tpc2pitch(_tpc[0]) + 12) % 12;
int tpc2Pitch = (tpc2pitch(_tpc[1]) + 12) % 12;
int soundingPitch = _pitch % 12;
if (tpc1Pitch != soundingPitch) {
qDebug("bad tpc1 - soundingPitch = %d, tpc1 = %d", soundingPitch, tpc1Pitch);
_pitch += tpc1Pitch - soundingPitch;
}
if (staff()) {
Interval v = staff()->part()->instrument(e.tick())->transpose();
int writtenPitch = (_pitch - v.chromatic) % 12;
if (tpc2Pitch != writtenPitch) {
qDebug("bad tpc2 - writtenPitch = %d, tpc2 = %d", writtenPitch, tpc2Pitch);
if (concertPitch()) {
// assume we want to keep sounding pitch
// so fix written pitch (tpc only)
v.flip();
_tpc[1] = Ms::transposeTpc(_tpc[0], v, true);
}
else {
// assume we want to keep written pitch
// so fix sounding pitch (both tpc and pitch)
_tpc[0] = Ms::transposeTpc(_tpc[1], v, true);
_pitch += tpc2Pitch - writtenPitch;
}
}
}
}
#endif
}
//---------------------------------------------------------
// adjustPlacement
//---------------------------------------------------------
static void adjustPlacement(Element* e)
{
if (!e || !e->staff())
return;
// element to use to determine placement
// for spanners, choose first segment
Element* ee;
Spanner* spanner;
if (e->isSpanner()) {
spanner = toSpanner(e);
if (spanner->spannerSegments().empty())
return;
ee = spanner->spannerSegments().front();
if (!ee)
return;
}
else {
spanner = nullptr;
ee = e;
}
// determine placement based on offset
// anything below staff will be set to below
qreal staffHeight = e->staff()->height();
qreal threshold = staffHeight;
qreal offsetAdjust = 0.0;
Placement defaultPlacement = Placement(e->propertyDefault(Pid::PLACEMENT).toInt());
Placement newPlacement;
// most offsets will be recorded as relative to top staff line
// exceptions are styled offsets on elements with default placement below
qreal normalize;
if (defaultPlacement == Placement::BELOW && ee->propertyFlags(Pid::OFFSET) == PropertyFlags::STYLED)
normalize = staffHeight;
else
normalize = 0.0;
qreal ypos = ee->offset().y() + normalize;
if (ypos >= threshold) {
newPlacement = Placement::BELOW;
offsetAdjust -= staffHeight;
}
else {
newPlacement = Placement::ABOVE;
}
// set placement
e->setProperty(Pid::PLACEMENT, int(newPlacement));
if (newPlacement != defaultPlacement)
e->setPropertyFlags(Pid::PLACEMENT, PropertyFlags::UNSTYLED);
// adjust offset
if (spanner) {
// adjust segments individually
for (auto a : spanner->spannerSegments()) {
// spanner segments share the placement setting of the spanner
// just adjust offset
if (defaultPlacement == Placement::BELOW && a->propertyFlags(Pid::OFFSET) == PropertyFlags::STYLED)
normalize = staffHeight;
else
normalize = 0.0;
qreal yp = a->offset().y() + normalize;
a->ryoffset() += normalize + offsetAdjust;
// if any segments are offset to opposite side of staff from placement,
// or if they are within staff,
// disable autoplace
bool disableAutoplace;
if (yp + a->height() <= 0.0)
disableAutoplace = (newPlacement == Placement::BELOW);
else if (yp > staffHeight)
disableAutoplace = (newPlacement == Placement::ABOVE);
else
disableAutoplace = true;
if (disableAutoplace)
a->setAutoplace(false);
// needed for https://musescore.org/en/node/281312
// ideally we would rebase and calculate new offset
// but this may not be possible
// since original offset is relative to system
a->rxoffset() = 0;
}
}
else {
e->ryoffset() += normalize + offsetAdjust;
// if within staff, disable autoplace
if (ypos + e->height() > 0.0 && ypos <= staffHeight)
e->setAutoplace(false);
}
}
//---------------------------------------------------------
// readNoteProperties206
//---------------------------------------------------------
bool readNoteProperties206(Note* note, XmlReader& e)
{
const QStringRef& tag(e.name());
if (tag == "pitch")
note->setPitch(e.readInt());
else if (tag == "tpc") {
const int tpc = e.readInt();
note->setTpc1(tpc);
note->setTpc2(tpc);
}
else if (tag == "track") // for performance
note->setTrack(e.readInt());
else if (tag == "Accidental") {
Accidental* a = new Accidental(note->score());
a->setTrack(note->track());
a->read(e);
note->add(a);
}
else if (tag == "Tie") {
Tie* tie = new Tie(note->score());
tie->setParent(note);
tie->setTrack(note->track());
readTie206(e, tie);
tie->setStartNote(note);
note->setTieFor(tie);
}
else if (tag == "tpc2")
note->setTpc2(e.readInt());
else if (tag == "small")
note->setSmall(e.readInt());
else if (tag == "mirror")
note->readProperty(e, Pid::MIRROR_HEAD);
else if (tag == "dotPosition")
note->readProperty(e, Pid::DOT_POSITION);
else if (tag == "fixed")
note->setFixed(e.readBool());
else if (tag == "fixedLine")
note->setFixedLine(e.readInt());
else if (tag == "head")
note->readProperty(e, Pid::HEAD_GROUP);
else if (tag == "velocity")
note->setVeloOffset(e.readInt());
else if (tag == "play")
note->setPlay(e.readInt());
else if (tag == "tuning")
note->setTuning(e.readDouble());
else if (tag == "fret")
note->setFret(e.readInt());
else if (tag == "string")
note->setString(e.readInt());
else if (tag == "ghost")
note->setGhost(e.readInt());
else if (tag == "headType")
note->readProperty(e, Pid::HEAD_TYPE);
else if (tag == "veloType")
note->readProperty(e, Pid::VELO_TYPE);
else if (tag == "line")
note->setLine(e.readInt());
else if (tag == "Fingering") {
Fingering* f = new Fingering(note->score());
f->setTrack(note->track());
readText206(e, f, note);
note->add(f);
}
else if (tag == "Symbol") {
Symbol* s = new Symbol(note->score());
s->setTrack(note->track());
s->read(e);
note->add(s);
}
else if (tag == "Image") {
if (MScore::noImages)
e.skipCurrentElement();
else {
Image* image = new Image(note->score());
image->setTrack(note->track());
image->read(e);
note->add(image);
}
}
else if (tag == "Bend") {
Bend* b = new Bend(note->score());
b->setTrack(note->track());
b->read(e);
note->add(b);
}
else if (tag == "NoteDot") {
NoteDot* dot = new NoteDot(note->score());
dot->read(e);
note->add(dot);
}
else if (tag == "Events") {
note->playEvents().clear(); // remove default event
while (e.readNextStartElement()) {
const QStringRef& etag(e.name());
if (etag == "Event") {
NoteEvent ne;
ne.read(e);
note->playEvents().append(ne);
}
else
e.unknown();
}
if (Chord* ch = note->chord())
ch->setPlayEventType(PlayEventType::User);
}
else if (tag == "endSpanner") {
int id = e.intAttribute("id");
Spanner* sp = e.findSpanner(id);
if (sp) {
sp->setEndElement(note);
if (sp->isTie())
note->setTieBack(toTie(sp));
else {
if (sp->isGlissando() && note->parent() && note->parent()->isChord())
toChord(note->parent())->setEndsGlissando(true);
note->addSpannerBack(sp);
}
e.removeSpanner(sp);
}
else {
// End of a spanner whose start element will appear later;
// may happen for cross-staff spanner from a lower to a higher staff
// (for instance a glissando from bass to treble staff of piano).
// Create a place-holder spanner with end data
// (a TextLine is used only because both Spanner or SLine are abstract,
// the actual class does not matter, as long as it is derived from Spanner)
int id1 = e.intAttribute("id", -1);
Staff* staff = note->staff();
if (id1 != -1 &&
// DISABLE if pasting into a staff with linked staves
// because the glissando is not properly cloned into the linked staves
staff && (!e.pasteMode() || !staff->links() || staff->links()->empty())) {
Spanner* placeholder = new TextLine(note->score());
placeholder->setAnchor(Spanner::Anchor::NOTE);
placeholder->setEndElement(note);
placeholder->setTrack2(note->track());
placeholder->setTick(Fraction(0,1));
placeholder->setTick2(e.tick());
e.addSpanner(id1, placeholder);
}
}
e.readNext();
}
else if (tag == "TextLine"
|| tag == "Glissando") {
Spanner* sp = toSpanner(Element::name2Element(tag, note->score()));
// check this is not a lower-to-higher cross-staff spanner we already got
int id = e.intAttribute("id");
Spanner* placeholder = e.findSpanner(id);
if (placeholder && placeholder->endElement()) {
// if it is, fill end data from place-holder
sp->setAnchor(Spanner::Anchor::NOTE); // make sure we can set a Note as end element
sp->setEndElement(placeholder->endElement());
sp->setTrack2(placeholder->track2());
sp->setTick(e.tick()); // make sure tick2 will be correct
sp->setTick2(placeholder->tick2());
toNote(placeholder->endElement())->addSpannerBack(sp);
// remove no longer needed place-holder before reading the new spanner,
// as reading it also adds it to XML reader list of spanners,
// which would overwrite the place-holder
e.removeSpanner(placeholder);
delete placeholder;
}
sp->setTrack(note->track());
sp->read(e);
Staff* staff = note->staff();
// DISABLE pasting of glissandi into staves with other lionked staves
// because the glissando is not properly cloned into the linked staves
if (e.pasteMode() && staff && staff->links() && !staff->links()->empty()) {
e.removeSpanner(sp); // read() added the element to the XMLReader: remove it
delete sp;
}
else {
sp->setAnchor(Spanner::Anchor::NOTE);
sp->setStartElement(note);
sp->setTick(e.tick());
note->addSpannerFor(sp);
sp->setParent(note);
}
adjustPlacement(sp);
}
else if (tag == "offset")
note->Element::readProperties(e);
else if (note->Element::readProperties(e))
;
else
return false;
return true;
}
//---------------------------------------------------------
// readTextPropertyStyle206
// This reads only the 'style' tag, so that it can be read
// before setting anything else.
//---------------------------------------------------------
static bool readTextPropertyStyle206(XmlReader& e, TextBase* t, Element* be, QStringRef elementName)
{
QString s;
if (e.readAheadAvailable()) {
e.performReadAhead([&s, &elementName](QIODevice& dev) {
const QString closeTag = QString("</").append(elementName.toString()).append(">");
QByteArray arrLine = dev.readLine();
while (!arrLine.isEmpty()) {
QString line(arrLine);
if (line.contains("<style>")) {
QRegExp re("<style>([^<]+)</style>");
if (re.indexIn(line) > -1)
s = re.cap(1);
return;
}
else if (line.contains(closeTag)) {
return;
}
arrLine = dev.readLine();
}
});
}
else
return false;
if (s.isEmpty())
return true;
if (!be->isTuplet()) { // Hack
if (excessTextStyles206.find(s) != excessTextStyles206.end()) {
// Init the text with a style that can't be stored as a user style
// due to the limit on the number of user styles possible.
// Use User-1, since it has all the possible user style pids
t->initTid(Tid::DEFAULT);
std::map<Sid, QVariant> styleVals = excessTextStyles206[s];
for (const StyledProperty& p : *textStyle("User-1")) {
if (t->getProperty(p.pid) == t->propertyDefault(p.pid) && styleVals.find(p.sid) != styleVals.end())
t->setProperty(p.pid, styleVals[p.sid]);
}
}
else {
Tid ss;
ss = e.lookupUserTextStyle(s);
if (ss == Tid::TEXT_STYLES)
ss = textStyleFromName(s);
if (ss != Tid::TEXT_STYLES)
t->initTid(ss);
}
}
return true;
}
//---------------------------------------------------------
// readTextProperties206
//---------------------------------------------------------
static bool readTextProperties206(XmlReader& e, TextBase* t)
{
const QStringRef& tag(e.name());
if (tag == "style") {
e.skipCurrentElement(); // read in readTextPropertyStyle206
}
else if (tag == "foregroundColor") // same as "color" ?
e.skipCurrentElement();
else if (tag == "frame")
t->setFrameType(e.readBool() ? FrameType::SQUARE : FrameType::NO_FRAME);
else if (tag == "frameRound")
t->setFrameRound(e.readInt());
else if (tag == "circle") {
if (e.readBool())
t->setFrameType(FrameType::CIRCLE);
else {
if (t->circle())
t->setFrameType(FrameType::SQUARE);
}
}
else if (tag == "paddingWidthS")
t->setPaddingWidth(Spatium(e.readDouble()));
else if (tag == "frameWidthS")
t->setFrameWidth(Spatium(e.readDouble()));
else if (tag == "frameColor")
t->setFrameColor(e.readColor());
else if (tag == "backgroundColor")
t->setBgColor(e.readColor());
else if (tag == "halign") {
Align align = Align(int(t->align()) & int(~Align::HMASK));
const QString& val(e.readElementText());
if (val == "center")
align = align | Align::HCENTER;
else if (val == "right")
align = align | Align::RIGHT;
else if (val == "left")
;
else
qDebug("unknown alignment: <%s>", qPrintable(val));
t->setAlign(align);
}
else if (tag == "valign") {
Align align = Align(int(t->align()) & int(~Align::VMASK));
const QString& val(e.readElementText());
if (val == "center")
align = align | Align::VCENTER;
else if (val == "bottom")
align = align | Align::BOTTOM;
else if (val == "baseline")
align = align | Align::BASELINE;
else if (val == "top")
;
else
qDebug("unknown alignment: <%s>", qPrintable(val));
t->setAlign(align);
}
else if (tag == "pos") {
t->readProperty(e, Pid::OFFSET);
if ((char(t->align()) & char(Align::VMASK)) == char(Align::TOP))
t->ryoffset() += .5 * t->score()->spatium(); // HACK: bbox is different in 2.x
adjustPlacement(t);
}
else if (!t->readProperties(e))
return false;
return true;
}
//---------------------------------------------------------
// readText206
//---------------------------------------------------------
static void readText206(XmlReader& e, TextBase* t, Element* be)
{
readTextPropertyStyle206(e, t, be, e.name());
while (e.readNextStartElement()) {
if (!readTextProperties206(e, t))
e.unknown();
}
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
static void readTempoText(TempoText* t, XmlReader& e)
{
readTextPropertyStyle206(e, t, t, e.name());
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "tempo")
t->setTempo(e.readDouble());
else if (tag == "followText")
t->setFollowText(e.readInt());
else if (!readTextProperties206(e, t))
e.unknown();
}
// check sanity
if (t->xmlText().isEmpty()) {
t->setXmlText(QString("<sym>metNoteQuarterUp</sym> = %1").arg(lrint(60 * t->tempo())));
t->setVisible(false);
}
else
t->setXmlText(t->xmlText().replace("<sym>unicode", "<sym>met"));
}
//---------------------------------------------------------
// readMarker
//---------------------------------------------------------
static void readMarker(Marker* m, XmlReader& e)
{
readTextPropertyStyle206(e, m, m, e.name());
Marker::Type mt = Marker::Type::SEGNO;
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "label") {
QString s(e.readElementText());
m->setLabel(s);
mt = m->markerType(s);
}
else if (!readTextProperties206(e, m))
e.unknown();
}
m->setMarkerType(mt);
}
//---------------------------------------------------------
// readDynamic
//---------------------------------------------------------
static void readDynamic(Dynamic* d, XmlReader& e)
{
readTextPropertyStyle206(e, d, d, e.name());
while (e.readNextStartElement()) {
const QStringRef& tag = e.name();
if (tag == "subtype")
d->setDynamicType(e.readElementText());
else if (tag == "velocity")
d->setVelocity(e.readInt());
else if (tag == "dynType")
d->setDynRange(Dynamic::Range(e.readInt()));
else if (!readTextProperties206(e, d))
e.unknown();
}
}
//---------------------------------------------------------
// readTuplet
//---------------------------------------------------------
static void readTuplet(Tuplet* tuplet, XmlReader& e)
{
tuplet->setId(e.intAttribute("id", 0));
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "Number") {
Text* _number = new Text(tuplet->score());
_number->setParent(tuplet);
_number->setComposition(true);
tuplet->setNumber(_number);
// _number reads property defaults from parent tuplet as "composition" is set:
tuplet->resetNumberProperty();
readText206(e, _number, tuplet);
_number->setVisible(tuplet->visible()); //?? override saved property
_number->setTrack(tuplet->track());
// move property flags from _number
for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN, Pid::SIZE_SPATIUM_DEPENDENT })
tuplet->setPropertyFlags(p, _number->propertyFlags(p));
}
else if (!readTupletProperties206(e, tuplet))
e.unknown();
}
Fraction r = (tuplet->ratio() == Fraction(1,1)) ? tuplet->ratio() : tuplet->ratio().reduced();
Fraction f(r.denominator(), tuplet->baseLen().fraction().denominator());
tuplet->setTicks(f.reduced());
}
//---------------------------------------------------------
// readLyrics
//---------------------------------------------------------
static void readLyrics(Lyrics* lyrics, XmlReader& e)
{
int iEndTick = 0; // used for backward compatibility
Text* _verseNumber = 0;
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "endTick") {
// store <endTick> tag value until a <ticks> tag has been read
// which positions this lyrics element in the score
iEndTick = e.readInt();
}
else if (tag == "Number") {
_verseNumber = new Text(lyrics->score());
readText206(e, _verseNumber, lyrics);
_verseNumber->setParent(lyrics);
}
else if (tag == "style")
e.readElementText(); // ignore style
else if (!lyrics->readProperties(e))
e.unknown();
}
// if any endTick, make it relative to current tick
if (iEndTick)
lyrics->setTicks(Fraction::fromTicks(iEndTick) - e.tick());
if (_verseNumber) {
// TODO: add text to main text
delete _verseNumber;
}
lyrics->setAutoplace(true);
if (!lyrics->isStyled(Pid::OFFSET) && !e.pasteMode()) {
// fix offset for pre-3.1 scores
// 2.x and earlier: y offset was relative to staff; x offset was relative to center of notehead
lyrics->rxoffset() -= lyrics->symWidth(SymId::noteheadBlack) * 0.5;
//lyrics->ryoffset() -= lyrics->placeBelow() && lyrics->staff() ? lyrics->staff()->height() : 0.0;
// temporarily set placement to above, since the original offset is relative to top of staff
// depend on adjustPlacement() to change the placement if appropriate
lyrics->setPlacement(Placement::ABOVE);
adjustPlacement(lyrics);
}
}
//---------------------------------------------------------
// readDurationProperties206
//---------------------------------------------------------
bool readDurationProperties206(XmlReader& e, DurationElement* de)
{
if (e.name() == "Tuplet") {
int i = e.readInt();
Tuplet* t = e.findTuplet(i);
if (!t) {
qDebug("readDurationProperties206(): Tuplet id %d not found", i);
t = de->score()->searchTuplet(e, i);
if (t) {
qDebug(" ...found outside measure, input file corrupted?");
e.addTuplet(t);
}
}
if (t) {
de->setTuplet(t);
if (!de->score()->undoStack()->active()) // HACK, also added in Undo::AddElement()
t->add(de);
}
return true;
}
else if (de->Element::readProperties(e))
return true;
return false;
}
//---------------------------------------------------------
// readTupletProperties206
//---------------------------------------------------------
bool readTupletProperties206(XmlReader& e, Tuplet* de)
{
const QStringRef& tag(e.name());
if (de->readStyledProperty(e, tag))
;
else if (tag == "normalNotes")
de->setProperty(Pid::NORMAL_NOTES, e.readInt());
else if (tag == "actualNotes")
de->setProperty(Pid::ACTUAL_NOTES, e.readInt());
else if (tag == "p1")
de->setProperty(Pid::P1, e.readPoint() * de->score()->spatium());
else if (tag == "p2")
de->setProperty(Pid::P2, e.readPoint() * de->score()->spatium());
else if (tag == "baseNote")
de->setBaseLen(TDuration(e.readElementText()));
else if (tag == "Number") {
Text* _number = new Text(de->score());
de->setNumber(_number);
_number->setComposition(true);
_number->setParent(de);
// _number->setSubStyleId(SubStyleId::TUPLET);
// initSubStyle(SubStyleId::TUPLET); // hack: initialize number
for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN })
_number->resetProperty(p);
readText206(e, _number, de);
_number->setVisible(de->visible()); //?? override saved property
_number->setTrack(de->track());
// move property flags from _number
for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN })
de->setPropertyFlags(p, _number->propertyFlags(p));
}
else if (!readDurationProperties206(e, de))
return false;
return true;
}
//---------------------------------------------------------
// readChordRestProperties206
//---------------------------------------------------------
bool readChordRestProperties206(XmlReader& e, ChordRest* ch)
{
const QStringRef& tag(e.name());
if (tag == "durationType") {
ch->setDurationType(e.readElementText());
if (ch->actualDurationType().type() != TDuration::DurationType::V_MEASURE) {
if (ch->score()->mscVersion() < 112 && (ch->type() == ElementType::REST) &&
// for backward compatibility, convert V_WHOLE rests to V_MEASURE
// if long enough to fill a measure.
// OTOH, freshly created (un-initialized) rests have numerator == 0 (< 4/4)
// (see Fraction() constructor in fraction.h; this happens for instance
// when pasting selection from clipboard): they should not be converted
ch->ticks().numerator() != 0 &&
// rest durations are initialized to full measure duration when
// created upon reading the <Rest> tag (see Measure::read() )
// so a V_WHOLE rest in a measure of 4/4 or less => V_MEASURE
(ch->actualDurationType()==TDuration::DurationType::V_WHOLE && ch->ticks() <= Fraction(4, 4)) ) {
// old pre 2.0 scores: convert
ch->setDurationType(TDuration::DurationType::V_MEASURE);
}
else // not from old score: set duration fraction from duration type
ch->setTicks(ch->actualDurationType().fraction());
}
else {
if (ch->score()->mscVersion() <= 114) {
SigEvent event = ch->score()->sigmap()->timesig(e.tick());
ch->setTicks(event.timesig());
}
}
}
else if (tag == "BeamMode") {
QString val(e.readElementText());
Beam::Mode bm = Beam::Mode::AUTO;
if (val == "auto")
bm = Beam::Mode::AUTO;
else if (val == "begin")
bm = Beam::Mode::BEGIN;
else if (val == "mid")
bm = Beam::Mode::MID;
else if (val == "end")
bm = Beam::Mode::END;
else if (val == "no")
bm = Beam::Mode::NONE;
else if (val == "begin32")
bm = Beam::Mode::BEGIN32;
else if (val == "begin64")
bm = Beam::Mode::BEGIN64;
else
bm = Beam::Mode(val.toInt());
ch->setBeamMode(bm);
}
else if (tag == "Articulation") {
Element* el = readArticulation(ch, e);
if (el->isFermata())
ch->segment()->add(el);
else
ch->add(el);
}
else if (tag == "leadingSpace" || tag == "trailingSpace") {
qDebug("ChordRest: %s obsolete", tag.toLocal8Bit().data());
e.skipCurrentElement();
}
else if (tag == "Beam") {
int id = e.readInt();
Beam* beam = e.findBeam(id);
if (beam)
beam->add(ch); // also calls ch->setBeam(beam)
else
qDebug("Beam id %d not found", id);
}
else if (tag == "small")
ch->setSmall(e.readInt());
else if (tag == "duration")
ch->setTicks(e.readFraction());
else if (tag == "ticklen") { // obsolete (version < 1.12)
int mticks = ch->score()->sigmap()->timesig(e.tick()).timesig().ticks();
int i = e.readInt();
if (i == 0)
i = mticks;
if ((ch->type() == ElementType::REST) && (mticks == i)) {
ch->setDurationType(TDuration::DurationType::V_MEASURE);
ch->setTicks(Fraction::fromTicks(i));
}
else {
Fraction f = Fraction::fromTicks(i);
ch->setTicks(f);
ch->setDurationType(TDuration(f));
}
}
else if (tag == "dots")
ch->setDots(e.readInt());
else if (tag == "move")
ch->setStaffMove(e.readInt());
else if (tag == "Slur") {
int id = e.intAttribute("id");
if (id == 0)
id = e.intAttribute("number"); // obsolete
Spanner* spanner = e.findSpanner(id);
QString atype(e.attribute("type"));
if (!spanner) {
if (atype == "stop") {
SpannerValues sv;
sv.spannerId = id;
sv.track2 = ch->track();
sv.tick2 = e.tick();
e.addSpannerValues(sv);
}
else if (atype == "start")
qDebug("spanner: start without spanner");
}
else {
if (atype == "start") {
if (spanner->ticks() > Fraction(0,1) && spanner->tick() == Fraction(-1,1)) // stop has been read first
spanner->setTicks(spanner->ticks() - e.tick() - Fraction::fromTicks(1));
spanner->setTick(e.tick());
spanner->setTrack(ch->track());
if (spanner->type() == ElementType::SLUR)
spanner->setStartElement(ch);
if (e.pasteMode()) {
for (ScoreElement* el : spanner->linkList()) {
if (el == spanner)
continue;
Spanner* ls = static_cast<Spanner*>(el);
ls->setTick(spanner->tick());
for (ScoreElement* ee : ch->linkList()) {
ChordRest* cr = toChordRest(ee);
if (cr->score() == ee->score() && cr->staffIdx() == ls->staffIdx()) {
ls->setTrack(cr->track());
if (ls->type() == ElementType::SLUR)
ls->setStartElement(cr);
break;
}
}
}
}
}
else if (atype == "stop") {
spanner->setTick2(e.tick());
spanner->setTrack2(ch->track());
if (spanner->isSlur())
spanner->setEndElement(ch);
ChordRest* start = toChordRest(spanner->startElement());
if (start)
spanner->setTrack(start->track());
if (e.pasteMode()) {
for (ScoreElement* el : spanner->linkList()) {
if (el == spanner)
continue;
Spanner* ls = static_cast<Spanner*>(el);
ls->setTick2(spanner->tick2());
for (ScoreElement* ee : ch->linkList()) {
ChordRest* cr = toChordRest(ee);
if (cr->score() == ee->score() && cr->staffIdx() == ls->staffIdx()) {
ls->setTrack2(cr->track());
if (ls->type() == ElementType::SLUR)
ls->setEndElement(cr);
break;
}
}
}
}
}
else
qDebug("readChordRestProperties206(): unknown Slur type <%s>", qPrintable(atype));
}
e.readNext();
}
else if (tag == "Lyrics") {
Lyrics* l = new Lyrics(ch->score());
l->setTrack(e.track());
readLyrics(l, e);
ch->add(l);
}
else if (tag == "pos") {
QPointF pt = e.readPoint();
ch->setOffset(pt * ch->spatium());
}
else if (!readDurationProperties206(e, ch))
return false;
return true;
}
//---------------------------------------------------------
// readChordProperties206
//---------------------------------------------------------
bool readChordProperties206(XmlReader& e, Chord* ch)
{
const QStringRef& tag(e.name());
if (tag == "Note") {
Note* note = new Note(ch->score());
// the note needs to know the properties of the track it belongs to
note->setTrack(ch->track());
note->setChord(ch);
readNote(note, e);
ch->add(note);
}
else if (readChordRestProperties206(e, ch))
;
else if (tag == "Stem") {
Stem* s = new Stem(ch->score());
s->read(e);
ch->add(s);
}
else if (tag == "Hook") {
Hook* hook = new Hook(ch->score());
hook->read(e);
ch->add(hook);
}
else if (tag == "appoggiatura") {
ch->setNoteType(NoteType::APPOGGIATURA);
e.readNext();
}
else if (tag == "acciaccatura") {
ch->setNoteType(NoteType::ACCIACCATURA);
e.readNext();
}
else if (tag == "grace4") {
ch->setNoteType(NoteType::GRACE4);
e.readNext();
}
else if (tag == "grace16") {
ch->setNoteType(NoteType::GRACE16);
e.readNext();
}
else if (tag == "grace32") {
ch->setNoteType(NoteType::GRACE32);
e.readNext();
}
else if (tag == "grace8after") {
ch->setNoteType(NoteType::GRACE8_AFTER);
e.readNext();
}
else if (tag == "grace16after") {
ch->setNoteType(NoteType::GRACE16_AFTER);
e.readNext();
}
else if (tag == "grace32after") {
ch->setNoteType(NoteType::GRACE32_AFTER);
e.readNext();
}
else if (tag == "StemSlash") {
StemSlash* ss = new StemSlash(ch->score());
ss->read(e);
ch->add(ss);
}
else if (ch->readProperty(tag, e, Pid::STEM_DIRECTION))
;
else if (tag == "noStem")
ch->setNoStem(e.readInt());
else if (tag == "Arpeggio") {
Arpeggio* arpeggio = new Arpeggio(ch->score());
arpeggio->setTrack(ch->track());
arpeggio->read(e);
arpeggio->setParent(ch);
ch->add(arpeggio);
}
// old glissando format, chord-to-chord, attached to its final chord
else if (tag == "Glissando") {
// the measure we are reading is not inserted in the score yet
// as well as, possibly, the glissando intended initial chord;
// then we cannot fully link the glissando right now;
// temporarily attach the glissando to its final note as a back spanner;
// after the whole score is read, Score::connectTies() will look for
// the suitable initial note
Note* finalNote = ch->upNote();
Glissando* gliss = new Glissando(ch->score());
gliss->read(e);
gliss->setAnchor(Spanner::Anchor::NOTE);
gliss->setStartElement(nullptr);
gliss->setEndElement(nullptr);
// in TAB, use straight line with no text
if (ch->score()->staff(e.track() >> 2)->isTabStaff(ch->tick())) {
gliss->setGlissandoType(GlissandoType::STRAIGHT);
gliss->setShowText(false);
}
finalNote->addSpannerBack(gliss);
}
else if (tag == "Tremolo") {
Tremolo* tremolo = new Tremolo(ch->score());
tremolo->setTrack(ch->track());
tremolo->read(e);
tremolo->setParent(ch);
tremolo->setDurationType(ch->durationType());
ch->setTremolo(tremolo);
}
else if (tag == "tickOffset") // obsolete
;
else if (tag == "ChordLine") {
ChordLine* cl = new ChordLine(ch->score());
cl->read(e);
QPointF o = cl->offset();
cl->setOffset(0.0, 0.0);
ch->add(cl);
e.fixOffsets().append({cl, o});
}
else
return false;
return true;
}
//---------------------------------------------------------
// convertDoubleArticulations
// Replace double articulations with proper SMuFL
// symbols which were not available for use prior to 3.0
//---------------------------------------------------------
static void convertDoubleArticulations(Chord* chord, XmlReader& e)
{
std::vector<Articulation*> pairableArticulations;
for (Articulation* a : chord->articulations()) {
if (a->isStaccato() || a->isTenuto()
|| a->isAccent() || a->isMarcato()) {
pairableArticulations.push_back(a);
};
}
if (pairableArticulations.size() != 2)
// Do not replace triple articulation if this happens
return;
SymId newSymId = SymId::noSym;
for (int i = 0; i < 2; ++i) {
if (newSymId != SymId::noSym)
break;
Articulation* ai = pairableArticulations[i];
Articulation* aj = pairableArticulations[(i == 0) ? 1 : 0];
if (ai->isStaccato()) {
if (aj->isAccent())
newSymId = SymId::articAccentStaccatoAbove;
else if (aj->isMarcato())
newSymId = SymId::articMarcatoStaccatoAbove;
else if (aj->isTenuto())
newSymId = SymId::articTenutoStaccatoAbove;
}
else if (ai->isTenuto()) {
if (aj->isAccent())
newSymId = SymId::articTenutoAccentAbove;
else if (aj->isMarcato())
newSymId = SymId::articMarcatoTenutoAbove;
}
}
if (newSymId != SymId::noSym) {
// We reuse old articulation and change symbol ID
// rather than constructing a new articulation
// in order to preserve its other properties.
Articulation* newArtic = pairableArticulations[0];
for (Articulation* a : pairableArticulations) {
chord->remove(a);
if (a != newArtic) {
if (LinkedElements* link = a->links())
e.linkIds().remove(link->lid());
delete a;
}
}
ArticulationAnchor anchor = newArtic->anchor();
newArtic->setSymId(newSymId);
newArtic->setAnchor(anchor);
chord->add(newArtic);
}
}
//---------------------------------------------------------
// readChord
//---------------------------------------------------------
static void readChord(Chord* chord, XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "Note") {
Note* note = new Note(chord->score());
// the note needs to know the properties of the track it belongs to
note->setTrack(chord->track());
note->setChord(chord);
readNote(note, e);
chord->add(note);
}
else if (tag == "Stem") {
Stem* stem = new Stem(chord->score());
while (e.readNextStartElement()) {
const QStringRef& t(e.name());
if (t == "subtype") // obsolete
e.skipCurrentElement();
else if (!stem->readProperties(e))
e.unknown();
}
chord->add(stem);
}
else if (tag == "Lyrics") {
Lyrics* lyrics = new Lyrics(chord->score());
lyrics->setTrack(e.track());
readLyrics(lyrics, e);
chord->add(lyrics);
}
else if (readChordProperties206(e, chord))
;
else
e.unknown();
}
convertDoubleArticulations(chord, e);
}
//---------------------------------------------------------
// readRest
//---------------------------------------------------------
static void readRest(Rest* rest, XmlReader& e)
{
while (e.readNextStartElement()) {
if (!readChordRestProperties206(e, rest))
e.unknown();
}
}
//---------------------------------------------------------
// readTextLineProperties
//---------------------------------------------------------
static bool readTextLineProperties(XmlReader& e, TextLineBase* tl)
{
const QStringRef& tag(e.name());
if (tag == "beginText") {
Text* text = new Text(tl->score());
readText206(e, text, tl);
tl->setBeginText(text->xmlText());
delete text;
}
else if (tag == "continueText") {
Text* text = new Text(tl->score());
readText206(e, text, tl);
tl->setContinueText(text->xmlText());
delete text;
}
else if (tag == "endText") {
Text* text = new Text(tl->score());
readText206(e, text, tl);
tl->setEndText(text->xmlText());
delete text;
}
else if (tag == "beginHook")
tl->setBeginHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
else if (tag == "endHook")
tl->setEndHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
else if (tag == "beginHookType")
tl->setBeginHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
else if (tag == "endHookType")
tl->setEndHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
else if (tl->readProperties(e))
return true;
return true;
}
//---------------------------------------------------------
// readVolta206
//---------------------------------------------------------
static void readVolta206(XmlReader& e, Volta* volta)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "endings") {
QString s = e.readElementText();
QStringList sl = s.split(",", QString::SkipEmptyParts);
volta->endings().clear();
for (const QString& l : sl) {
int i = l.simplified().toInt();
volta->endings().append(i);
}
}
else if (tag == "lineWidth") {
volta->setLineWidth(e.readDouble() * volta->spatium());
// TODO lineWidthStyle = PropertyStyle::UNSTYLED;
}
else if (!readTextLineProperties(e, volta))
e.unknown();
}
adjustPlacement(volta);
}
//---------------------------------------------------------
// readPedal
//---------------------------------------------------------
static void readPedal(XmlReader& e, Pedal* pedal)
{
while (e.readNextStartElement()) {
if (!readTextLineProperties(e, pedal))
e.unknown();
}
adjustPlacement(pedal);
}
//---------------------------------------------------------
// readOttava
//---------------------------------------------------------
static void readOttava(XmlReader& e, Ottava* ottava)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "subtype") {
QString s = e.readElementText();
bool ok;
int idx = s.toInt(&ok);
if (!ok) {
idx = 0; // OttavaType::OTTAVA_8VA;
int i = 0;
for (auto p : { "8va","8vb","15ma","15mb","22ma","22mb" } ) {
if (p == s) {
idx = i;
break;
}
++i;
}
}
ottava->setOttavaType(OttavaType(idx));
}
else if (tag == "numbersOnly") {
ottava->setNumbersOnly(e.readBool());
//TODO numbersOnlyStyle = PropertyFlags::UNSTYLED;
}
else if (!readTextLineProperties(e, ottava))
e.unknown();
}
ottava->styleChanged();
adjustPlacement(ottava);
}
//---------------------------------------------------------
// readHairpin206
//---------------------------------------------------------
void readHairpin206(XmlReader& e, Hairpin* h)
{
bool useText = false;
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "subtype")
h->setHairpinType(HairpinType(e.readInt()));
else if (tag == "lineWidth") {
h->setLineWidth(e.readDouble() * h->spatium());
// lineWidthStyle = PropertyFlags::UNSTYLED;
}
else if (tag == "hairpinHeight") {
h->setHairpinHeight(Spatium(e.readDouble()));
// hairpinHeightStyle = PropertyFlags::UNSTYLED;
}
else if (tag == "hairpinContHeight") {
h->setHairpinContHeight(Spatium(e.readDouble()));
// hairpinContHeightStyle = PropertyFlags::UNSTYLED;
}
else if (tag == "hairpinCircledTip")
h->setHairpinCircledTip(e.readInt());
else if (tag == "veloChange")
h->setVeloChange(e.readInt());
else if (tag == "dynType")
h->setDynRange(Dynamic::Range(e.readInt()));
else if (tag == "useTextLine") { // < 206
e.readInt();
if (h->hairpinType() == HairpinType::CRESC_HAIRPIN)
h->setHairpinType(HairpinType::CRESC_LINE);
else if (h->hairpinType() == HairpinType::DECRESC_HAIRPIN)
h->setHairpinType(HairpinType::DECRESC_LINE);
useText = true;
}
else if (!readTextLineProperties(e, h))
e.unknown();
}
if (!useText) {
h->setBeginText("");
h->setContinueText("");
h->setEndText("");
}
adjustPlacement(h);
}
//---------------------------------------------------------
// readTrill206
//---------------------------------------------------------
void readTrill206(XmlReader& e, Trill* t)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "subtype")
t->setTrillType(e.readElementText());
else if (tag == "Accidental") {
Accidental* _accidental = new Accidental(t->score());
readAccidental206(_accidental, e);
_accidental->setParent(t);
t->setAccidental(_accidental);
}
else if (tag == "ornamentStyle")
t->readProperty(e, Pid::ORNAMENT_STYLE);
else if (tag == "play")
t->setPlayArticulation(e.readBool());
else if (!t->SLine::readProperties(e))
e.unknown();
}
adjustPlacement(t);
}
//---------------------------------------------------------
// readTextLine206
//---------------------------------------------------------
void readTextLine206(XmlReader& e, TextLineBase* tlb)
{
while (e.readNextStartElement()) {
if (!readTextLineProperties(e, tlb))
e.unknown();
}
adjustPlacement(tlb);
}
//---------------------------------------------------------
// setFermataPlacement
// set fermata placement from old ArticulationAnchor
// for backwards compatibility
//---------------------------------------------------------
static void setFermataPlacement(Element* el, ArticulationAnchor anchor, Direction direction)
{
if (direction == Direction::UP)
el->setPlacement(Placement::ABOVE);
else if (direction == Direction::DOWN)
el->setPlacement(Placement::BELOW);
else {
switch (anchor) {
case ArticulationAnchor::TOP_STAFF:
case ArticulationAnchor::TOP_CHORD:
el->setPlacement(Placement::ABOVE);
break;
case ArticulationAnchor::BOTTOM_STAFF:
case ArticulationAnchor::BOTTOM_CHORD:
el->setPlacement(Placement::BELOW);
break;
case ArticulationAnchor::CHORD:
break;
default:
break;
}
}
}
//---------------------------------------------------------
// readArticulation
//---------------------------------------------------------
Element* readArticulation(Element* parent, XmlReader& e)
{
Element* el = 0;
SymId sym = SymId::fermataAbove; // default -- backward compatibility (no type = ufermata in 1.2)
ArticulationAnchor anchor = ArticulationAnchor::TOP_STAFF;
Direction direction = Direction::AUTO;
Score* score = parent->score();
int track = parent->track();
double timeStretch = 0.0;
bool useDefaultPlacement = true;
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "subtype") {
QString s = e.readElementText();
if (s[0].isDigit()) {
int oldType = s.toInt();
sym = articulationNames[oldType].id;
}
else {
sym = oldArticulationNames2SymId(s);
if (sym == SymId::noSym) {
struct {
const char* name;
bool up;
SymId id;
} al[] =
{
{ "fadein", true, SymId::guitarFadeIn },
{ "fadeout", true, SymId::guitarFadeOut },
{ "volumeswell", true, SymId::guitarVolumeSwell },
{ "wigglesawtooth", true, SymId::wiggleSawtooth },
{ "wigglesawtoothwide", true, SymId::wiggleSawtoothWide },
{ "wigglevibratolargefaster", true, SymId::wiggleVibratoLargeFaster },
{ "wigglevibratolargeslowest", true, SymId::wiggleVibratoLargeSlowest },
{ "umarcato", true, SymId::articMarcatoAbove },
{ "dmarcato", false, SymId::articMarcatoBelow },
{ "ufermata", true, SymId::fermataAbove },
{ "dfermata", false, SymId::fermataBelow },
{ "ushortfermata", true, SymId::fermataShortAbove },
{ "dshortfermata", false, SymId::fermataShortBelow },
{ "ulongfermata", true, SymId::fermataLongAbove },
{ "dlongfermata", false, SymId::fermataLongBelow },
{ "uverylongfermata", true, SymId::fermataVeryLongAbove },
{ "dverylongfermata", false, SymId::fermataVeryLongBelow },
// watch out, bug in 1.2 uportato and dportato are reversed
{ "dportato", true, SymId::articTenutoStaccatoAbove },
{ "uportato", false, SymId::articTenutoStaccatoBelow },
{ "ustaccatissimo", true, SymId::articStaccatissimoAbove },
{ "dstaccatissimo", false, SymId::articStaccatissimoBelow }
};
int i;
int n = sizeof(al) / sizeof(*al);
for (i = 0; i < n; ++i) {
if (s == al[i].name) {
sym = al[i].id;
bool up = al[i].up;
direction = up ? Direction::UP : Direction::DOWN;
if ((direction == Direction::DOWN) != (track & 1))
useDefaultPlacement = false;
break;
}
}
if (i == n) {
sym = Sym::name2id(s);
if (sym == SymId::noSym)
qDebug("Articulation: unknown type <%s>", qPrintable(s));
}
}
}
switch (sym) {
case SymId::fermataAbove:
case SymId::fermataBelow:
case SymId::fermataShortAbove:
case SymId::fermataShortBelow:
case SymId::fermataLongAbove:
case SymId::fermataLongBelow:
case SymId::fermataVeryLongAbove:
case SymId::fermataVeryLongBelow:
el = new Fermata(sym, score);
break;
default:
el = new Articulation(sym, score);
toArticulation(el)->setDirection(direction);
break;
};
}
else if (tag == "anchor") {
useDefaultPlacement = false;
if (!el || el->isFermata())
anchor = ArticulationAnchor(e.readInt());
else
el->readProperties(e);
}
else if (tag == "direction") {
useDefaultPlacement = false;
if (!el || el->isFermata())
direction = toDirection(e.readElementText());
else
el->readProperties(e);
}
else if (tag == "timeStretch") {
timeStretch = e.readDouble();
}
else {
if (!el) {
qDebug("not handled <%s>", qPrintable(tag.toString()));
}
if (!el || !el->readProperties(e))
e.unknown();
}
}
// Special case for "no type" = ufermata, with missing subtype tag
if (!el)
el = new Fermata(sym, score);
if (el->isFermata()) {
if (timeStretch != 0.0)
el->setProperty(Pid::TIME_STRETCH, timeStretch);
if (useDefaultPlacement)
el->setPlacement(track & 1 ? Placement::BELOW : Placement::ABOVE);
else
setFermataPlacement(el, anchor, direction);
}
el->setTrack(track);
return el;
}
//---------------------------------------------------------
// readSlurTieProperties
//---------------------------------------------------------
static bool readSlurTieProperties(XmlReader& e, SlurTie* st)
{
const QStringRef& tag(e.name());
if (st->readProperty(tag, e, Pid::SLUR_DIRECTION))
;
else if (tag == "lineType")
st->setLineType(e.readInt());
else if (tag == "SlurSegment") {
SlurTieSegment* s = st->newSlurTieSegment();
s->read(e);
st->add(s);
}
else if (!st->Element::readProperties(e))
return false;
return true;
}
//---------------------------------------------------------
// readSlur206
//---------------------------------------------------------
void readSlur206(XmlReader& e, Slur* s)
{
s->setTrack(e.track()); // set staff
e.addSpanner(e.intAttribute("id"), s);
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "track2")
s->setTrack2(e.readInt());
else if (tag == "startTrack") // obsolete
s->setTrack(e.readInt());
else if (tag == "endTrack") // obsolete
e.readInt();
else if (!readSlurTieProperties(e, s))
e.unknown();
}
if (s->track2() == -1)
s->setTrack2(s->track());
}
//---------------------------------------------------------
// readTie206
//---------------------------------------------------------
void readTie206(XmlReader& e, Tie* t)
{
e.addSpanner(e.intAttribute("id"), t);
while (e.readNextStartElement()) {
if (readSlurTieProperties(e, t))
;
else
e.unknown();
}
if (t->score()->mscVersion() <= 114 && t->spannerSegments().size() == 1) {
// ignore manual adjustments to single-segment ties in older scores
TieSegment* ss = t->frontSegment();
QPointF zeroP;
ss->ups(Grip::START).off = zeroP;
ss->ups(Grip::BEZIER1).off = zeroP;
ss->ups(Grip::BEZIER2).off = zeroP;
ss->ups(Grip::END).off = zeroP;
ss->setOffset(zeroP);
ss->setUserOff2(zeroP);
}
}
//---------------------------------------------------------
// readMeasure
//---------------------------------------------------------
static void readMeasure(Measure* m, int staffIdx, XmlReader& e)
{
Segment* segment = 0;
qreal _spatium = m->spatium();
Score* score = m->score();
QList<Chord*> graceNotes;
e.tuplets().clear();
e.setTrack(staffIdx * VOICES);
m->createStaves(staffIdx);
// tick is obsolete
if (e.hasAttribute("tick"))
e.setTick(Fraction::fromTicks(score->fileDivision(e.intAttribute("tick"))));
bool irregular;
if (e.hasAttribute("len")) {
QStringList sl = e.attribute("len").split('/');
if (sl.size() == 2)
m->setTicks(Fraction(sl[0].toInt(), sl[1].toInt()));
else
qDebug("illegal measure size <%s>", qPrintable(e.attribute("len")));
irregular = true;
score->sigmap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
score->sigmap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
}
else
irregular = false;
Staff* staff = score->staff(staffIdx);
Fraction timeStretch(staff->timeStretch(m->tick()));
// keep track of tick of previous element
// this allows markings that need to apply to previous element to do so
// even though we may have already advanced to next tick position
Fraction lastTick = e.tick();
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "move")
e.setTick(e.readFraction() + m->tick());
else if (tag == "tick") {
e.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
lastTick = e.tick();
}
else if (tag == "BarLine") {
Fermata* fermataAbove = nullptr;
Fermata* fermataBelow = nullptr;
BarLine* bl = new BarLine(score);
bl->setTrack(e.track());
while (e.readNextStartElement()) {
const QStringRef& t(e.name());
if (t == "subtype")
bl->setBarLineType(e.readElementText());
else if (t == "customSubtype") // obsolete
e.readInt();
else if (t == "span") {
//TODO bl->setSpanFrom(e.intAttribute("from", bl->spanFrom())); // obsolete
// bl->setSpanTo(e.intAttribute("to", bl->spanTo())); // obsolete
int span = e.readInt();
if (span)
span--;
bl->setSpanStaff(span);
}
else if (t == "spanFromOffset")
bl->setSpanFrom(e.readInt());
else if (t == "spanToOffset")
bl->setSpanTo(e.readInt());
else if (t == "Articulation") {
Element* el = readArticulation(bl, e);
if (el->isFermata()) {
if (el->placement() == Placement::ABOVE)
fermataAbove = toFermata(el);
else {
fermataBelow = toFermata(el);
fermataBelow->setTrack((bl->staffIdx() + bl->spanStaff()) * VOICES);
}
}
else
bl->add(el);
}
else if (!bl->Element::readProperties(e))
e.unknown();
}
//
// StartRepeatBarLine: always at the beginning tick of a measure, always BarLineType::START_REPEAT
// BarLine: in the middle of a measure, has no semantic
// EndBarLine: at the end tick of a measure
// BeginBarLine: first segment of a measure
SegmentType st;
if ((e.tick() != m->tick()) && (e.tick() != m->endTick()))
st = SegmentType::BarLine;
else if (bl->barLineType() == BarLineType::START_REPEAT && e.tick() == m->tick())
st = SegmentType::StartRepeatBarLine;
else if (e.tick() == m->tick() && segment == 0)
st = SegmentType::BeginBarLine;
else
st = SegmentType::EndBarLine;
segment = m->getSegment(st, e.tick());
segment->add(bl);
bl->layout();
if (fermataAbove)
segment->add(fermataAbove);
if (fermataBelow)
segment->add(fermataBelow);
}
else if (tag == "Chord") {
Chord* chord = new Chord(score);
chord->setTrack(e.track());
segment = m->getSegment(SegmentType::ChordRest, e.tick());
chord->setParent(segment);
readChord(chord, e);
if (chord->noteType() != NoteType::NORMAL)
graceNotes.push_back(chord);
else {
segment->add(chord);
for (int i = 0; i < graceNotes.size(); ++i) {
Chord* gc = graceNotes[i];
gc->setGraceIndex(i);
chord->add(gc);
}
graceNotes.clear();
Fraction crticks = chord->actualTicks();
lastTick = e.tick();
e.incTick(crticks);
}
}
else if (tag == "Rest") {
Rest* rest = new Rest(score);
rest->setDurationType(TDuration::DurationType::V_MEASURE);
rest->setTicks(m->timesig()/timeStretch);
rest->setTrack(e.track());
segment = m->getSegment(SegmentType::ChordRest, e.tick());
rest->setParent(segment);
readRest(rest, e);
segment->add(rest);
if (!rest->ticks().isValid()) // hack
rest->setTicks(m->timesig()/timeStretch);
lastTick = e.tick();
e.incTick(rest->actualTicks());
}
else if (tag == "Breath") {
Breath* breath = new Breath(score);
breath->setTrack(e.track());
Fraction tick = e.tick();
breath->read(e);
// older scores placed the breath segment right after the chord to which it applies
// rather than before the next chordrest segment with an element for the staff
// result would be layout too far left if there are other segments due to notes in other staves
// we need to find tick of chord to which this applies, and add its duration
Fraction prevTick;
if (e.tick() < tick)
prevTick = e.tick(); // use our own tick if we explicitly reset to earlier position
else
prevTick = lastTick; // otherwise use tick of previous tick/chord/rest tag
// find segment
Segment* prev = m->findSegment(SegmentType::ChordRest, prevTick);
if (prev) {
// find chordrest
ChordRest* lastCR = toChordRest(prev->element(e.track()));
if (lastCR)
tick = prevTick + lastCR->actualTicks();
}
segment = m->getSegment(SegmentType::Breath, tick);
segment->add(breath);
}
else if (tag == "endSpanner") {
int id = e.attribute("id").toInt();
Spanner* spanner = e.findSpanner(id);
if (spanner) {
spanner->setTicks(e.tick() - spanner->tick());
// if (spanner->track2() == -1)
// the absence of a track tag [?] means the
// track is the same as the beginning of the slur
if (spanner->track2() == -1)
spanner->setTrack2(spanner->track() ? spanner->track() : e.track());
}
else {
// remember "endSpanner" values
SpannerValues sv;
sv.spannerId = id;
sv.track2 = e.track();
sv.tick2 = e.tick();
e.addSpannerValues(sv);
}
e.readNext();
}
else if (tag == "Slur") {
Slur *sl = new Slur(score);
sl->setTick(e.tick());
readSlur206(e, sl);
//
// check if we already saw "endSpanner"
//
int id = e.spannerId(sl);
const SpannerValues* sv = e.spannerValues(id);
if (sv) {
sl->setTick2(sv->tick2);
sl->setTrack2(sv->track2);
}
score->addSpanner(sl);
}
else if (tag == "HairPin"
|| tag == "Pedal"
|| tag == "Ottava"
|| tag == "Trill"
|| tag == "TextLine"
|| tag == "Volta") {
Spanner* sp = toSpanner(Element::name2Element(tag, score));
sp->setTrack(e.track());
sp->setTick(e.tick());
sp->eraseSpannerSegments();
e.addSpanner(e.intAttribute("id", -1), sp);
if (tag == "Volta")
readVolta206(e, toVolta(sp));
else if (tag == "Pedal")
readPedal(e, toPedal(sp));
else if (tag == "Ottava")
readOttava(e, toOttava(sp));
else if (tag == "HairPin")
readHairpin206(e, toHairpin(sp));
else if (tag == "Trill")
readTrill206(e, toTrill(sp));
else
readTextLine206(e, toTextLineBase(sp));
score->addSpanner(sp);
//
// check if we already saw "endSpanner"
//
int id = e.spannerId(sp);
const SpannerValues* sv = e.spannerValues(id);
if (sv) {
sp->setTicks(sv->tick2 - sp->tick());
sp->setTrack2(sv->track2);
}
}
else if (tag == "RepeatMeasure") {
RepeatMeasure* rm = new RepeatMeasure(score);
rm->setTrack(e.track());
readRest(rm, e);
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(rm);
lastTick = e.tick();
e.incTick(m->ticks());
}
else if (tag == "Clef") {
Clef* clef = new Clef(score);
clef->setTrack(e.track());
clef->read(e);
clef->setGenerated(false);
if (e.tick().isZero()) {
if (score->staff(staffIdx)->clef(Fraction(0,1)) != clef->clefType())
score->staff(staffIdx)->setDefaultClefType(clef->clefType());
if (clef->links() && clef->links()->size() == 1) {
e.linkIds().remove(clef->links()->lid());
qDebug("remove link %d", clef->links()->lid());
}
delete clef;
continue;
}
// there may be more than one clef segment for same tick position
if (!segment) {
// this is the first segment of measure
segment = m->getSegment(SegmentType::Clef, e.tick());
}
else {
bool firstSegment = false;
// the first clef may be missing and is added later in layout
for (Segment* s = m->segments().first(); s && s->tick() == e.tick(); s = s->next()) {
if (s->segmentType() == SegmentType::Clef
// hack: there may be other segment types which should
// generate a clef at current position
|| s->segmentType() == SegmentType::StartRepeatBarLine
) {
firstSegment = true;
break;
}
}
if (firstSegment) {
Segment* ns = 0;
if (segment->next()) {
ns = segment->next();
while (ns && ns->tick() < e.tick())
ns = ns->next();
}
segment = 0;
for (Segment* s = ns; s && s->tick() == e.tick(); s = s->next()) {
if (s->segmentType() == SegmentType::Clef) {
segment = s;
break;
}
}
if (!segment) {
segment = new Segment(m, SegmentType::Clef, e.tick() - m->tick());
m->segments().insert(segment, ns);
}
}
else {
// this is the first clef: move to left
segment = m->getSegment(SegmentType::Clef, e.tick());
}
}
if (e.tick() != m->tick())
clef->setSmall(true); // TODO: layout does this ?
segment->add(clef);
}
else if (tag == "TimeSig") {
TimeSig* ts = new TimeSig(score);
ts->setTrack(e.track());
ts->read(e);
// if time sig not at beginning of measure => courtesy time sig
Fraction currTick = e.tick();
bool courtesySig = (currTick > m->tick());
if (courtesySig) {
// if courtesy sig., just add it without map processing
segment = m->getSegment(SegmentType::TimeSigAnnounce, currTick);
segment->add(ts);
}
else {
// if 'real' time sig., do full process
segment = m->getSegment(SegmentType::TimeSig, currTick);
segment->add(ts);
timeStretch = ts->stretch().reduced();
m->setTimesig(ts->sig() / timeStretch);
if (irregular) {
score->sigmap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
score->sigmap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
}
else {
m->setTicks(m->timesig());
score->sigmap()->add(m->tick().ticks(), SigEvent(m->timesig()));
}
}
}
else if (tag == "KeySig") {
KeySig* ks = new KeySig(score);
ks->setTrack(e.track());
ks->read(e);
Fraction curTick = e.tick();
if (!ks->isCustom() && !ks->isAtonal() && ks->key() == Key::C && curTick.isZero()) {
// ignore empty key signature
qDebug("remove keysig c at tick 0");
if (ks->links()) {
if (ks->links()->size() == 1)
e.linkIds().remove(ks->links()->lid());
}
delete ks;
}
else {
// if key sig not at beginning of measure => courtesy key sig
bool courtesySig = (curTick == m->endTick());
segment = m->getSegment(courtesySig ? SegmentType::KeySigAnnounce : SegmentType::KeySig, curTick);
segment->add(ks);
if (!courtesySig)
staff->setKey(curTick, ks->keySigEvent());
}
}
else if (tag == "Text" || tag == "StaffText") {
// MuseScore 3 has different types for system text and
// staff text while MuseScore 2 didn't.
// We need to decide first which one we should create.
QString styleName;
if (e.readAheadAvailable()) {
e.performReadAhead([&styleName, tag](QIODevice& dev) {
const QString closeTag = QString("</").append(tag).append(">");
QByteArray arrLine = dev.readLine();
while (!arrLine.isEmpty()) {
QString line(arrLine);
if (line.contains("<style>")) {
QRegExp re("<style>([A-z0-9]+)</style>");
if (re.indexIn(line) > -1)
styleName = re.cap(1);
return;
}
if (line.contains(closeTag))
return;
arrLine = dev.readLine();
}
});
}
StaffTextBase* t;
if (styleName == "System" || styleName == "Tempo"
|| styleName == "Marker" || styleName == "Jump"
|| styleName == "Volta") // TODO: is it possible to get it from style?
t = new SystemText(score);
else
t = new StaffText(score);
t->setTrack(e.track());
readText206(e, t, t);
if (t->empty()) {
if (t->links()) {
if (t->links()->size() == 1) {
qDebug("reading empty text: deleted lid = %d", t->links()->lid());
e.linkIds().remove(t->links()->lid());
delete t;
}
}
}
else {
#if 0
// This code was added at commit ed5b615
// but it seems to no longer be appropriate.
// autoplace is usually true,
// exception is text within staff,
// and in this case offset is already correct without further adjustment.
if (!t->autoplace()) {
// adjust position
qreal userY = t->offset().y() / t->spatium();
qreal yo = -(-2.0 - userY) * t->spatium();
t->layout();
t->setAlign(Align::LEFT | Align::TOP);
t->ryoffset() = yo;
}
#endif
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(t);
}
}
//----------------------------------------------------
// Annotation
else if (tag == "Dynamic") {
Dynamic* dyn = new Dynamic(score);
dyn->setTrack(e.track());
readDynamic(dyn, e);
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(dyn);
}
else if (tag == "RehearsalMark") {
RehearsalMark* el = new RehearsalMark(score);
el->setTrack(e.track());
readText206(e, el, el);
// el->setOffset(el->offset() - el->score()->styleValue(Pid::OFFSET, Sid::rehearsalMarkPosAbove).toPointF());
// if (el->offset().isNull())
// el->setAutoplace(true);
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(el);
}
#if 0
else if (tag == "StaffText") {
StaffText* el = new StaffText(score);
el->setTrack(e.track());
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "foregroundColor")
e.skipCurrentElement();
else if (!el->readProperties(e))
e.unknown();
}
TextBase* tt = static_cast<TextBase*>(el);
tt->setXmlText(tt->xmlText().replace("<sym>unicode", "<sym>met"));
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(el);
}
#endif
else if (tag == "Harmony"
|| tag == "FretDiagram"
|| tag == "TremoloBar"
|| tag == "Symbol"
|| tag == "InstrumentChange"
|| tag == "StaffState"
|| tag == "FiguredBass"
) {
Element* el = Element::name2Element(tag, score);
// hack - needed because tick tags are unreliable in 1.3 scores
// for symbols attached to anything but a measure
el->setTrack(e.track());
el->read(e);
if (el->staff() && (el->isHarmony() || el->isFretDiagram() || el->isInstrumentChange()))
adjustPlacement(el);
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(el);
}
else if (tag == "Tempo") {
TempoText* tt = new TempoText(score);
// hack - needed because tick tags are unreliable in 1.3 scores
// for symbols attached to anything but a measure
tt->setTrack(e.track());
readTempoText(tt, e);
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(tt);
}
else if (tag == "Marker" || tag == "Jump") {
Element* el = Element::name2Element(tag, score);
el->setTrack(e.track());
if (tag == "Marker") {
Marker* ma = toMarker(el);
readMarker(ma, e);
Element* markerEl = toElement(ma);
m->add(markerEl);
}
else {
el->read(e);
m->add(el);
}
}
else if (tag == "Image") {
if (MScore::noImages)
e.skipCurrentElement();
else {
Element* el = Element::name2Element(tag, score);
el->setTrack(e.track());
el->read(e);
segment = m->getSegment(SegmentType::ChordRest, e.tick());
segment->add(el);
}
}
//----------------------------------------------------
else if (tag == "stretch") {
double val = e.readDouble();
if (val < 0.0)
val = 0;
m->setUserStretch(val);
}
else if (tag == "noOffset")
m->setNoOffset(e.readInt());
else if (tag == "measureNumberMode")
m->setMeasureNumberMode(MeasureNumberMode(e.readInt()));
else if (tag == "irregular")
m->setIrregular(e.readBool());
else if (tag == "breakMultiMeasureRest")
m->setBreakMultiMeasureRest(e.readBool());
else if (tag == "sysInitBarLineType") {
const QString& val(e.readElementText());
BarLine* barLine = new BarLine(score);
barLine->setTrack(e.track());
barLine->setBarLineType(val);
segment = m->getSegment(SegmentType::BeginBarLine, m->tick());
segment->add(barLine);
}
else if (tag == "Tuplet") {
Tuplet* tuplet = new Tuplet(score);
tuplet->setTrack(e.track());
tuplet->setTick(e.tick());
tuplet->setParent(m);
readTuplet(tuplet, e);
e.addTuplet(tuplet);
}
else if (tag == "startRepeat") {
m->setRepeatStart(true);
e.readNext();
}
else if (tag == "endRepeat") {
m->setRepeatCount(e.readInt());
m->setRepeatEnd(true);
}
else if (tag == "vspacer" || tag == "vspacerDown") {
if (!m->vspacerDown(staffIdx)) {
Spacer* spacer = new Spacer(score);
spacer->setSpacerType(SpacerType::DOWN);
spacer->setTrack(staffIdx * VOICES);
m->add(spacer);
}
m->vspacerDown(staffIdx)->setGap(e.readDouble() * _spatium);
}
else if (tag == "vspacer" || tag == "vspacerUp") {
if (!m->vspacerUp(staffIdx)) {
Spacer* spacer = new Spacer(score);
spacer->setSpacerType(SpacerType::UP);
spacer->setTrack(staffIdx * VOICES);
m->add(spacer);
}
m->vspacerUp(staffIdx)->setGap(e.readDouble() * _spatium);
}
else if (tag == "visible")
m->setStaffVisible(staffIdx, e.readInt());
else if (tag == "slashStyle")
m->setStaffStemless(staffIdx, e.readInt());
else if (tag == "Beam") {
Beam* beam = new Beam(score);
beam->setTrack(e.track());
beam->read(e);
beam->setParent(0);
e.addBeam(beam);
}
else if (tag == "Segment") {
if (segment) segment->read(e);
else e.unknown();
}
else if (tag == "MeasureNumber") {
MeasureNumber* noText = new MeasureNumber(score);
readText206(e, noText, m);
noText->setTrack(e.track());
noText->setParent(m);
m->setNoText(noText->staffIdx(), noText);
}
else if (tag == "SystemDivider") {
SystemDivider* sd = new SystemDivider(score);
sd->read(e);
m->add(sd);
}
else if (tag == "Ambitus") {
Ambitus* range = new Ambitus(score);
readAmbitus(range, e);
segment = m->getSegment(SegmentType::Ambitus, e.tick());
range->setParent(segment); // a parent segment is needed for setTrack() to work
range->setTrack(trackZeroVoice(e.track()));
segment->add(range);
}
else if (tag == "multiMeasureRest") {
m->setMMRestCount(e.readInt());
// set tick to previous measure
m->setTick(e.lastMeasure()->tick());
e.setTick(e.lastMeasure()->tick());
}
else if (m->MeasureBase::readProperties(e))
;
else
e.unknown();
}
e.checkTuplets();
m->connectTremolo();
}
//---------------------------------------------------------
// readBox
//---------------------------------------------------------
static void readBox(Box* b, XmlReader& e)
{
b->setLeftMargin(0.0);
b->setRightMargin(0.0);
b->setTopMargin(0.0);
b->setBottomMargin(0.0);
b->setTopGap(0.0);
b->setBottomGap(0.0);
b->setPropertyFlags(Pid::TOP_GAP, PropertyFlags::UNSTYLED);
b->setPropertyFlags(Pid::BOTTOM_GAP, PropertyFlags::UNSTYLED);
b->setBoxHeight(Spatium(0)); // override default set in constructor
b->setBoxWidth(Spatium(0));
bool keepMargins = false; // whether original margins have to be kept when reading old file
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "HBox") {
HBox* hb = new HBox(b->score());
hb->read(e);
b->add(hb);
keepMargins = true; // in old file, box nesting used outer box margins
}
else if (tag == "VBox") {
VBox* vb = new VBox(b->score());
vb->read(e);
b->add(vb);
keepMargins = true; // in old file, box nesting used outer box margins
}
else if (tag == "Text") {
Text* t;
if (b->isTBox()) {
t = toTBox(b)->text();
readText206(e, t, t);
}
else {
t = new Text(b->score());
readText206(e, t, t);
if (t->empty()) {
qDebug("read empty text");
}
else
b->add(t);
}
}
else if (!b->readProperties(e))
e.unknown();
}
// with .msc versions prior to 1.17, box margins were only used when nesting another box inside this box:
// for backward compatibility set them to 0 in all other cases
if (b->score()->mscVersion() <= 114 && (b->isHBox() || b->isVBox()) && !keepMargins) {
b->setLeftMargin(0.0);
b->setRightMargin(0.0);
b->setTopMargin(0.0);
b->setBottomMargin(0.0);
}
}
//---------------------------------------------------------
// readStaffContent
//---------------------------------------------------------
static void readStaffContent(Score* score, XmlReader& e)
{
int staff = e.intAttribute("id", 1) - 1;
e.setTick(Fraction(0,1));
e.setTrack(staff * VOICES);
Box* lastReadBox = nullptr;
bool readMeasureLast = false;
if (staff == 0) {
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "Measure") {
if (lastReadBox) {
lastReadBox->setBottomGap(lastReadBox->bottomGap() + lastReadBox->propertyDefault(Pid::BOTTOM_GAP).toReal());
lastReadBox = nullptr;
}
readMeasureLast = true;
Measure* measure = 0;
measure = new Measure(score);
measure->setTick(e.tick());
//
// inherit timesig from previous measure
//
Measure* m = e.lastMeasure(); // measure->prevMeasure();
Fraction f(m ? m->timesig() : Fraction(4,4));
measure->setTicks(f);
measure->setTimesig(f);
readMeasure(measure, staff, e);
measure->checkMeasure(staff);
if (!measure->isMMRest()) {
score->measures()->add(measure);
e.setLastMeasure(measure);
e.setTick(measure->endTick());
}
else {
// this is a multi measure rest
// always preceded by the first measure it replaces
Measure* lm = e.lastMeasure();
if (lm) {
lm->setMMRest(measure);
measure->setTick(lm->tick());
}
}
}
else if (tag == "HBox" || tag == "VBox" || tag == "TBox" || tag == "FBox") {
Box* b = toBox(Element::name2Element(tag, score));
readBox(b, e);
b->setTick(e.tick());
score->measures()->add(b);
// If it's the first box, and comes before any measures, reset to
// 301 default.
if (!readMeasureLast && !lastReadBox) {
b->setTopGap(b->propertyDefault(Pid::TOP_GAP).toReal());
b->setPropertyFlags(Pid::TOP_GAP, PropertyFlags::STYLED);
}
else if (readMeasureLast)
b->setTopGap(b->topGap() + b->propertyDefault(Pid::TOP_GAP).toReal());
lastReadBox = b;
readMeasureLast = false;
}
else if (tag == "tick")
e.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
else
e.unknown();
}
}
else {
Measure* measure = score->firstMeasure();
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "Measure") {
if (measure == 0) {
qDebug("Score::readStaff(): missing measure!");
measure = new Measure(score);
measure->setTick(e.tick());
score->measures()->add(measure);
}
e.setTick(measure->tick());
readMeasure(measure, staff, e);
measure->checkMeasure(staff);
if (measure->isMMRest())
measure = e.lastMeasure()->nextMeasure();
else {
e.setLastMeasure(measure);
if (measure->mmRest())
measure = measure->mmRest();
else
measure = measure->nextMeasure();
}
}
else if (tag == "tick")
e.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
else
e.unknown();
}
}
}
//---------------------------------------------------------
// readStyle
//---------------------------------------------------------
static void readStyle(MStyle* style, XmlReader& e)
{
QString oldChordDescriptionFile = style->value(Sid::chordDescriptionFile).toString();
bool chordListTag = false;
excessTextStyles206.clear();
while (e.readNextStartElement()) {
QString tag = e.name().toString();
if (tag == "TextStyle")
readTextStyle206(style, e, excessTextStyles206);
else if (tag == "Spatium")
style->set(Sid::spatium, e.readDouble() * DPMM);
else if (tag == "page-layout")
readPageFormat(style, e);
else if (tag == "displayInConcertPitch")
style->set(Sid::concertPitch, QVariant(bool(e.readInt())));
else if (tag == "pedalY") {
qreal y = e.readDouble();
style->set(Sid::pedalPosBelow, QPointF(0.0, y));
}
else if (tag == "lyricsDistance") {
qreal y = e.readDouble();
style->set(Sid::lyricsPosBelow, QPointF(0.0, y));
}
else if (tag == "lyricsMinBottomDistance") {
// no longer meaningful since it is now measured from skyline rather than staff
//style->set(Sid::lyricsMinBottomDistance, QPointF(0.0, y));
e.skipCurrentElement();
}
else if (tag == "ottavaHook") {
qreal y = qAbs(e.readDouble());
style->set(Sid::ottavaHookAbove, y);
style->set(Sid::ottavaHookBelow, -y);
}
else if (tag == "endBarDistance") {
double d = e.readDouble();
d += style->value(Sid::barWidth).toDouble();
d += style->value(Sid::endBarWidth).toDouble();
style->set(Sid::endBarDistance, QVariant(d));
}
else if (tag == "ChordList") {
style->chordList()->clear();
style->chordList()->read(e);
style->setCustomChordList(true);
for (ChordFont f : style->chordList()->fonts) {
if (f.family == "MuseJazz") {
f.family = "MuseJazz Text";
}
}
chordListTag = true;
}
else if (tag == "harmonyY") {
qreal val = -e.readDouble();
if (val > 0.0) {
style->set(Sid::harmonyPlacement, int(Placement::BELOW));
style->set(Sid::chordSymbolAPosBelow, QPointF(.0, val));
}
else {
style->set(Sid::harmonyPlacement, int(Placement::ABOVE));
style->set(Sid::chordSymbolAPosBelow, QPointF(.0, val));
}
}
else {
if (!style->readProperties(e)) {
e.skipCurrentElement();
}
}
}
// if we just specified a new chord description file
// and didn't encounter a ChordList tag
// then load the chord description file
QString newChordDescriptionFile = style->value(Sid::chordDescriptionFile).toString();
if (newChordDescriptionFile != oldChordDescriptionFile && !chordListTag) {
if (!newChordDescriptionFile.startsWith("chords_") && style->value(Sid::chordStyle).toString() == "std") {
// should not normally happen,
// but treat as "old" (114) score just in case
style->set(Sid::chordStyle, QVariant(QString("custom")));
style->set(Sid::chordsXmlFile, QVariant(true));
qDebug("StyleData::load: custom chord description file %s with chordStyle == std", qPrintable(newChordDescriptionFile));
}
if (style->value(Sid::chordStyle).toString() == "custom")
style->setCustomChordList(true);
else
style->setCustomChordList(false);
style->chordList()->unload();
}
// make sure we have a chordlist
if (!chordListTag)
style->checkChordList();
}
//---------------------------------------------------------
// readScore
//---------------------------------------------------------
static bool readScore(Score* score, XmlReader& e)
{
while (e.readNextStartElement()) {
e.setTrack(-1);
const QStringRef& tag(e.name());
if (tag == "Staff")
readStaffContent(score, e);
else if (tag == "siglist")
score->sigmap()->read(e, score->fileDivision());
else if (tag == "Omr") {
#ifdef OMR
score->masterScore()->setOmr(new Omr(score));
score->masterScore()->omr()->read(e);
#else
e.skipCurrentElement();
#endif
}
else if (tag == "Audio") {
score->setAudio(new Audio);
score->audio()->read(e);
}
else if (tag == "showOmr")
score->masterScore()->setShowOmr(e.readInt());
else if (tag == "playMode")
score->setPlayMode(PlayMode(e.readInt()));
else if (tag == "LayerTag") {
int id = e.intAttribute("id");
const QString& t = e.attribute("tag");
QString val(e.readElementText());
if (id >= 0 && id < 32) {
score->layerTags()[id] = t;
score->layerTagComments()[id] = val;
}
}
else if (tag == "Layer") {
Layer layer;
layer.name = e.attribute("name");
layer.tags = e.attribute("mask").toUInt();
score->layer().append(layer);
e.readNext();
}
else if (tag == "currentLayer")
score->setCurrentLayer(e.readInt());
else if (tag == "Synthesizer")
score->synthesizerState().read(e);
else if (tag == "page-offset")
score->setPageNumberOffset(e.readInt());
else if (tag == "Division")
score->setFileDivision(e.readInt());
else if (tag == "showInvisible")
score->setShowInvisible(e.readInt());
else if (tag == "showUnprintable")
score->setShowUnprintable(e.readInt());
else if (tag == "showFrames")
score->setShowFrames(e.readInt());
else if (tag == "showMargins")
score->setShowPageborders(e.readInt());
else if (tag == "Style") {
qreal sp = score->style().value(Sid::spatium).toDouble();
readStyle(&score->style(), e);
if (score->style().value(Sid::MusicalTextFont).toString() == "MuseJazz")
score->style().set(Sid::MusicalTextFont, "MuseJazz Text");
// if (_layoutMode == LayoutMode::FLOAT || _layoutMode == LayoutMode::SYSTEM) {
if (score->layoutMode() == LayoutMode::FLOAT) {
// style should not change spatium in
// float mode
score->style().set(Sid::spatium, sp);
}
score->setScoreFont(ScoreFont::fontFactory(score->style().value(Sid::MusicalSymbolFont).toString()));
}
else if (tag == "copyright" || tag == "rights") {
Text* text = new Text(score);
readText206(e, text, text);
score->setMetaTag("copyright", text->xmlText());
delete text;
}
else if (tag == "movement-number")
score->setMetaTag("movementNumber", e.readElementText());
else if (tag == "movement-title")
score->setMetaTag("movementTitle", e.readElementText());
else if (tag == "work-number")
score->setMetaTag("workNumber", e.readElementText());
else if (tag == "work-title")
score->setMetaTag("workTitle", e.readElementText());
else if (tag == "source")
score->setMetaTag("source", e.readElementText());
else if (tag == "metaTag") {
QString name = e.attribute("name");
score->setMetaTag(name, e.readElementText());
}
else if (tag == "Part") {
Part* part = new Part(score);
readPart206(part, e);
score->parts().push_back(part);
}
else if ((tag == "HairPin") // TODO: do this elements exist here?
|| (tag == "Ottava")
|| (tag == "TextLine")
|| (tag == "Volta")
|| (tag == "Trill")
|| (tag == "Slur")
|| (tag == "Pedal")) {
Spanner* s = toSpanner(Element::name2Element(tag, score));
if (tag == "HairPin")
readHairpin206(e, toHairpin(s));
else if (tag == "Ottava")
readOttava(e, toOttava(s));
else if (tag == "TextLine")
readTextLine206(e, toTextLine(s));
else if (tag == "Volta")
readVolta206(e, toVolta(s));
else if (tag == "Trill")
readTrill206(e, toTrill(s));
else if (tag == "Slur")
readSlur206(e, toSlur(s));
else {
Q_ASSERT(tag == "Pedal");
readPedal(e, toPedal(s));
}
score->addSpanner(s);
}
else if (tag == "Excerpt") {
if (MScore::noExcerpts)
e.skipCurrentElement();
else {
if (score->isMaster()) {
Excerpt* ex = new Excerpt(static_cast<MasterScore*>(score));
ex->read(e);
score->excerpts().append(ex);
}
else {
qDebug("read206: readScore(): part cannot have parts");
e.skipCurrentElement();
}
}
}
else if (tag == "Score") { // recursion
if (MScore::noExcerpts)
e.skipCurrentElement();
else {
e.tracks().clear();
MasterScore* m = score->masterScore();
Score* s = new Score(m, MScore::baseStyle());
Excerpt* ex = new Excerpt(m);
ex->setPartScore(s);
e.setLastMeasure(nullptr);
readScore(s, e);
ex->setTracks(e.tracks());
m->addExcerpt(ex);
}
}
else if (tag == "PageList")
e.skipCurrentElement();
else if (tag == "name") {
QString n = e.readElementText();
if (!score->isMaster()) //ignore the name if it's not a child score
score->excerpt()->setTitle(n);
}
else if (tag == "layoutMode") {
QString s = e.readElementText();
if (s == "line")
score->setLayoutMode(LayoutMode::LINE);
else if (s == "system")
score->setLayoutMode(LayoutMode::SYSTEM);
else
qDebug("layoutMode: %s", qPrintable(s));
}
else
e.unknown();
}
if (e.error() != QXmlStreamReader::NoError) {
qDebug("%s: xml read error at line %lld col %lld: %s",
qPrintable(e.getDocName()), e.lineNumber(), e.columnNumber(),
e.name().toUtf8().data());
MScore::lastError = QObject::tr("XML read error at line %1, column %2: %3").arg(e.lineNumber()).arg(e.columnNumber()).arg(e.name().toString());
return false;
}
score->connectTies();
score->setFileDivision(MScore::division);
//
// sanity check for barLineSpan
//
#if 0 // TODO:barline
for (Staff* st : score->staves()) {
int barLineSpan = st->barLineSpan();
int idx = st->idx();
int n = score->nstaves();
if (idx + barLineSpan > n) {
qDebug("bad span: idx %d span %d staves %d", idx, barLineSpan, n);
// span until last staff
barLineSpan = n - idx;
st->setBarLineSpan(barLineSpan);
}
else if (idx == 0 && barLineSpan == 0) {
qDebug("bad span: idx %d span %d staves %d", idx, barLineSpan, n);
// span from the first staff until the start of the next span
barLineSpan = 1;
for (int i = 1; i < n; ++i) {
if (score->staff(i)->barLineSpan() == 0)
++barLineSpan;
else
break;
}
st->setBarLineSpan(barLineSpan);
}
// check spanFrom
int minBarLineFrom = st->lines(0) == 1 ? BARLINE_SPAN_1LINESTAFF_FROM : MIN_BARLINE_SPAN_FROMTO;
if (st->barLineFrom() < minBarLineFrom)
st->setBarLineFrom(minBarLineFrom);
if (st->barLineFrom() > st->lines(0) * 2)
st->setBarLineFrom(st->lines(0) * 2);
// check spanTo
Staff* stTo = st->barLineSpan() <= 1 ? st : score->staff(idx + st->barLineSpan() - 1);
// 1-line staves have special bar line spans
int maxBarLineTo = stTo->lines(0) == 1 ? BARLINE_SPAN_1LINESTAFF_TO : stTo->lines(0)*2;
int defaultBarLineTo = stTo->lines(0) == 1 ? BARLINE_SPAN_1LINESTAFF_TO : (stTo->lines(0) - 1) * 2;
if (st->barLineTo() == UNKNOWN_BARLINE_TO)
st->setBarLineTo(defaultBarLineTo);
if (st->barLineTo() < MIN_BARLINE_SPAN_FROMTO)
st->setBarLineTo(MIN_BARLINE_SPAN_FROMTO);
if (st->barLineTo() > maxBarLineTo)
st->setBarLineTo(maxBarLineTo);
// on single staff span, check spanFrom and spanTo are distant enough
if (st->barLineSpan() == 1) {
if (st->barLineTo() - st->barLineFrom() < MIN_BARLINE_FROMTO_DIST) {
st->setBarLineFrom(0);
st->setBarLineTo(defaultBarLineTo);
}
}
}
#endif
score->fixTicks();
if (score->isMaster()) {
MasterScore* ms = static_cast<MasterScore*>(score);
if (!ms->omr())
ms->setShowOmr(false);
ms->rebuildMidiMapping();
ms->updateChannel();
// ms->createPlayEvents();
}
return true;
}
//---------------------------------------------------------
// read
// <page-layout>
// <page-height>
// <page-width>
// <landscape>1</landscape>
// <page-margins type="both">
// <left-margin>28.3465</left-margin>
// <right-margin>28.3465</right-margin>
// <top-margin>28.3465</top-margin>
// <bottom-margin>56.6929</bottom-margin>
// </page-margins>
// </page-layout>
//---------------------------------------------------------
void PageFormat::read(XmlReader& e)
{
qreal _oddRightMargin = 0.0;
qreal _evenRightMargin = 0.0;
QString type;
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "page-margins") {
type = e.attribute("type","both");
qreal lm = 0.0, rm = 0.0, tm = 0.0, bm = 0.0;
while (e.readNextStartElement()) {
const QStringRef& t(e.name());
qreal val = e.readDouble() * 0.5 / PPI;
if (t == "left-margin")
lm = val;
else if (t == "right-margin")
rm = val;
else if (t == "top-margin")
tm = val;
else if (t == "bottom-margin")
bm = val;
else
e.unknown();
}
_twosided = type == "odd" || type == "even";
if (type == "odd" || type == "both") {
_oddLeftMargin = lm;
_oddRightMargin = rm;
_oddTopMargin = tm;
_oddBottomMargin = bm;
}
if (type == "even" || type == "both") {
_evenLeftMargin = lm;
_evenRightMargin = rm;
_evenTopMargin = tm;
_evenBottomMargin = bm;
}
}
else if (tag == "page-height")
_size.rheight() = e.readDouble() * 0.5 / PPI;
else if (tag == "page-width")
_size.rwidth() = e.readDouble() * .5 / PPI;
else
e.unknown();
}
qreal w1 = _size.width() - _oddLeftMargin - _oddRightMargin;
qreal w2 = _size.width() - _evenLeftMargin - _evenRightMargin;
_printableWidth = qMin(w1, w2); // silently adjust right margins
}
//---------------------------------------------------------
// read206
// import old version > 1.3 and < 3.x files
//---------------------------------------------------------
Score::FileError MasterScore::read206(XmlReader& e)
{
for (unsigned int i = 0; i < sizeof(style206)/sizeof(*style206); ++i)
style().set(style206[i].idx, style206[i].val);
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "programVersion") {
setMscoreVersion(e.readElementText());
parseVersion(mscoreVersion());
}
else if (tag == "programRevision")
setMscoreRevision(e.readIntHex());
else if (tag == "Score") {
if (!readScore(this, e))
return FileError::FILE_BAD_FORMAT;
}
else if (tag == "Revision") {
Revision* revision = new Revision;
revision->read(e);
revisions()->add(revision);
}
}
int id = 1;
for (LinkedElements* le : e.linkIds())
le->setLid(this, id++);
for (Staff* s : staves())
s->updateOttava();
// fix segment span
SegmentType st = SegmentType::BarLineType;
for (Segment* s = firstSegment(st); s; s = s->next1(st)) {
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
BarLine* b = toBarLine(s->element(staffIdx * VOICES));
if (!b)
continue;
int sp = b->spanStaff();
if (sp <= 0)
continue;
for (int span = 1; span <= sp; ++span) {
BarLine* nb = toBarLine(s->element((staffIdx + span) * VOICES));
if (!nb) {
nb = b->clone();
nb->setTrack((staffIdx + span) * VOICES);
s->add(nb);
}
nb->setSpanStaff(sp - span);
}
staffIdx += sp;
}
}
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
Staff* s = staff(staffIdx);
int sp = s->barLineSpan();
if (sp <= 0)
continue;
for (int span = 1; span <= sp; ++span) {
Staff* ns = staff(staffIdx + span);
ns->setBarLineSpan(sp - span);
}
staffIdx += sp;
}
// fix positions
// offset = saved offset - layout position
doLayout();
for (auto i : e.fixOffsets()) {
i.first->setOffset(i.second - i.first->pos());
}
// treat reading a 2.06 file as import
// on save warn if old file will be overwritten
setCreated(true);
// don't autosave (as long as there's no change to the score)
setAutosaveDirty(false);
return FileError::FILE_NO_ERROR;
}
}